/* vim: set noet ts=4: * $Id: wmpuzzle.c,v 1.46 2006/04/16 08:16:25 godisch Exp $ * * Copyright (c) 2002-2006 Martin A. Godisch */ #include #include #include #include #include #include #include #include #include #include #include #include "wmgeneral.h" #include "wmpuzzle.h" #define TILE_X(N) ((N) % 4 * 14 + 4) #define TILE_Y(N) ((N) / 4 * 14 + 4) #define BLANK_X (0) #define BLANK_Y (64) #define LAST_X (14) #define LAST_Y (64) #define TEMP_X (28) #define TEMP_Y (64) #define TILE_WIDTH (14) #define TILE_HEIGHT (14) #define BUFF_LEN (256) char #ifdef BSD *image = "daemon", #else *image = "linux", #endif *rcname = NULL; int blank = 15, puzzle[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; int main(int argc, char *argv[]) { int n, but_stat = -1, auto_save = 0, use_keyboard = 0, shuffle_count = -1; char *geometry = NULL, *xdisplay = NULL, #ifdef BSD **wmpuzzle_xpm = daemon_xpm, #else **wmpuzzle_xpm = linux_xpm, #endif state[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}, wmpuzzle_mask[64*64]; static int signals[] = {SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGTERM, SIGUSR1, SIGUSR2, 0}; XEvent Event; assert(sizeof(char) == 1); do_opts(argc, argv, &wmpuzzle_xpm, &auto_save, &use_keyboard, &shuffle_count, &xdisplay, &geometry); n = strlen(getenv("HOME")) + strlen("/.wmpuzzlerc") + 1; if ((rcname = malloc(n)) == NULL) { fprintf(stderr, "%s: %s\n", PACKAGE_NAME, strerror(errno)); exit(1); } snprintf(rcname, n, "%s/%s", getenv("HOME"), ".wmpuzzlerc"); for (n = 0; signals[n]; n++) { if (signal(signals[n], handler) == SIG_ERR) { fprintf(stderr, "%s: cannot set handler for signal %d\n", PACKAGE_NAME, signals[n]); fprintf(stderr, "%s: %s\n", PACKAGE_NAME, strerror(errno)); } } initXwindow(xdisplay); createXBMfromXPM(wmpuzzle_mask, wmpuzzle_xpm, 64, 64); openXwindow(argc, argv, wmpuzzle_xpm, wmpuzzle_mask, 64, 64, geometry, NULL); for (n = 0; n < 16; n++) AddMouseRegion(n, TILE_X(n), TILE_Y(n), TILE_X(n) + TILE_WIDTH - 1, TILE_Y(n) + TILE_HEIGHT - 1); if (shuffle_count >= 0 || !read_state(state)) shuffle(shuffle_count == -1 ? DEFAULT_SHUFFLE_COUNT : shuffle_count, state); if (auto_save) atexit(write_state); copyXPMArea(TILE_X(15), TILE_Y(15), TILE_WIDTH, TILE_HEIGHT, LAST_X, LAST_Y); prepare(state); while (1) { while (XPending(display)) { XNextEvent(display, &Event); switch (Event.type) { case Expose: RedrawWindow(); break; case DestroyNotify: XCloseDisplay(display); exit(0); case ButtonPress: but_stat = CheckMouseRegion(Event.xbutton.x, Event.xbutton.y); break; case ButtonRelease: switch (Event.xbutton.button) { case Button1: n = CheckMouseRegion(Event.xbutton.x, Event.xbutton.y); if (but_stat >= 0 && n == but_stat && valid(n)) { move(n); RedrawWindow(); } break; case Button2: if (read_state(state)) { prepare(state); RedrawWindow(); } break; case Button3: write_state(); break; } but_stat = -1; break; case KeyPress: if (!use_keyboard) break; switch (Event.xkey.keycode) { case 27: /* r */ if (read_state(state)) { prepare(state); RedrawWindow(); } break; case 39: /* s */ write_state(); break; case 98: /* up */ if (blank >= 12) break; move(blank + 4); break; case 100: /* left */ if (blank % 4 == 3) break; move(blank + 1); break; case 102: /* right */ if (blank % 4 == 0) break; move(blank - 1); break; case 104: /* down */ if (blank < 4) break; move(blank - 4); break; } but_stat = -1; RedrawWindow(); break; case EnterNotify: if (use_keyboard) XSetInputFocus(display, PointerRoot, RevertToParent, CurrentTime); break; case LeaveNotify: if (use_keyboard) XSetInputFocus(display, PointerRoot, RevertToParent, CurrentTime); break; } } usleep(50000L); } } void do_opts(int argc, char **argv, char ***wmpuzzle, int *autosave, int *keyboard, int *shuffle, char **xdisplay, char **geometry) { char *s; int i; static struct option long_opts[] = { {"help", 0, NULL, 'h'}, {"auto-save", 0, NULL, 'a'}, {"keyboard", 0, NULL, 'k'}, {"shuffle", 1, NULL, 's'}, {"version", 0, NULL, 'v'}, {"display", 1, NULL, 0 }, {"geometry", 1, NULL, 1 }, {NULL, 0, NULL, 0}}; int opt_index = 0; while (1) { if ((i = getopt_long(argc, argv, "haks:v", long_opts, &opt_index)) == -1) break; switch (i) { case 'a': *autosave = 1; break; case 'k': *keyboard = 1; break; case 's': i = strtol(optarg, &s, 10); if (*s || i <= 0) { fprintf(stderr, "%s: invalid shuffle count '%s'\n", PACKAGE_NAME, optarg); exit(1); } *shuffle = i; break; case 'h': printf("usage: %s [options] []\n", PACKAGE_NAME); printf(" -h, --help displays this command line summary,\n"); printf(" -a, --auto-save automatically save the puzzle state on program exit,\n"); printf(" -k, --keyboard enables the arrow keys on the keyboard,\n"); printf(" -s, --shuffle shuffles the image times, default %d,\n", DEFAULT_SHUFFLE_COUNT); printf(" -v, --version displays the version number,\n"); printf(" --display= open the mini window on display , e.g. ':0.0',\n"); printf(" --geometry= open the mini window at position , e.g. '+10+10'.\n"); exit(0); case 'v': printf("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION); printf("copyright (c) 2002-2006 Martin A. Godisch \n"); exit(0); case 0: *xdisplay = optarg; break; case 1: *geometry = optarg; break; case '?': exit(1); } } if (optind < argc) { optarg = argv[optind++]; if ((errno = XpmReadFileToData(optarg, wmpuzzle)) < 0) { fprintf(stderr, "%s: cannot read XPM from '%s'\n", PACKAGE_NAME, optarg); fprintf(stderr, "%s: %s\n", PACKAGE_NAME, XpmGetErrorString(errno)); exit(1); } if (transform(wmpuzzle) < 0) { switch (errno) { case EINTERNAL: fprintf(stderr, "%s: internal error reading '%s'\n", PACKAGE_NAME, optarg); break; case EINVALXPM: fprintf(stderr, "%s: unknown format of XPM file '%s'\n", PACKAGE_NAME, optarg); break; case ESMALLXPM: fprintf(stderr, "%s: XPM '%s' is too small, minimum is 48x48 pixels\n", PACKAGE_NAME, optarg); break; default: fprintf(stderr, "%s: %s\n", PACKAGE_NAME, strerror(errno)); } exit(1); } if ((s = strstr(optarg, ".xpm")) != NULL) *s = '\0'; else if ((s = strstr(optarg, ".XPM")) != NULL) *s = '\0'; if ((s = rindex(optarg, '/')) != NULL) image = s + 1; else image = optarg; } if (optind < argc) { fprintf(stderr, "%s: too many arguments -- %s\n", PACKAGE_NAME, argv[optind++]); exit(1); } } static void handler(int signo) { exit(0); } void shuffle(int n, char *state) { int i = blank, j = blank; time_t t; srand(time(&t)); for (; n; n--) { /* FIXME: optimize this! */ while (!valid(i) || i == j) i = rand() % 16; j = state[blank]; state[blank] = state[i]; state[i] = j; j = blank; blank = i; } } void prepare(const char *state) { int i, j, k; for (i = 0; i < 16; i++) { for (j = 0; puzzle[j] != state[i]; j++); if (i == j) continue; copyXPMArea(TILE_X(i), TILE_Y(i), TILE_WIDTH, TILE_HEIGHT, TEMP_X, TEMP_Y); copyXPMArea(TILE_X(j), TILE_Y(j), TILE_WIDTH, TILE_HEIGHT, TILE_X(i), TILE_Y(i)); copyXPMArea(TEMP_X, TEMP_Y, TILE_WIDTH, TILE_HEIGHT, TILE_X(j), TILE_Y(j)); k = puzzle[i]; puzzle[i] = puzzle[j]; puzzle[j] = k; } copyXPMArea(BLANK_X, BLANK_Y, TILE_WIDTH, TILE_HEIGHT, TILE_X(blank), TILE_Y(blank)); } void move(int n) { int i; copyXPMArea(TILE_X(n), TILE_Y(n), TILE_WIDTH, TILE_HEIGHT, TILE_X(blank), TILE_Y(blank)); copyXPMArea(BLANK_X, BLANK_Y, TILE_WIDTH, TILE_HEIGHT, TILE_X(n), TILE_Y(n)); puzzle[blank] = puzzle[n]; puzzle[n] = 15; blank = n; if (n != 15) return; for (i = 0; i < 16; i++) if (puzzle[i] != i) return; copyXPMArea(LAST_X, LAST_Y, TILE_WIDTH, TILE_HEIGHT, TILE_X(n), TILE_Y(n)); } int valid(int n) { return( (n / 4 == blank / 4 && (n % 4 == blank % 4 + 1 || n % 4 == blank % 4 - 1)) || (n % 4 == blank % 4 && (n / 4 == blank / 4 + 1 || n / 4 == blank / 4 - 1))); } char *read_state(char *state) { FILE *F; char buffer[BUFF_LEN], *p, *s, temp[17]; int i, line; if ((F = fopen(rcname, "r")) == NULL) { if (errno != ENOENT) { fprintf(stderr, "%s: cannot open rcfile %s\n", PACKAGE_NAME, rcname); fprintf(stderr, "%s: %s\n", PACKAGE_NAME, strerror(errno)); } return(NULL); } for (line = 1;; line++) { fgets(buffer, sizeof(buffer), F); if (feof(F)) break; if ((s = strchr(buffer, '\n'))) *s = '\0'; if (buffer[0] == '#' || buffer[0] == '\0') continue; s = strchr(buffer, ':'); if (!s) { fprintf(stderr, "%s: missing ':' in %s line %d\n", PACKAGE_NAME, rcname, line); continue; } *s = '\0'; s++; if (strcmp(buffer, image)) continue; if (strlen(s) != 16) { fprintf(stderr, "%s: invalid state in %s line %d\n", PACKAGE_NAME, rcname, line); fclose(F); return(NULL); } s += 15; for (i = 15; i >= 0; i--) { temp[i] = strtol(s--, &p, 16); if (*p) { fprintf(stderr, "%s: invalid state in %s line %d\n", PACKAGE_NAME, rcname, line); fclose(F); return(NULL); } if (temp[i] == 15) blank = i; s[1] = '\0'; } memcpy(state, temp, 16); fclose(F); return(state); } fclose(F); return(NULL); } void write_state(void) { FILE *F, *G; char *s, buffer[BUFF_LEN], entry[BUFF_LEN], tmpname[BUFF_LEN + 4]; int line, written = 0; strcpy(tmpname, rcname); strcat(tmpname, ".new"); umask(0077); if ((F = fopen(tmpname, "w")) == NULL) { fprintf(stderr, "%s: cannot open tempfile '%s'\n", PACKAGE_NAME, tmpname); fprintf(stderr, "%s: %s\n", PACKAGE_NAME, strerror(errno)); return; } if ((G = fopen(rcname, "r")) == NULL && errno != ENOENT) { fprintf(stderr, "%s: cannot open rcfile '%s'\n", PACKAGE_NAME, rcname); fprintf(stderr, "%s: %s\n", PACKAGE_NAME, strerror(errno)); fclose(F); unlink(tmpname); return; } sprintf(entry, "%s:%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x\n", image, puzzle[0], puzzle[1], puzzle[2], puzzle[3], puzzle[4], puzzle[5], puzzle[6], puzzle[7], puzzle[8], puzzle[9], puzzle[10], puzzle[11], puzzle[12], puzzle[13], puzzle[14], puzzle[15]); if (G) { for (line = 1;; line++) { if (line > 1) fputs(buffer, F); fgets(buffer, sizeof(buffer), G); if (feof(G)) break; if (buffer[0] == '#' || buffer[0] == '\n') continue; s = strchr(buffer, ':'); if (!s) { fprintf(stderr, "%s: missing ':' in %s line %d\n", PACKAGE_NAME, rcname, line); continue; } if (!strncmp(buffer, image, s - buffer)) { strcpy(buffer, entry); written = 1; } } fclose(G); } if (!written) fputs(entry, F); fclose(F); if (rename(tmpname, rcname)) { fprintf(stderr, "%s: cannot overwrite rcfile '%s'\n", PACKAGE_NAME, rcname); fprintf(stderr, "%s: %s\n", PACKAGE_NAME, strerror(errno)); return; } } int transform(char ***puzzle) { char **src = *puzzle, **dst = NULL, *s = NULL, *d = NULL, *color[6] = { "#00000000FFFF", "#000000000000", "#861782078E38", "#F7DEF3CEFFFF", "#C71BC30BC71B", "#AEBAAAAAAEBA"}, code[64]; int max_len, width, height, numcol, chars, i, j, k, n, si; if (sscanf(src[0], "%d %d %d %d", &width, &height, &numcol, &chars) != 4) { errno = EINVALXPM; return -1; } if (width < 48 || height < 48) { errno = ESMALLXPM; return -1; } if (chars >= sizeof(code)) { errno = EINTERNAL; return -1; } numcol += 6; n = 1 + numcol + 64 + 14; max_len = 64 * chars + 1; if ((dst = malloc(n * sizeof(char*))) == NULL) return -1; for (i = 0; i < n; i++) if ((dst[i] = malloc(max_len)) == NULL) return -1; *puzzle = dst; snprintf(dst[0], max_len, "%d %d %d %d", 64, 78, numcol, chars); memset(code, 35, chars); code[chars] = '\0'; for (i = 0; i < 6; i++) { TRY_AGAIN: for (j = 1; j <= numcol - 6; j++) { if (strncmp(src[j], code, chars) == 0) { for (k = 0; k < chars; k++) if (code[k] < 126) { code[k]++; goto TRY_AGAIN; } fprintf(stderr, "%s: not enough free color symbols\n", PACKAGE_NAME); exit(1); } } snprintf(dst[i+1], max_len, "%s\tc %s", code, color[i]); if ((color[i] = strdup(code)) == NULL) return -1; if (i < 5) { for (k = 0; k < chars; k++) if (code[k] < 126) { code[k]++; goto NEXT_COLOR; } fprintf(stderr, "%s: not enough free color symbols\n", PACKAGE_NAME); exit(1); } NEXT_COLOR:; } for (i = 7; i <= numcol; i++) snprintf(dst[i], max_len, "%s", src[i-6]); for (i = numcol + 1; i <= numcol + 3; i++) { d = dst[i]; for (j = 0; j < 64; j++) { memcpy(d, color[0], chars); d += chars; } *d++ = '\0'; } d = dst[numcol + 4]; for (j = 0; j < 64; j++) { if (j < 3 || j > 60) memcpy(d, color[0], chars); else memcpy(d, color[1], chars); d += chars; } *d++ = '\0'; for (i = 4, si = numcol - 5; i < 60; i++) { d = dst[i + numcol + 1]; for (j = 0; j < 3; j++) { memcpy(d, color[0], chars); d += chars; } memcpy(d, color[1], chars); d += chars; switch (i % 14) { case 3: for (j = 4; j < 60; j++) { if (j % 14 == 4) memcpy(d, color[2], chars); else memcpy(d, color[1], chars); d += chars; } break; case 4: for (j = 4; j < 60; j++) { if (j % 14 == 3) memcpy(d, color[2], chars); else memcpy(d, color[3], chars); d += chars; } break; default: s = src[si++]; for (j = 4; j < 60; j++) { switch (j % 14) { case 3: memcpy(d, color[1], chars); break; case 4: memcpy(d, color[3], chars); break; default: if (strlen(s) < chars) { errno = EINVALXPM; return -1; } memcpy(d, s, chars); s += chars; } d += chars; } } memcpy(d, color[4], chars); d += chars; for (j = 61; j < 64; j++) { memcpy(d, color[0], chars); d += chars; } *d++ = '\0'; } d = dst[numcol + 61]; for (j = 0; j < 64; j++) { if (j < 3 || j > 60) memcpy(d, color[0], chars); else memcpy(d, color[4], chars); d += chars; } *d++ = '\0'; for (i = numcol + 62; i <= numcol + 64; i++) { d = dst[i]; for (j = 0; j < 64; j++) { memcpy(d, color[0], chars); d += chars; } *d++ = '\0'; } d = dst[numcol + 65]; for (j = 0; j < 13; j++) { memcpy(d, color[1], chars); d += chars; } memcpy(d, color[2], chars); d += chars; for (j = 14; j < 64; j++) { memcpy(d, color[0], chars); d += chars; } *d++ = '\0'; for (i = numcol + 66; i < numcol + 78; i++) { d = dst[i]; memcpy(d, color[1], chars); d += chars; for (j = 1; j < 13; j++) { memcpy(d, color[5], chars); d += chars; } memcpy(d, color[3], chars); d += chars; for (j = 14; j < 64; j++) { memcpy(d, color[0], chars); d += chars; } *d++ = '\0'; } d = dst[numcol + 78]; memcpy(d, color[2], chars); d += chars; for (j = 1; j < 14; j++) { memcpy(d, color[3], chars); d += chars; } for (j = 14; j < 64; j++) { memcpy(d, color[0], chars); d += chars; } *d++ = '\0'; /* FIXME: free src */ return 0; }