common.c (9480B)
1 /* 2 * Copyright (c) 2021-2024 lumidify <nobody@lumidify.org> 3 * 4 * Permission to use, copy, modify, and/or distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 #include <stdio.h> 18 #include <errno.h> 19 #include <string.h> 20 #include <stdlib.h> 21 #include <limits.h> 22 23 #include <X11/X.h> 24 #include <X11/Xlib.h> 25 #include <X11/Xutil.h> 26 #ifndef NODB 27 #include <X11/extensions/Xdbe.h> 28 #endif 29 30 #include <Imlib2.h> 31 32 #include "common.h" 33 34 void 35 setup_x(GraphicsContext *ctx, int window_w, int window_h, int line_width, int cache_size) { 36 XSetWindowAttributes attrs; 37 XGCValues gcv; 38 39 ctx->dpy = XOpenDisplay(NULL); 40 ctx->screen = DefaultScreen(ctx->dpy); 41 ctx->vis = DefaultVisual(ctx->dpy, ctx->screen); 42 ctx->depth = DefaultDepth(ctx->dpy, ctx->screen); 43 ctx->cm = DefaultColormap(ctx->dpy, ctx->screen); 44 ctx->dirty = 1; 45 46 #ifndef NODB 47 ctx->db_enabled = 0; 48 /* based on http://wili.cc/blog/xdbe.html */ 49 int major, minor; 50 if (XdbeQueryExtension(ctx->dpy, &major, &minor)) { 51 int num_screens = 1; 52 Drawable screens[] = { DefaultRootWindow(ctx->dpy) }; 53 XdbeScreenVisualInfo *info = XdbeGetVisualInfo( 54 ctx->dpy, screens, &num_screens 55 ); 56 if (!info || num_screens < 1 || info->count < 1) { 57 fprintf(stderr, 58 "Warning: No visuals support Xdbe, " 59 "double buffering disabled.\n" 60 ); 61 } else { 62 XVisualInfo xvisinfo_templ; 63 xvisinfo_templ.visualid = info->visinfo[0].visual; 64 xvisinfo_templ.screen = 0; 65 xvisinfo_templ.depth = info->visinfo[0].depth; 66 int matches; 67 XVisualInfo *xvisinfo_match = XGetVisualInfo( 68 ctx->dpy, 69 VisualIDMask | VisualScreenMask | VisualDepthMask, 70 &xvisinfo_templ, &matches 71 ); 72 if (!xvisinfo_match || matches < 1) { 73 fprintf(stderr, 74 "Warning: Couldn't match a Visual with " 75 "double buffering, double buffering disabled.\n" 76 ); 77 } else { 78 ctx->vis = xvisinfo_match->visual; 79 ctx->depth = xvisinfo_match->depth; 80 ctx->db_enabled = 1; 81 } 82 XFree(xvisinfo_match); 83 } 84 XdbeFreeVisualInfo(info); 85 } else { 86 fprintf(stderr, "Warning: No Xdbe support, double buffering disabled.\n"); 87 } 88 #endif 89 90 memset(&attrs, 0, sizeof(attrs)); 91 attrs.background_pixel = BlackPixel(ctx->dpy, ctx->screen); 92 attrs.colormap = ctx->cm; 93 /* this causes the window contents to be kept 94 * when it is resized, leading to less flicker */ 95 attrs.bit_gravity = NorthWestGravity; 96 ctx->win = XCreateWindow(ctx->dpy, DefaultRootWindow(ctx->dpy), 0, 0, 97 window_w, window_h, 0, ctx->depth, 98 InputOutput, ctx->vis, CWBackPixel | CWColormap | CWBitGravity, &attrs); 99 100 #ifndef NODB 101 if (ctx->db_enabled) { 102 ctx->back_buf = XdbeAllocateBackBufferName( 103 ctx->dpy, ctx->win, XdbeCopied 104 ); 105 ctx->drawable = ctx->back_buf; 106 } else { 107 ctx->drawable = ctx->win; 108 } 109 #else 110 ctx->drawable = ctx->win; 111 #endif 112 113 memset(&gcv, 0, sizeof(gcv)); 114 gcv.line_width = line_width; 115 ctx->gc = XCreateGC(ctx->dpy, ctx->win, GCLineWidth, &gcv); 116 117 XSelectInput( 118 ctx->dpy, ctx->win, 119 StructureNotifyMask | KeyPressMask | ButtonPressMask | 120 ButtonReleaseMask | PointerMotionMask | ExposureMask 121 ); 122 123 ctx->wm_delete_msg = XInternAtom(ctx->dpy, "WM_DELETE_WINDOW", False); 124 XSetWMProtocols(ctx->dpy, ctx->win, &ctx->wm_delete_msg, 1); 125 126 /* note: since cache_size is <= 1024, this definitely fits in long */ 127 long cs = (long)cache_size * 1024 * 1024; 128 if (cs > INT_MAX) { 129 fprintf(stderr, "Cache size would cause integer overflow.\n"); 130 exit(1); 131 } 132 imlib_set_cache_size((int)cs); 133 imlib_set_color_usage(128); 134 imlib_context_set_dither(1); 135 imlib_context_set_display(ctx->dpy); 136 imlib_context_set_visual(ctx->vis); 137 imlib_context_set_colormap(ctx->cm); 138 imlib_context_set_drawable(ctx->drawable); 139 ctx->updates = imlib_updates_init(); 140 ctx->cur_image = NULL; 141 } 142 143 void 144 cleanup_x(GraphicsContext *ctx) { 145 if (ctx->cur_image) { 146 imlib_context_set_image(ctx->cur_image); 147 imlib_free_image(); 148 } 149 XDestroyWindow(ctx->dpy, ctx->win); 150 XCloseDisplay(ctx->dpy); 151 } 152 153 int 154 parse_int(const char *str, int min, int max, int *value) { 155 char *end; 156 long l = strtol(str, &end, 10); 157 if (min > max) 158 return 1; 159 if (str == end || *end != '\0') { 160 return 1; 161 } else if (l < min || l > max || ((l == LONG_MIN || 162 l == LONG_MAX) && errno == ERANGE)) { 163 return 1; 164 } 165 *value = (int)l; 166 167 return 0; 168 } 169 170 void 171 queue_area_update(GraphicsContext *ctx, ImageSize *sz, int x, int y, int w, int h) { 172 if (x > sz->scaled_w || y > sz->scaled_h) 173 return; 174 ctx->updates = imlib_update_append_rect( 175 ctx->updates, x, y, 176 w + x > sz->scaled_w ? sz->scaled_w - x : w, 177 h + y > sz->scaled_h ? sz->scaled_h - y : h 178 ); 179 ctx->dirty = 1; 180 } 181 182 void 183 clear_screen(GraphicsContext *ctx) { 184 185 /* clear the window completely */ 186 XSetForeground(ctx->dpy, ctx->gc, BlackPixel(ctx->dpy, ctx->screen)); 187 XFillRectangle( 188 ctx->dpy, ctx->drawable, ctx->gc, 189 0, 0, ctx->window_w, ctx->window_h 190 ); 191 } 192 193 void 194 draw_image_updates(GraphicsContext *ctx, ImageSize *sz) { 195 Imlib_Image buffer; 196 Imlib_Updates current_update; 197 198 /* draw the parts of the image that need to be redrawn */ 199 ctx->updates = imlib_updates_merge_for_rendering( 200 ctx->updates, sz->scaled_w, sz->scaled_h 201 ); 202 /* FIXME: check since when imlib_render_image_updates_on_drawable supported, also maybe just render_on_drawable part scaled */ 203 for (current_update = ctx->updates; current_update; 204 current_update = imlib_updates_get_next(current_update)) { 205 int up_x, up_y, up_w, up_h; 206 imlib_updates_get_coordinates(current_update, &up_x, &up_y, &up_w, &up_h); 207 buffer = imlib_create_image(up_w, up_h); 208 imlib_context_set_blend(0); 209 imlib_context_set_image(buffer); 210 imlib_blend_image_onto_image( 211 ctx->cur_image, 0, 0, 0, 212 sz->orig_w, sz->orig_h, 213 -up_x, -up_y, 214 sz->scaled_w, sz->scaled_h); 215 imlib_render_image_on_drawable(up_x, up_y); 216 imlib_free_image(); 217 } 218 if (ctx->updates) 219 imlib_updates_free(ctx->updates); 220 ctx->updates = imlib_updates_init(); 221 } 222 223 void 224 wipe_around_image(GraphicsContext *ctx, ImageSize *sz) { 225 226 /* wipe the black area around the image */ 227 XSetForeground(ctx->dpy, ctx->gc, BlackPixel(ctx->dpy, ctx->screen)); 228 XFillRectangle( 229 ctx->dpy, ctx->drawable, ctx->gc, 230 0, sz->scaled_h, sz->scaled_w, ctx->window_h - sz->scaled_h 231 ); 232 XFillRectangle( 233 ctx->dpy, ctx->drawable, ctx->gc, 234 sz->scaled_w, 0, ctx->window_w - sz->scaled_w, ctx->window_h 235 ); 236 } 237 238 void 239 swap_buffers(GraphicsContext *ctx) { 240 #ifndef NODB 241 if (ctx->db_enabled) { 242 XdbeSwapInfo swap_info; 243 swap_info.swap_window = ctx->win; 244 swap_info.swap_action = XdbeCopied; 245 246 if (!XdbeSwapBuffers(ctx->dpy, &swap_info, 1)) 247 fprintf(stderr, "Warning: Unable to swap buffers.\n"); 248 } 249 #endif 250 ctx->dirty = 0; 251 } 252 253 /* get the scaled size of an image based on the current window size */ 254 void 255 get_scaled_size(GraphicsContext *ctx, int orig_w, int orig_h, int *scaled_w, int *scaled_h) { 256 double scale_w, scale_h; 257 scale_w = (double)ctx->window_w / (double)orig_w; 258 scale_h = (double)ctx->window_h / (double)orig_h; 259 if (orig_w <= ctx->window_w && orig_h <= ctx->window_h) { 260 *scaled_w = orig_w; 261 *scaled_h = orig_h; 262 } else if (scale_w * orig_h > ctx->window_h) { 263 *scaled_w = (int)(scale_h * orig_w); 264 *scaled_h = ctx->window_h; 265 } else { 266 *scaled_w = ctx->window_w; 267 *scaled_h = (int)(scale_w * orig_h); 268 } 269 } 270 271 void 272 next_picture(int cur_selection, char **filenames, int num_files, int copy_box) { 273 if (cur_selection + 1 >= num_files) 274 return; 275 Imlib_Image tmp_image = NULL; 276 int tmp_cur_selection = cur_selection; 277 /* loop until we find a loadable file */ 278 while (!tmp_image && tmp_cur_selection + 1 < num_files) { 279 tmp_cur_selection++; 280 if (!filenames[tmp_cur_selection]) 281 continue; 282 tmp_image = imlib_load_image_immediately( 283 filenames[tmp_cur_selection] 284 ); 285 if (!tmp_image) { 286 fprintf( 287 stderr, "Warning: Unable to load image '%s'.\n", 288 filenames[tmp_cur_selection] 289 ); 290 filenames[tmp_cur_selection] = NULL; 291 } 292 } 293 /* immediately exit program if no loadable image is found on startup */ 294 if (cur_selection < 0 && !tmp_image) { 295 fprintf(stderr, "No loadable images found.\n"); 296 cleanup(); 297 exit(1); 298 } 299 if (!tmp_image) 300 return; 301 302 change_picture(tmp_image, tmp_cur_selection, copy_box); 303 } 304 305 void 306 last_picture(int cur_selection, char **filenames, int copy_box) { 307 if (cur_selection <= 0) 308 return; 309 Imlib_Image tmp_image = NULL; 310 int tmp_cur_selection = cur_selection; 311 /* loop until we find a loadable file */ 312 while (!tmp_image && tmp_cur_selection > 0) { 313 tmp_cur_selection--; 314 if (!filenames[tmp_cur_selection]) 315 continue; 316 tmp_image = imlib_load_image_immediately( 317 filenames[tmp_cur_selection] 318 ); 319 if (!tmp_image) { 320 fprintf( 321 stderr, "Warning: Unable to load image '%s'.\n", 322 filenames[tmp_cur_selection] 323 ); 324 filenames[tmp_cur_selection] = NULL; 325 } 326 } 327 328 if (!tmp_image) 329 return; 330 331 change_picture(tmp_image, tmp_cur_selection, copy_box); 332 }