pictw_t * simg_load_icon(session_t *ps, Window wid, int desired_size) { pictw_t *pictw = NULL; bool processed = false; { // _NET_WM_ICON int best_width = 0, best_height = 0; float best_scale = 1.0f, best_area = 0.0f; const unsigned char *best_data = NULL; winprop_t prop = wid_get_prop_adv(ps, wid, _NET_WM_ICON, 0, ICON_PROP_MAXLEN, XA_CARDINAL, 32); if (prop.nitems) { int width = 0, height = 0; const long *end = prop.data32 + prop.nitems; // Format: WIDTH HEIGHT DATA (32-bit) int wanted_bytes = 0; for (const long *p = prop.data32; p < end; p += wanted_bytes) { if (p + 2 >= end) { printfef("(%#010lx): %d trailing byte(s).", wid, (int) (end - p)); break; } width = p[0]; height = p[1]; if (width <= 0 || height <= 0) { printfef("(%#010lx): (offset %d, width %d, height %d) Invalid width/height.", wid, (int) (p - prop.data32), width, height); break; } wanted_bytes = 2 + width * height; if ((end - p) < wanted_bytes) { printfef("(%#010lx): (offset %d, width %d, height %d) Not enough bytes (%d/%d).", wid, (int) (p - prop.data32), width, height, (int) (end - p), wanted_bytes); break; } // Prefer larger ones if possible if (best_width >= desired_size && best_height >= desired_size && (width < desired_size || height < desired_size)) continue; float scale = MAX(1.0f, MIN((float) best_height / height, (float) best_width / width)); float area = width * height * scale * scale; if (area > best_area) { best_width = width; best_height = height; best_scale = scale; best_area = area; best_data = (const unsigned char *) (p + 2); } } } if (best_data) { { unsigned char *converted_data = simg_data32_from_long( (const long *) best_data, best_width * best_height); simg_data32_premultiply(converted_data, best_width * best_height); pictw = simg_data_to_pictw(ps, best_width, best_height, 32, converted_data, 0); if (converted_data != best_data) free(converted_data); } if (!pictw) printfef("(%#010lx): Failed to create picture.", wid); /* if (pictw) printfdf("(%#010lx): (offset %d, width %d, height %d) Loaded.", wid, (int) (best_data - prop.data8), pictw->width, pictw->height); */ } free_winprop(&prop); } if (pictw) goto simg_load_icon_end; // WM_HINTS // Our method probably fills 1-8 bit pixmaps as black instead of using // "suitable background/foreground". I hope it isn't a problem, though. { XWMHints *h = XGetWMHints(ps->dpy, wid); if (h && (IconPixmapHint & h->flags) && h->icon_pixmap) pictw = simg_pixmap_to_pictw(ps, 0, 0, 0, h->icon_pixmap, h->icon_mask); sxfree(h); } if (pictw) goto simg_load_icon_end; // KWM_WIN_ICON // Same issue as above. { winprop_t prop = wid_get_prop_adv(ps, wid, KWM_WIN_ICON, 0, 2, KWM_WIN_ICON, 32); if (prop.nitems) { Pixmap pxmap = prop.data32[0], mask = (prop.nitems >= 2 ? prop.data32[1]: None); if (pxmap) pictw = simg_pixmap_to_pictw(ps, 0, 0, 0, pxmap, mask); } free_winprop(&prop); } if (pictw) goto simg_load_icon_end; simg_load_icon_end: // Post-processing if (pictw && !processed) { pictw = simg_postprocess(ps, pictw, PICTPOSP_SCALEK, desired_size, desired_size, ALIGN_MID, ALIGN_MID, NULL); /* printfdf("(%#010lx): (width %d, height %d) Processed.", wid, pictw->width, pictw->height); */ } return pictw; }
pictw_t * sjpg_read(session_t *ps, const char *path) { pictw_t *pictw = NULL; JSAMPLE *data = NULL; bool need_abort = false; struct jpeg_error_mgr jerr; struct jpeg_decompress_struct cinfo = { .err = jpeg_std_error(&jerr), }; jpeg_create_decompress(&cinfo); FILE *fp = fopen(path, "rb"); if (unlikely(!fp)) { printfef("(\"%s\"): Failed to open file.", path); goto sjpg_read_end; } jpeg_stdio_src(&cinfo, fp); jpeg_read_header(&cinfo, TRUE); jpeg_start_decompress(&cinfo); need_abort = true; int width = 0, height = 0, depth = 24; { const int comps = 4; data = allocchk(malloc(cinfo.output_width * cinfo.output_height * comps * sizeof(JSAMPLE))); JSAMPROW rowptrs[cinfo.output_height]; for (int i = 0; i < cinfo.output_height; ++i) rowptrs[i] = data + i * cinfo.output_width * comps; width = cinfo.output_width; height = cinfo.output_height; // libjpeg enforces a loop to read scanlines while (cinfo.output_scanline < cinfo.output_height) { if (unlikely(!jpeg_read_scanlines(&cinfo, &rowptrs[cinfo.output_scanline], cinfo.output_height - cinfo.output_scanline))) { printfef("(\"%s\"): Failed to read scanline %d.", path, cinfo.output_scanline); goto sjpg_read_end; } } // Expand greyscale value to RGBA if (1 == cinfo.output_components) { for (int i = 0; i < height; ++i) for (int j = width - 1; j >= 0; --j) { unsigned char a = rowptrs[i][j]; rowptrs[i][j * comps + 0] = a; rowptrs[i][j * comps + 1] = a; rowptrs[i][j * comps + 2] = a; rowptrs[i][j * comps + 3] = 0xff; } } else if (3 == cinfo.output_components) { for (int i = 0; i < height; ++i) { simg_data24_fillalpha(&data[i * 4 * width], width); simg_data24_tobgr(&data[i * 4 * width], width); } } } if (unlikely(!jpeg_finish_decompress(&cinfo))) { printfef("(\"%s\"): Failed to finish decompression.", path); goto sjpg_read_end; } need_abort = false; pictw = simg_data_to_pictw(ps, width, height, depth, data, 0); free(data); if (unlikely(!pictw)) { printfef("(\"%s\"): Failed to create Picture.", path); goto sjpg_read_end; } sjpg_read_end: /* if (unlikely(data && !pictw)) free(data); */ if (unlikely(need_abort)) jpeg_abort_decompress(&cinfo); if (likely(fp)) fclose(fp); jpeg_destroy_decompress(&cinfo); return pictw; }
int clientwin_handle(ClientWin *cw, XEvent *ev) { MainWin *mw = cw->mainwin; session_t *ps = mw->ps; if (ev->type == ButtonRelease) { const unsigned button = ev->xbutton.button; if (cw->mainwin->pressed_mouse) { if (button < MAX_MOUSE_BUTTONS) { int ret = clientwin_action(cw, ps->o.bindings_miwMouse[button]); if (ret) { printfef("(): Quitting."); return ret; } } } else printfef("(): ButtonRelease %u ignored.", button); } else if (ev->type == KeyRelease) { if (cw->mainwin->pressed_key) { if (ev->xkey.keycode == cw->mainwin->key_up || ev->xkey.keycode == cw->mainwin->key_k) focus_up(cw); else if (ev->xkey.keycode == cw->mainwin->key_down || ev->xkey.keycode == cw->mainwin->key_j) focus_down(cw); else if (ev->xkey.keycode == cw->mainwin->key_left || ev->xkey.keycode == cw->mainwin->key_h) focus_left(cw); else if (ev->xkey.keycode == cw->mainwin->key_right || ev->xkey.keycode == cw->mainwin->key_l) focus_right(cw); else if (ev->xkey.keycode == cw->mainwin->key_enter || ev->xkey.keycode == cw->mainwin->key_space) { childwin_focus(cw); return 1; } else report_key_unbinded(ev); } else report_key_ignored(ev); } else if (ev->type == ButtonPress) { cw->mainwin->pressed_mouse = true; /* if (ev->xbutton.button == 1) cw->mainwin->pressed = cw; */ } else if (KeyPress == ev->type) { cw->mainwin->pressed_key = true; } else if(ev->type == FocusIn) { cw->focused = 1; clientwin_render(cw); XFlush(ps->dpy); } else if(ev->type == FocusOut) { cw->focused = 0; clientwin_render(cw); XFlush(ps->dpy); } else if(ev->type == EnterNotify) { XSetInputFocus(ps->dpy, cw->mini.window, RevertToParent, CurrentTime); if (cw->mainwin->tooltip) { int win_title_len = 0; FcChar8 *win_title = wm_get_window_title(ps, cw->wid_client, &win_title_len); if (win_title) { tooltip_map(cw->mainwin->tooltip, ev->xcrossing.x_root, ev->xcrossing.y_root, win_title, win_title_len); free(win_title); } } } else if(ev->type == LeaveNotify) { // XSetInputFocus(ps->dpy, mw->window, RevertToParent, CurrentTime); if(cw->mainwin->tooltip) tooltip_unmap(cw->mainwin->tooltip); } return 0; }
pictw_t * sgif_read(session_t *ps, const char *path) { assert(path); pictw_t *pictw = NULL; GifPixelType *data = NULL; unsigned char *tdata = NULL; GifRecordType rectype; int ret = 0, err = 0; const char *errstr = NULL; #ifdef SGIF_THREADSAFE GifFileType *f = DGifOpenFileName(path, &err); #else GifFileType *f = DGifOpenFileName(path); #endif if (unlikely(!f)) { #ifdef SGIF_HAS_ERROR err = GifError(); errstr = GifErrorString(); #endif #ifdef SGIF_THREADSAFE errstr = GifErrorString(err); #endif printfef("(\"%s\"): Failed to open file: %d (%s)", path, err, errstr); goto sgif_read_end; } int width = 0, height = 0, transp = -1; { int i = 0; while (GIF_OK == (ret = DGifGetRecordType(f, &rectype)) && TERMINATE_RECORD_TYPE != rectype) { ++i; switch (rectype) { case UNDEFINED_RECORD_TYPE: printfef("(\"%s\"): %d: Encountered a record of unknown type.", path, i); break; case SCREEN_DESC_RECORD_TYPE: printfef("(\"%s\"): %d: Encountered a record of " "ScreenDescRecordType. This shouldn't happen!", path, i); break; case IMAGE_DESC_RECORD_TYPE: if (data) { printfef("(\"%s\"): %d: Extra image section ignored.", path, i); break; } if (GIF_ERROR == DGifGetImageDesc(f)) { printfef("(\"%s\"): %d: Failed to read GIF image info.", path, i); break; } width = f->Image.Width; height = f->Image.Height; if (width <= 0 || height <= 0) { printfef("(\"%s\"): %d: Width/height invalid.", path, i); break; } assert(!data); data = allocchk(malloc(width * height * sizeof(GifPixelType))); // FIXME: Interlace images may need special treatments for (int j = 0; j < height; ++j) if (GIF_OK != DGifGetLine(f, &data[j * width], width)) { printfef("(\"%s\"): %d: Failed to read line %d.", path, i, j); goto sgif_read_end; } break; case EXTENSION_RECORD_TYPE: { int code = 0; GifByteType *pbytes = NULL; if (GIF_OK != DGifGetExtension(f, &code, &pbytes) || !pbytes) { printfef("(\"%s\"): %d: Failed to read extension block.", path, i); break; } do { // Transparency if (0xf9 == code && (pbytes[1] & 1)) transp = pbytes[4]; } while (GIF_OK == DGifGetExtensionNext(f, &pbytes) && pbytes); } break; case TERMINATE_RECORD_TYPE: assert(0); break; } } if (unlikely(!data)) { printfef("(\"%s\"): No valid data found.", path); goto sgif_read_end; } } // Colormap translation int depth = 32; { ColorMapObject *cmap = f->Image.ColorMap; if (!cmap) cmap = f->SColorMap; if (unlikely(!cmap)) { printfef("(\"%s\"): No colormap found.", path); goto sgif_read_end; } tdata = allocchk(malloc(width * height * depth / 8)); { GifPixelType *pd = data; unsigned char *end = tdata + width * height * depth / 8; for (unsigned char *p = tdata; p < end; p += depth / 8, ++pd) { // When the alpha is 0, X seemingly wants all color channels // to be 0 as well. if (transp >= 0 && transp == *pd) { p[0] = p[1] = p[2] = 0; if (32 == depth) p[3] = 0; continue; } p[0] = cmap->Colors[*pd].Blue; p[1] = cmap->Colors[*pd].Green; p[2] = cmap->Colors[*pd].Red; p[3] = 0xff; } } } if (unlikely(!(pictw = simg_data_to_pictw(ps, width, height, depth, tdata, 0)))) { printfef("(\"%s\"): Failed to create Picture.", path); goto sgif_read_end; } sgif_read_end: if (data) free(data); if (likely(f)) DGifCloseFile(f); return pictw; }
pictw_t * spng_read(session_t *ps, const char *path) { assert(path); char sig[SPNG_SIGBYTES] = ""; pictw_t *pictw = NULL; png_structp png_ptr = NULL; png_infop info_ptr = NULL; FILE *fp = fopen(path, "rb"); bool need_premultiply = false; if (unlikely(!fp)) { printfef("(\"%s\"): Failed to open file.", path); goto spng_read_end; } if (unlikely(SPNG_SIGBYTES != fread(&sig, 1, SPNG_SIGBYTES, fp))) { printfef("(\"%s\"): Failed to read %d-byte signature.", path, SPNG_SIGBYTES); goto spng_read_end; } if (unlikely(png_sig_cmp((png_bytep) sig, 0, SPNG_SIGBYTES))) { printfef("(\"%s\"): PNG signature invalid.", path); goto spng_read_end; } png_ptr = allocchk(png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)); info_ptr = allocchk(png_create_info_struct(png_ptr)); if (setjmp(png_jmpbuf(png_ptr))) goto spng_read_end; png_init_io(png_ptr, fp); png_set_sig_bytes(png_ptr, SPNG_SIGBYTES); png_read_info(png_ptr, info_ptr); png_uint_32 width = 0, height = 0; // Set transformations int bit_depth = 0, color_type = 0; { int interlace_type = 0; png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL); // Scale or strip 16-bit colors if (bit_depth == 16) { printfdf("(\"%s\"): Scaling 16-bit colors.", path); #if PNG_LIBPNG_VER >= 10504 png_set_scale_16(png_ptr); #else png_set_strip_16(png_ptr); #endif bit_depth = 8; } /* if (bit_depth < 8) png_set_packing(png_ptr); */ // No idea why this is needed... png_set_bgr(png_ptr); // Convert palette to RGB if (color_type == PNG_COLOR_TYPE_PALETTE) { printfdf("(\"%s\"): Converting palette PNG to RGB.", path); png_set_palette_to_rgb(png_ptr); color_type = PNG_COLOR_TYPE_RGB; } if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { printfdf("(\"%s\"): Converting rDNS to full alpha.", path); png_set_tRNS_to_alpha(png_ptr); } if (color_type == PNG_COLOR_TYPE_GRAY || PNG_COLOR_TYPE_GRAY_ALPHA == color_type) { printfdf("(\"%s\"): Converting gray (+ alpha) PNG to RGB.", path); png_set_gray_to_rgb(png_ptr); if (PNG_COLOR_TYPE_GRAY == color_type) color_type = PNG_COLOR_TYPE_RGB; else color_type = PNG_COLOR_TYPE_RGB_ALPHA; } /* if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { printfdf("(\"%s\"): Converting 1/2/4 bit gray PNG to 8-bit.", path); #if PNG_LIBPNG_VER >= 10209 png_set_expand_gray_1_2_4_to_8(png_ptr); #else png_set_gray_1_2_4_to_8(png_ptr); #endif bit_depth = 8; } */ // Somehow XImage requires 24-bit visual to use 32 bits per pixel if (color_type == PNG_COLOR_TYPE_RGB) { printfdf("(\"%s\"): Appending filler alpha values.", path); png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); } // Premultiply alpha if (PNG_COLOR_TYPE_RGB_ALPHA == color_type) { #if PNG_LIBPNG_VER >= 10504 png_set_alpha_mode(png_ptr, PNG_ALPHA_STANDARD, 1.0); #else need_premultiply = true; #endif } /* int number_passes = 1; #ifdef PNG_READ_INTERLACING_SUPPORTED number_passes = png_set_interlace_handling(png_ptr); #endif */ if (PNG_INTERLACE_NONE != interlace_type) png_set_interlace_handling(png_ptr); } png_read_update_info(png_ptr, info_ptr); int depth = 0; png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL); switch (color_type) { case PNG_COLOR_TYPE_GRAY: depth = 1 * bit_depth; break; case PNG_COLOR_TYPE_RGB: depth = 3 * bit_depth; break; case PNG_COLOR_TYPE_RGB_ALPHA: depth = 4 * bit_depth; break; default: assert(0); break; } // Read data and fill to Picture { int rowbytes = png_get_rowbytes(png_ptr, info_ptr); png_bytep row_pointers[height]; memset(row_pointers, 0, sizeof(row_pointers)); row_pointers[0] = png_malloc(png_ptr, rowbytes * height); for (int row = 1; row < height; row++) row_pointers[row] = row_pointers[row - 1] + rowbytes; png_read_image(png_ptr, row_pointers); if (need_premultiply) for (int row = 0; row < height; row++) simg_data32_premultiply(row_pointers[row], width); if (unlikely(!(pictw = simg_data_to_pictw(ps, width, height, depth, row_pointers[0], rowbytes)))) { printfef("(\"%s\"): Failed to create Picture.", path); goto spng_read_end; } } spng_read_end: if (png_ptr) png_destroy_read_struct(&png_ptr, &info_ptr, NULL); if (fp) fclose(fp); return pictw; }
pictw_t * simg_postprocess(session_t *ps, pictw_t *src, enum pict_posp_mode mode, int twidth, int theight, enum align alg, enum align valg, const XRenderColor *pc) { static const XRenderColor XRC_TRANS = { .red = 0, .green = 0, .blue = 0, .alpha = 0 }; if (!pc) pc = &XRC_TRANS; const int depth = 32; pictw_t *dest = NULL; bool transformed = false; if (!src) { if (twidth && theight) { if (!(dest = create_pictw(ps, twidth, theight, depth))) printfef("(): Failed to create Picture."); else XRenderFillRectangle(ps->dpy, PictOpSrc, dest->pict, pc, 0, 0, twidth, theight); } goto simg_postprocess_end; } if (!(twidth || theight) || (twidth == src->width && theight == src->height)) return src; // Determine composite paramaters. We have to do this before create // Picture because the width/height may need to be calculated. int width = src->width, height = src->height; if (!twidth) twidth = (double) theight / height * width; else if (!theight) theight = (double) twidth / width * height; double ratio_x = 1.0, ratio_y = 1.0; switch (mode) { case PICTPOSP_ORIG: break; case PICTPOSP_TILE: break; case PICTPOSP_SCALE: case PICTPOSP_SCALEK: case PICTPOSP_SCALEE: case PICTPOSP_SCALEEK: { if (twidth) ratio_x = (double) twidth / width; if (theight) ratio_y = (double) theight / height; if (PICTPOSP_SCALEK == mode || PICTPOSP_SCALEEK == mode) ratio_x = ratio_y = MIN(ratio_x, ratio_y); if (PICTPOSP_SCALEE == mode || PICTPOSP_SCALEEK == mode) { ratio_x = MAX(1.0f, ratio_x); ratio_y = MAX(1.0f, ratio_y); } width *= ratio_x; height *= ratio_y; } break; default: assert(0); break; } if (!(dest = create_pictw(ps, twidth, theight, depth))) { printfef("(): Failed to create Picture."); goto simg_postprocess_end; } int x = 0, y = 0; int num_x = 1, num_y = 1; if (PICTPOSP_TILE == mode) { // num_x = ceil((float) twidth / width); // num_y = ceil((float) theight / height); num_x = twidth / width; num_y = theight / height; } switch (alg) { case ALIGN_LEFT: break; case ALIGN_RIGHT: x = twidth - width * num_x; break; case ALIGN_MID: x = (twidth - width * num_x) / 2; break; }; switch (valg) { case ALIGN_LEFT: break; case ALIGN_RIGHT: y = theight - height * num_y; break; case ALIGN_MID: y = (theight - height * num_y) / 2; break; }; x = MAX(x, 0); y = MAX(y, 0); int x2 = MIN(x + width * num_x, twidth), y2 = MIN(y + height * num_y, theight); /* if (pc->alpha) */ { if (src->depth >= 32) XRenderFillRectangle(ps->dpy, PictOpSrc, dest->pict, pc, 0, 0, twidth, theight); else { XRenderFillRectangle(ps->dpy, PictOpSrc, dest->pict, pc, 0, 0, twidth, y); XRenderFillRectangle(ps->dpy, PictOpSrc, dest->pict, pc, 0, y2, twidth, theight - y2); XRenderFillRectangle(ps->dpy, PictOpSrc, dest->pict, pc, 0, y, x, y2 - y); XRenderFillRectangle(ps->dpy, PictOpSrc, dest->pict, pc, x2, y, twidth - x2, y2 - y); } } if (src->pict) { if (1.0 != ratio_x || 1.0 != ratio_y) { XTransform transform = { .matrix = { { XDoubleToFixed(1.0 / ratio_x), XDoubleToFixed(0.0), XDoubleToFixed(0.0) }, { XDoubleToFixed(0.0), XDoubleToFixed(1.0 / ratio_y), XDoubleToFixed(0.0) }, { XDoubleToFixed(0.0), XDoubleToFixed(0.0), XDoubleToFixed(1.0) }, } }; transformed = true; XRenderSetPictureTransform(ps->dpy, src->pict, &transform); }