Skip to content

Instantly share code, notes, and snippets.

@richardgv
Last active December 18, 2015 00:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save richardgv/5696281 to your computer and use it in GitHub Desktop.
Save richardgv/5696281 to your computer and use it in GitHub Desktop.
richardgv/skippy-xd #10: Possible fix & other WIP things
diff --git a/Makefile b/Makefile
index 369ea19..52b72f8 100644
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,7 @@ BINDIR ?= ${PREFIX}/bin
CC ?= gcc
+SRCS_RAW = skippy wm dlist mainwin clientwin layout focus config tooltip img
PACKAGES = x11 xft xrender xcomposite xdamage xfixes
# === Options ===
@@ -11,6 +12,24 @@ ifeq "${CFG_NO_XINERAMA}" ""
PACKAGES += xext xinerama
endif
+ifeq "${CFG_NO_PNG}" ""
+ CPPFLAGS += -DCFG_LIBPNG
+ SRCS_RAW += img-png
+ PACKAGES += libpng zlib
+endif
+
+ifeq "${CFG_NO_JPEG}" ""
+ CPPFLAGS += -DCFG_JPEG
+ SRCS_RAW += img-jpeg
+ LIBS += -ljpeg
+endif
+
+ifeq "${CFG_NO_GIF}" ""
+ CPPFLAGS += -DCFG_GIFLIB
+ SRCS_RAW += img-gif
+ LIBS += -lgif
+endif
+
ifeq "$(CFG_DEV)" ""
CFLAGS ?= -DNDEBUG -O2 -D_FORTIFY_SOURCE=2
else
@@ -24,7 +43,7 @@ endif
CFLAGS += -std=c99 -Wall
LDFLAGS ?= -Wl,-O1 -Wl,--as-needed
INCS = $(shell pkg-config --cflags $(PACKAGES))
-LIBS = -lm $(shell pkg-config --libs $(PACKAGES))
+LIBS += -lm $(shell pkg-config --libs $(PACKAGES))
# === Version string ===
SKIPPYXD_VERSION ?= git-$(shell git describe --always --dirty)-$(shell git log -1 --date=short --pretty=format:%cd)
@@ -33,7 +52,6 @@ CPPFLAGS += -DSKIPPYXD_VERSION="\"${SKIPPYXD_VERSION}\""
# === Recipes ===
EXESUFFIX =
BINS = skippy-xd${EXESUFFIX}
-SRCS_RAW = skippy wm dlist mainwin clientwin layout focus config tooltip
SRCS = $(foreach name,$(SRCS_RAW),src/$(name).c)
HDRS = $(foreach name,$(SRCS_RAW),src/$(name).h)
OBJS = $(foreach name,$(SRCS_RAW),$(name).o)
diff --git a/skippy-xd.rc-default b/skippy-xd.rc-default
index 3836848..665a386 100644
--- a/skippy-xd.rc-default
+++ b/skippy-xd.rc-default
@@ -38,6 +38,8 @@ lazyTrans = false
pipePath = /tmp/skippy-xd-fifo
movePointerOnStart = true
useNameWindowPixmap = false
+includeFrame = true
+backgroundImg =
[xinerama]
showAll = false
diff --git a/src/clientwin.c b/src/clientwin.c
index 82a6920..e510a6a 100644
--- a/src/clientwin.c
+++ b/src/clientwin.c
@@ -27,51 +27,50 @@ static int
clientwin_action(ClientWin *cw, enum cliop action);
int
-clientwin_cmp_func(dlist *l, void *data)
-{
- return ((ClientWin*)l->data)->client.window == (Window)data;
+clientwin_cmp_func(dlist *l, void *data) {
+ ClientWin *cw = (ClientWin *) l->data;
+ return cw->src.window == (Window) data
+ || cw->wid_client == (Window) data;
}
int
clientwin_validate_func(dlist *l, void *data) {
- ClientWin *cw = (ClientWin *)l->data;
+ ClientWin *cw = l->data;
MainWin *mw = cw->mainwin;
CARD32 desktop = (*(CARD32*)data),
- w_desktop = wm_get_window_desktop(mw->ps->dpy, cw->client.window);
+ w_desktop = wm_get_window_desktop(mw->ps, cw->wid_client);
#ifdef CFG_XINERAMA
- if(mw->xin_active && ! INTERSECTS(cw->client.x, cw->client.y, cw->client.width, cw->client.height,
+ if(mw->xin_active && ! INTERSECTS(cw->src.x, cw->src.y, cw->src.width, cw->src.height,
mw->xin_active->x_org, mw->xin_active->y_org,
mw->xin_active->width, mw->xin_active->height))
return 0;
#endif
return (w_desktop == (CARD32)-1 || desktop == w_desktop) &&
- wm_validate_window(mw->ps->dpy, cw->client.window);
+ wm_validate_window(mw->ps, cw->wid_client);
}
int
clientwin_check_group_leader_func(dlist *l, void *data)
{
ClientWin *cw = (ClientWin *)l->data;
- return wm_get_group_leader(cw->mainwin->ps->dpy, cw->client.window) == *((Window*)data);
+ return wm_get_group_leader(cw->mainwin->ps->dpy, cw->wid_client) == *((Window*)data);
}
int
clientwin_sort_func(dlist* a, dlist* b, void* data)
{
- unsigned int pa = ((ClientWin*)a->data)->client.x * ((ClientWin*)a->data)->client.y,
- pb = ((ClientWin*)b->data)->client.x * ((ClientWin*)b->data)->client.y;
+ unsigned int pa = ((ClientWin *) a->data)->src.x * ((ClientWin *) a->data)->src.y,
+ pb = ((ClientWin *) b->data)->src.x * ((ClientWin *) b->data)->src.y;
return (pa < pb) ? -1 : (pa == pb) ? 0 : 1;
}
ClientWin *
clientwin_create(MainWin *mw, Window client) {
session_t *ps = mw->ps;
- ClientWin *cw = (ClientWin *)malloc(sizeof(ClientWin));
- if (!cw)
- return NULL;
+ ClientWin *cw = allocchk(malloc(sizeof(ClientWin)));
{
static const ClientWin CLIENTWT_DEF = CLIENTWT_INIT;
memcpy(cw, &CLIENTWT_DEF, sizeof(ClientWin));
@@ -80,14 +79,11 @@ clientwin_create(MainWin *mw, Window client) {
XWindowAttributes attr;
cw->mainwin = mw;
- cw->pixmap = None;
- cw->focused = 0;
- cw->origin = cw->destination = None;
- cw->damage = None;
- cw->damaged = false;
- /* cw->repair = None; */
-
- cw->client.window = client;
+ cw->wid_client = client;
+ if (ps->o.includeFrame)
+ cw->src.window = wm_find_frame(ps, client);
+ if (!cw->src.window)
+ cw->src.window = client;
cw->mini.format = mw->format;
{
XSetWindowAttributes sattr = {
@@ -107,35 +103,44 @@ clientwin_create(MainWin *mw, Window client) {
if (!cw->mini.window)
goto clientwin_create_err;
- wm_wid_set_info(cw->mainwin->ps, cw->mini.window, "mini window", None);
+ {
+ static const char *PREFIX = "mini window of ";
+ const int len = strlen(PREFIX) + 20;
+ char *str = allocchk(malloc(len));
+ snprintf(str, len, "%s%#010lx", PREFIX, cw->src.window);
+ wm_wid_set_info(cw->mainwin->ps, cw->mini.window, str, None);
+ free(str);
+ }
+
// Listen to events on the window. We don't want to miss any changes so
// this is to be done as early as possible
- XSelectInput(cw->mainwin->ps->dpy, cw->client.window, SubstructureNotifyMask | StructureNotifyMask);
+ XSelectInput(cw->mainwin->ps->dpy, cw->src.window, SubstructureNotifyMask | StructureNotifyMask);
XGetWindowAttributes(ps->dpy, client, &attr);
- cw->client.format = XRenderFindVisualFormat(ps->dpy, attr.visual);
+ if (IsViewable != attr.map_state)
+ goto clientwin_create_err;
+ clientwin_update(cw);
// Get window pixmap
- // Seemingly we could only redirect IsViewable windows
- if (ps->o.useNameWindowPixmap && IsViewable == attr.map_state) {
- XCompositeRedirectWindow(ps->dpy, cw->client.window, CompositeRedirectAutomatic);
+ if (ps->o.useNameWindowPixmap) {
+ XCompositeRedirectWindow(ps->dpy, cw->src.window, CompositeRedirectAutomatic);
cw->redirected = true;
- cw->cpixmap = XCompositeNameWindowPixmap(ps->dpy, cw->client.window);
+ cw->cpixmap = XCompositeNameWindowPixmap(ps->dpy, cw->src.window);
}
// Create window picture
{
Drawable draw = cw->cpixmap;
- if (!draw) draw = cw->client.window;
+ if (!draw) draw = cw->src.window;
XRenderPictureAttributes pa = { .subwindow_mode = IncludeInferiors };
- cw->origin = XRenderCreatePicture (cw->mainwin->ps->dpy,
- draw, cw->client.format, CPSubwindowMode, &pa);
+ cw->origin = XRenderCreatePicture(cw->mainwin->ps->dpy,
+ draw, cw->src.format, CPSubwindowMode, &pa);
}
if (!cw->origin)
goto clientwin_create_err;
-
+
XRenderSetPictureFilter(cw->mainwin->ps->dpy, cw->origin, FilterBest, 0, 0);
-
-
+
+
return cw;
clientwin_create_err:
@@ -146,22 +151,18 @@ clientwin_create_err:
}
void
-clientwin_update(ClientWin *cw)
-{
+clientwin_update(ClientWin *cw) {
Window tmpwin;
XWindowAttributes wattr;
-
- XGetWindowAttributes(cw->mainwin->ps->dpy, cw->client.window, &wattr);
-
- cw->client.format = XRenderFindVisualFormat(cw->mainwin->ps->dpy, wattr.visual);
- XTranslateCoordinates(cw->mainwin->ps->dpy, cw->client.window, wattr.root,
- -wattr.border_width,
- -wattr.border_width,
- &cw->client.x, &cw->client.y, &tmpwin);
-
- cw->client.width = wattr.width;
- cw->client.height = wattr.height;
-
+
+ XGetWindowAttributes(cw->mainwin->ps->dpy, cw->src.window, &wattr);
+ XTranslateCoordinates(cw->mainwin->ps->dpy, cw->src.window, wattr.root,
+ -wattr.border_width, -wattr.border_width,
+ &cw->src.x, &cw->src.y, &tmpwin);
+ cw->src.width = wattr.width;
+ cw->src.height = wattr.height;
+ cw->src.format = XRenderFindVisualFormat(cw->mainwin->ps->dpy, wattr.visual);
+
cw->mini.x = cw->mini.y = 0;
cw->mini.width = cw->mini.height = 1;
}
@@ -176,15 +177,15 @@ clientwin_destroy(ClientWin *cw, bool destroyed) {
free_pixmap(ps, &cw->pixmap);
free_pixmap(ps, &cw->cpixmap);
- if (cw->client.window && !destroyed) {
+ if (cw->src.window && !destroyed) {
free_damage(ps, &cw->damage);
// Stop listening to events, this should be safe because we don't
// monitor window re-map anyway
- XSelectInput(ps->dpy, cw->client.window, 0);
+ XSelectInput(ps->dpy, cw->src.window, 0);
if (cw->redirected)
- XCompositeUnredirectWindow(ps->dpy, cw->client.window, CompositeRedirectAutomatic);
+ XCompositeUnredirectWindow(ps->dpy, cw->src.window, CompositeRedirectAutomatic);
}
if (cw->mini.window) {
@@ -230,8 +231,8 @@ clientwin_render(ClientWin *cw)
{
XRectangle rect;
rect.x = rect.y = 0;
- rect.width = cw->client.width;
- rect.height = cw->client.height;
+ rect.width = cw->src.width;
+ rect.height = cw->src.height;
clientwin_repaint(cw, &rect);
}
@@ -277,8 +278,8 @@ clientwin_move(ClientWin *cw, float f, int x, int y)
cw->mini.x += cw->mainwin->x;
cw->mini.y += cw->mainwin->y;
}
- cw->mini.width = MAX(1, (int)cw->client.width * f);
- cw->mini.height = MAX(1, (int)cw->client.height * f);
+ cw->mini.width = MAX(1, cw->src.width * f);
+ cw->mini.height = MAX(1, cw->src.height * f);
XMoveResizeWindow(cw->mainwin->ps->dpy, cw->mini.window, cw->mini.x - border, cw->mini.y - border, cw->mini.width, cw->mini.height);
if(cw->pixmap)
@@ -298,7 +299,7 @@ clientwin_map(ClientWin *cw) {
session_t *ps = cw->mainwin->ps;
free_damage(ps, &cw->damage);
- cw->damage = XDamageCreate(ps->dpy, cw->client.window, XDamageReportDeltaRectangles);
+ cw->damage = XDamageCreate(ps->dpy, cw->src.window, XDamageReportDeltaRectangles);
XRenderSetPictureTransform(ps->dpy, cw->origin, &cw->mainwin->transform);
clientwin_render(cw);
@@ -335,16 +336,16 @@ clientwin_unmap(ClientWin *cw)
}
static void
-childwin_focus(ClientWin *cw)
-{
- XWarpPointer(cw->mainwin->ps->dpy, None, cw->client.window, 0, 0, 0, 0, cw->client.width / 2, cw->client.height / 2);
- XRaiseWindow(cw->mainwin->ps->dpy, cw->client.window);
- XSetInputFocus(cw->mainwin->ps->dpy, cw->client.window, RevertToParent, CurrentTime);
+childwin_focus(ClientWin *cw) {
+ XWarpPointer(cw->mainwin->ps->dpy, None, cw->wid_client, 0, 0, 0, 0, cw->src.width / 2, cw->src.height / 2);
+ XRaiseWindow(cw->mainwin->ps->dpy, cw->wid_client);
+ XSetInputFocus(cw->mainwin->ps->dpy, cw->wid_client, RevertToParent, CurrentTime);
}
int
clientwin_handle(ClientWin *cw, XEvent *ev) {
- session_t *ps = cw->mainwin->ps;
+ MainWin *mw = cw->mainwin;
+ session_t *ps = mw->ps;
if (ev->type == ButtonRelease) {
const unsigned button = ev->xbutton.button;
@@ -402,10 +403,10 @@ clientwin_handle(ClientWin *cw, XEvent *ev) {
clientwin_render(cw);
XFlush(ps->dpy);
} else if(ev->type == EnterNotify) {
- if(cw->mainwin->tooltip)
- {
+ 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->client.window, &win_title_len);
+ 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,
@@ -414,6 +415,7 @@ clientwin_handle(ClientWin *cw, XEvent *ev) {
}
}
} else if(ev->type == LeaveNotify) {
+ // XSetInputFocus(ps->dpy, mw->window, RevertToParent, CurrentTime);
if(cw->mainwin->tooltip)
tooltip_unmap(cw->mainwin->tooltip);
}
@@ -423,7 +425,7 @@ clientwin_handle(ClientWin *cw, XEvent *ev) {
static int
clientwin_action(ClientWin *cw, enum cliop action) {
session_t * const ps = cw->mainwin->ps;
- const Window wid = cw->client.window;
+ const Window wid = cw->wid_client;
switch (action) {
case CLIENTOP_NO:
@@ -444,7 +446,7 @@ clientwin_action(ClientWin *cw, enum cliop action) {
wm_close_window_ewmh(ps, wid);
break;
case CLIENTOP_DESTROY:
- XDestroyWindow(cw->mainwin->ps->dpy, cw->client.window);
+ XDestroyWindow(cw->mainwin->ps->dpy, wid);
break;
}
diff --git a/src/clientwin.h b/src/clientwin.h
index f8d87d6..fd6375d 100644
--- a/src/clientwin.h
+++ b/src/clientwin.h
@@ -23,7 +23,7 @@
typedef struct {
Window window;
int x, y;
- unsigned int width, height;
+ int width, height;
XRenderPictFormat *format;
} SkippyWindow;
@@ -32,8 +32,9 @@ typedef struct {
struct _MainWin;
typedef struct {
struct _MainWin *mainwin;
-
- SkippyWindow client;
+
+ Window wid_client;
+ SkippyWindow src;
bool redirected;
Pixmap cpixmap;
SkippyWindow mini;
@@ -53,7 +54,7 @@ typedef struct {
} ClientWin;
#define CLIENTWT_INIT { \
- .client = SKIPPYWINT_INIT, \
+ .src = SKIPPYWINT_INIT, \
.mini = SKIPPYWINT_INIT, \
.mainwin = NULL \
}
diff --git a/src/img-gif.c b/src/img-gif.c
new file mode 100644
index 0000000..4c350bb
--- /dev/null
+++ b/src/img-gif.c
@@ -0,0 +1,129 @@
+#include "skippy.h"
+#include <gif_lib.h>
+
+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;
+ GifFileType *f = DGifOpenFileName(path);
+ if (unlikely(!f)) {
+ printfef("(\"%s\"): Failed to open file.", path);
+ 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 = (transp >= 0 ? 32: 24);
+ {
+ 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].Red;
+ p[1] = cmap->Colors[*pd].Green;
+ p[2] = cmap->Colors[*pd].Blue;
+ 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;
+}
diff --git a/src/img-gif.h b/src/img-gif.h
new file mode 100644
index 0000000..72128d3
--- /dev/null
+++ b/src/img-gif.h
@@ -0,0 +1,4 @@
+#include "skippy.h"
+
+pictw_t *
+sgif_read(session_t *ps, const char *path);
diff --git a/src/img-jpeg.c b/src/img-jpeg.c
new file mode 100644
index 0000000..90f6f23
--- /dev/null
+++ b/src/img-jpeg.c
@@ -0,0 +1,83 @@
+#include "skippy.h"
+#include <jpeglib.h>
+
+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;
+ if (unlikely(!(pictw = simg_data_to_pictw(ps, width, height, depth,
+ data, 0)))) {
+ 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;
+}
diff --git a/src/img-jpeg.h b/src/img-jpeg.h
new file mode 100644
index 0000000..4986da2
--- /dev/null
+++ b/src/img-jpeg.h
@@ -0,0 +1,4 @@
+#include "skippy.h"
+
+pictw_t *
+sjpg_read(session_t *ps, const char *path);
diff --git a/src/img-png.c b/src/img-png.c
new file mode 100644
index 0000000..3173aa3
--- /dev/null
+++ b/src/img-png.c
@@ -0,0 +1,174 @@
+#include "skippy.h"
+#include <png.h>
+#include <zlib.h>
+#define SPNG_SIGBYTES 8
+
+// <libpng-1.0.6 compatibility
+#ifndef png_jmpbuf
+# define png_jmpbuf(png_ptr) ((png_ptr)->png_jmpbuf)
+#endif
+
+void
+spng_about(FILE *os) {
+ fprintf(os, "PNG support: Yes\n"
+ " Compiled with libpng %s, using %s.\n"
+ " Compiled with zlib %s, using %s.\n",
+ PNG_LIBPNG_VER_STRING, png_libpng_ver,
+ ZLIB_VERSION, zlib_version);
+}
+
+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;
+}
diff --git a/src/img-png.h b/src/img-png.h
new file mode 100644
index 0000000..dee883b
--- /dev/null
+++ b/src/img-png.h
@@ -0,0 +1,7 @@
+#include "skippy.h"
+
+void
+spng_about(FILE *os);
+
+pictw_t *
+spng_read(session_t *ps, const char *path);
diff --git a/src/img-xlib.c b/src/img-xlib.c
new file mode 100644
index 0000000..ec34b1a
--- /dev/null
+++ b/src/img-xlib.c
@@ -0,0 +1,6 @@
+#include "skippy.h"
+
+unsigned char *
+simg_argb_to_argb32(unsigned char *data, int width, int height) {
+ return data;
+}
diff --git a/src/img.c b/src/img.c
new file mode 100644
index 0000000..ebeebb7
--- /dev/null
+++ b/src/img.c
@@ -0,0 +1,157 @@
+#include "skippy.h"
+#include <float.h>
+
+pictw_t *
+simg_load(session_t *ps, const char *path, enum pict_posp_mode mode,
+ int twidth, int theight, enum align alg, enum align valg,
+ const XRenderColor *pc) {
+ pictw_t *result = NULL;
+ bool processed = false;
+
+ if (0) { }
+#ifdef CFG_LIBPNG
+ else if (str_endwith(path, ".png"))
+ result = spng_read(ps, path);
+#endif
+#ifdef CFG_JPEG
+ else if (str_endwith(path, ".jpg") || str_endwith(path, ".jpeg")
+ || str_endwith(path, ".jpe"))
+ result = sjpg_read(ps, path);
+#endif
+#ifdef CFG_JPEG
+ else if (str_endwith(path, ".gif"))
+ result = sgif_read(ps, path);
+#endif
+ if (!processed && result)
+ result = simg_postprocess(ps, result, mode, twidth, theight,
+ alg, valg, pc);
+
+ return result;
+}
+
+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) {
+ 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 = width;
+ if (!theight) theight = height;
+ double ratio_x = 1.0, ratio_y = 1.0;
+ switch (mode) {
+ case PICTPOSP_ORIG: break;
+ case PICTPOSP_TILE:
+ if (twidth) width = twidth;
+ if (theight) height = theight;
+ break;
+ case PICTPOSP_SCALE:
+ case PICTPOSP_SCALEK:
+ {
+ if (twidth) ratio_x = (double) twidth / width;
+ if (theight) ratio_y = (double) theight / height;
+ if (PICTPOSP_SCALEK == mode)
+ ratio_x = ratio_y = MIN(ratio_x, 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 = 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;
+ };
+ int x2 = x + width * num_x, y2 = y + height + num_y;
+ /* 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(ratio_x), XDoubleToFixed(0.0), XDoubleToFixed(0.0) },
+ { XDoubleToFixed(0.0), XDoubleToFixed(ratio_y), XDoubleToFixed(0.0) },
+ { XDoubleToFixed(0.0), XDoubleToFixed(0.0), XDoubleToFixed(1.0) },
+ } };
+ transformed = true;
+ XRenderSetPictureTransform(ps->dpy, src->pict, &transform);
+ }
+
+ XRectangle bound = { .x = x, .y = y, .width = x2, .height = y2 };
+ for (int i = 0; i < num_x; ++i)
+ for (int j = 0; j < num_y; ++j) {
+ XRectangle reg = { .x = x + i * width, .y = y + j * height,
+ .width = width, .height = height };
+ XRectangle reg2;
+ rect_crop(&reg2, &reg, &bound);
+ if (reg2.width && reg2.height) {
+ int op = (src->depth >= 32 && pc->alpha ? PictOpOver: PictOpSrc);
+ XRenderComposite(ps->dpy, op, src->pict, None,
+ dest->pict, 0, 0, 0, 0,
+ reg2.x, reg2.y, reg2.width, reg2.height);
+ }
+ }
+ }
+
+ if (transformed) {
+ static XTransform transform = { .matrix = {
+ { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 },
+ } };
+ XRenderSetPictureTransform(ps->dpy, src->pict, &transform);
+ }
+
+simg_postprocess_end:
+ free_pictw(ps, &src);
+ return dest;
+}
diff --git a/src/img.h b/src/img.h
new file mode 100644
index 0000000..a6d1bc0
--- /dev/null
+++ b/src/img.h
@@ -0,0 +1,170 @@
+#include "skippy.h"
+
+/**
+ * @brief Crop a rectangle by another rectangle.
+ *
+ * psrc and pdst cannot be the same.
+ */
+static inline void
+rect_crop(XRectangle *pdst, const XRectangle *psrc, const XRectangle *pbound) {
+ assert(psrc != pdst);
+ pdst->x = MAX(psrc->x, pbound->x);
+ pdst->y = MAX(psrc->y, pbound->y);
+ pdst->width = MAX(0,
+ MIN(psrc->x + psrc->width, pbound->x + pbound->width) - pdst->x);
+ pdst->height = MAX(0,
+ MIN(psrc->y + psrc->height, pbound->y + pbound->height) - pdst->y);
+}
+
+/**
+ * @brief Get X Render format for a specified depth.
+ */
+static inline const XRenderPictFormat *
+depth_to_rfmt(session_t *ps, int depth) {
+ switch (depth) {
+ case 32: return XRenderFindStandardFormat(ps->dpy, PictStandardARGB32);
+ case 24: return XRenderFindStandardFormat(ps->dpy, PictStandardRGB24);
+ case 8: return XRenderFindStandardFormat(ps->dpy, PictStandardA8);
+ case 4: return XRenderFindStandardFormat(ps->dpy, PictStandardA4);
+ case 1: return XRenderFindStandardFormat(ps->dpy, PictStandardA1);
+ };
+ assert(0);
+ return NULL;
+}
+
+static inline void
+free_pictw(session_t *ps, pictw_t **ppictw) {
+ free_pixmap(ps, &(*ppictw)->pxmap);
+ free_picture(ps, &(*ppictw)->pict);
+ free(*ppictw);
+ *ppictw = NULL;
+}
+
+/**
+ * @brief Build a pictw_t of specified size and depth.
+ */
+static inline pictw_t *
+create_pictw(session_t *ps, int width, int height, int depth) {
+ pictw_t *pictw = allocchk(calloc(1, sizeof(pictw_t)));
+
+ if (!(pictw->pxmap =
+ XCreatePixmap(ps->dpy, ps->root, width, height, depth))) {
+ printfef("(%d, %d, %d): Failed to create Pixmap.",
+ width, height, depth);
+ goto create_pictw_err;
+ }
+ if (!(pictw->pict = XRenderCreatePicture(ps->dpy, pictw->pxmap,
+ depth_to_rfmt(ps, depth), 0, NULL))) {
+ printfef("(%d, %d, %d): Failed to create Picture.",
+ width, height, depth);
+ goto create_pictw_err;
+ }
+ pictw->width = width;
+ pictw->height = height;
+ pictw->depth = depth;
+
+ return pictw;
+
+create_pictw_err:
+ free_pictw(ps, &pictw);
+
+ return NULL;
+}
+
+pictw_t *
+simg_load(session_t *ps, const char *path, enum pict_posp_mode mode,
+ int twidth, int theight, enum align alg, enum align valg,
+ const XRenderColor *pc);
+
+static inline pictw_t *
+simg_load_s(session_t *ps, const pictspec_t *spec) {
+ return simg_load(ps, spec->path, spec->mode, spec->twidth, spec->theight,
+ spec->alg, spec->valg, &spec->c);
+}
+
+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 inline pictw_t *
+simg_data_to_pictw(session_t *ps, int width, int height, int depth,
+ const unsigned char *data, int bytes_per_line) {
+ assert(data);
+ pictw_t *pictw = NULL;
+ GC gc = None;
+ XImage *img = XCreateImage(ps->dpy, DefaultVisual(ps->dpy, ps->screen),
+ depth, ZPixmap, 0, (char *) data, width, height,
+ 8, bytes_per_line);
+ if (!img) {
+ printfef("(%d, %d, %d): Failed to create XImage.",
+ width, height, depth);
+ goto simg_data_to_pict_end;
+ }
+ if (!(pictw = create_pictw(ps, width, height, depth))) {
+ printfef("(%d, %d, %d): Failed to create Picture.",
+ width, height, depth);
+ goto simg_data_to_pict_end;
+ }
+ gc = XCreateGC(ps->dpy, pictw->pxmap, 0, 0);
+ if (!gc) {
+ printfef("(%d, %d, %d): Failed to create GC.",
+ width, height, depth);
+ free_pictw(ps, &pictw);
+ goto simg_data_to_pict_end;
+ }
+ XPutImage(ps->dpy, pictw->pxmap, gc, img, 0, 0, 0, 0, width, height);
+
+simg_data_to_pict_end:
+ if (img)
+ XDestroyImage(img);
+ if (gc)
+ XFreeGC(ps->dpy, gc);
+
+ return pictw;
+}
+
+static inline void
+simg_data24_premultiply(unsigned char *data, int len) {
+ for (; len > 0; len--, data += 4) {
+ const unsigned char alpha = data[3];
+ if (0xff == alpha || !alpha) continue;
+ // This is slightly inaccurate because 1.0 == 255 instead of 256 (2 ** 8).
+ // Of course there are "smarter" ways to perform 3 multiplications all at
+ // once, but I don't feel like writing endian-specific code.
+ data[0] = ((uint_fast16_t) data[0] * alpha) >> 8;
+ data[1] = ((uint_fast16_t) data[1] * alpha) >> 8;
+ data[2] = ((uint_fast16_t) data[2] * alpha) >> 8;
+ }
+}
+
+static inline void
+simg_data24_fillalpha(unsigned char *data, int len) {
+ for (; len >= 0; --len) {
+ if (len) {
+ data[len * 4 + 0] = data[len * 3 + 0];
+ data[len * 4 + 1] = data[len * 3 + 1];
+ data[len * 4 + 2] = data[len * 3 + 2];
+ }
+ data[len * 4 + 3] = 0xff;
+ }
+}
+
+static inline void
+simg_data24_tobgr(unsigned char *data, int len) {
+ for (; len >= 0; --len) {
+ unsigned char r = data[len * 4];
+ data[len * 4 + 0] = data[len * 4 + 2];
+ data[len * 4 + 2] = r;
+ }
+}
+
+static inline void
+simg_data32_premultiply(unsigned char *data, int len) {
+ for (; len > 0; --len) {
+ float a = (float) data[len * 4 + 3] / 0xff;
+ data[len * 4 + 0] *= a;
+ data[len * 4 + 1] *= a;
+ data[len * 4 + 2] *= a;
+ }
+}
diff --git a/src/layout.c b/src/layout.c
index 78fb3be..736c62a 100644
--- a/src/layout.c
+++ b/src/layout.c
@@ -35,9 +35,9 @@ layout_run(MainWin *mw, dlist *windows, unsigned int *total_width, unsigned int
for(iter = windows; iter; iter = iter->next)
{
ClientWin *cw = (ClientWin *)iter->data;
- sum_w += cw->client.width;
- max_w = MAX(max_w, cw->client.width);
- max_h = MAX(max_h, cw->client.height);
+ sum_w += cw->src.width;
+ max_w = MAX(max_w, cw->src.width);
+ max_h = MAX(max_h, cw->src.height);
}
for(iter = windows; iter; iter = iter->next)
@@ -48,8 +48,8 @@ layout_run(MainWin *mw, dlist *windows, unsigned int *total_width, unsigned int
{
dlist *slot = (dlist *)slot_iter->data;
int slot_h = -mw->distance;
- REDUCE(slot_h = slot_h + ((ClientWin*)iter->data)->client.height + mw->distance, slot);
- if(slot_h + mw->distance + cw->client.height < max_h)
+ REDUCE(slot_h = slot_h + ((ClientWin*)iter->data)->src.height + mw->distance, slot);
+ if(slot_h + mw->distance + cw->src.height < max_h)
{
slot_iter->data = dlist_add(slot, cw);
break;
@@ -64,14 +64,14 @@ layout_run(MainWin *mw, dlist *windows, unsigned int *total_width, unsigned int
{
dlist *slot = (dlist *)slot_iter->data;
int slot_w = 0;
- REDUCE(slot_w = MAX(slot_w, ((ClientWin*)iter->data)->client.width), slot);
+ REDUCE(slot_w = MAX(slot_w, ((ClientWin*)iter->data)->src.width), slot);
y = row_y;
for(iter = dlist_first(slot); iter; iter = iter->next)
{
ClientWin *cw = (ClientWin *)iter->data;
- cw->x = x + (slot_w - cw->client.width) / 2;
+ cw->x = x + (slot_w - cw->src.width) / 2;
cw->y = y;
- y += cw->client.height + mw->distance;
+ y += cw->src.height + mw->distance;
rows->data = dlist_add((dlist *)rows->data, cw);
}
row_h = MAX(row_h, y - row_y);
@@ -92,15 +92,20 @@ layout_run(MainWin *mw, dlist *windows, unsigned int *total_width, unsigned int
*total_width -= mw->distance;
*total_height -= mw->distance;
- for(iter = dlist_first(rows); iter; iter = iter->next)
- {
- dlist *row = (dlist *)iter->data;
+ foreach_dlist (rows) {
+ dlist *row = (dlist *) iter->data;
int row_w = 0, xoff;
- REDUCE(row_w = MAX(row_w, ((ClientWin*)iter->data)->x + ((ClientWin*)iter->data)->client.width), row);
+ foreach_dlist (row) {
+ ClientWin *cw = (ClientWin *) iter->data;
+ row_w = MAX(row_w, cw->x + cw->src.width);
+ }
xoff = (*total_width - row_w) / 2;
- REDUCE(((ClientWin*)iter->data)->x += xoff, row);
+ foreach_dlist (row) {
+ ClientWin *cw = (ClientWin *) iter->data;
+ cw->x += xoff;
+ }
dlist_free(row);
}
-
+
dlist_free(rows);
}
diff --git a/src/mainwin.c b/src/mainwin.c
index f637430..2bd0cb6 100644
--- a/src/mainwin.c
+++ b/src/mainwin.c
@@ -194,7 +194,6 @@ mainwin_update_background(MainWin *mw) {
session_t *ps = mw->ps;
Pixmap root = wm_get_root_pmap(ps->dpy);
- XRenderColor black = { 0, 0, 0, 65535};
XRenderPictureAttributes pa;
if(mw->bg_pixmap)
@@ -206,10 +205,14 @@ mainwin_update_background(MainWin *mw) {
pa.repeat = True;
mw->background = XRenderCreatePicture(ps->dpy, mw->bg_pixmap, mw->format, CPRepeat, &pa);
- if(root == None)
+ if (ps->o.backgroundImg) {
+ XRenderComposite(ps->dpy, PictOpSrc, ps->o.backgroundImg->pict, None, mw->background, mw->x, mw->y, 0, 0, 0, 0, mw->width, mw->height);
+ }
+ else if (!root) {
+ static const XRenderColor black = { 0, 0, 0, 0xffff};
XRenderFillRectangle(ps->dpy, PictOpSrc, mw->background, &black, 0, 0, mw->width, mw->height);
- else
- {
+ }
+ else {
Picture from = XRenderCreatePicture(ps->dpy, root, XRenderFindVisualFormat(ps->dpy, DefaultVisual(ps->dpy, ps->screen)), 0, 0);
XRenderComposite(ps->dpy, PictOpSrc, from, None, mw->background, mw->x, mw->y, 0, 0, 0, 0, mw->width, mw->height);
XRenderFreePicture(ps->dpy, from);
@@ -280,15 +283,19 @@ void
mainwin_map(MainWin *mw) {
session_t *ps = mw->ps;
- wm_set_fullscreen(ps->dpy, mw->window, mw->x, mw->y, mw->width, mw->height);
+ wm_set_fullscreen(ps, mw->window, mw->x, mw->y, mw->width, mw->height);
mw->pressed = 0;
XMapWindow(ps->dpy, mw->window);
XRaiseWindow(ps->dpy, mw->window);
- // Might because of WM reparent, XSetInput() doesn't work here
+ // Might because of WM reparent, XSetInputFocus() doesn't work here
XSetInputFocus(ps->dpy, mw->window, RevertToParent, CurrentTime);
- XGrabKeyboard(ps->dpy, mw->window, True, GrabModeAsync, GrabModeAsync,
- CurrentTime);
+ {
+ int ret = XGrabKeyboard(ps->dpy, mw->window, True, GrabModeAsync,
+ GrabModeAsync, CurrentTime);
+ if (Success != ret)
+ printfef("(): Failed to grab keyboard (%d), troubles ahead.", ret);
+ }
}
void
diff --git a/src/skippy.c b/src/skippy.c
index c11c66f..123a591 100644
--- a/src/skippy.c
+++ b/src/skippy.c
@@ -55,21 +55,195 @@ parse_cliop(session_t *ps, const char *str, enum cliop *dest) {
/**
* @brief Parse a string representation of enum align.
*/
-static bool
+static int
parse_align(session_t *ps, const char *str, enum align *dest) {
static const char * const STRS_ALIGN[] = {
[ ALIGN_LEFT ] = "left",
[ ALIGN_MID ] = "mid",
[ ALIGN_RIGHT ] = "right",
};
- for (int i = 0; i < sizeof(STRS_ALIGN) / sizeof(STRS_ALIGN[0]); ++i)
- if (!strcmp(STRS_ALIGN[i], str)) {
+ for (int i = 0; i < CARR_LEN(STRS_ALIGN); ++i)
+ if (str_startswithword(str, STRS_ALIGN[i])) {
*dest = i;
- return true;
+ return strlen(STRS_ALIGN[i]);
}
printfef("(\"%s\"): Unrecognized operation.", str);
- return false;
+ return 0;
+}
+
+static inline bool
+parse_align_full(session_t *ps, const char *str, enum align *dest) {
+ int r = parse_align(ps, str, dest);
+ if (r && str[r]) r = 0;
+ return r;
+}
+
+/**
+ * @brief Parse a string representation of picture positioning mode.
+ */
+static int
+parse_pict_posp_mode(session_t *ps, const char *str, enum pict_posp_mode *dest) {
+ static const char * const STRS_PICTPOSP[] = {
+ [ PICTPOSP_ORIG ] = "orig",
+ [ PICTPOSP_SCALE ] = "scale",
+ [ PICTPOSP_SCALEK ] = "scalek",
+ [ PICTPOSP_TILE ] = "tile",
+ };
+ for (int i = 0; i < CARR_LEN(STRS_PICTPOSP); ++i)
+ if (str_startswithword(str, STRS_PICTPOSP[i])) {
+ *dest = i;
+ return strlen(STRS_PICTPOSP[i]);
+ }
+
+ printfef("(\"%s\"): Unrecognized operation.", str);
+ return 0;
+}
+static inline int
+parse_color_sub(const char *s, unsigned short *dest) {
+ static const int SEG = 2;
+
+ char *endptr = NULL;
+ long v = 0L;
+ char *s2 = mstrncpy(s, SEG);
+ v = strtol(s2, &endptr, 16);
+ int ret = 0;
+ if (endptr && s2 + strlen(s2) == endptr)
+ ret = endptr - s2;
+ free(s2);
+ if (!ret) return ret;
+ *dest = (double) v / 0xff * 0xffff;
+ return ret;
+}
+
+/**
+ * @brief Parse an option string into XRenderColor.
+ */
+static int
+parse_color(session_t *ps, const char *s, XRenderColor *pc) {
+ const char * const sorig = s;
+ static const struct {
+ const char *name;
+ XRenderColor c;
+ } PREDEF_COLORS[] = {
+ { "black", { 0x0000, 0x0000, 0x0000 } },
+ { "red", { 0xffff, 0x0000, 0x0000 } },
+ };
+
+ // Predefined color names
+ for (int i = 0; i < CARR_LEN(PREDEF_COLORS); ++i)
+ if (str_startswithwordi(s, PREDEF_COLORS[i].name)) {
+ *pc = PREDEF_COLORS[i].c;
+ return strlen(PREDEF_COLORS[i].name);
+ }
+
+ // RRGGBBAA color
+ if ('#' == s[0]) {
+ ++s;
+ int next = 0;
+ if (!((next = parse_color_sub(s, &pc->red))
+ && (next = parse_color_sub((s += next), &pc->green))
+ && (next = parse_color_sub((s += next), &pc->blue)))) {
+ printfef("(\"%s\"): Failed to read color segment.", s);
+ return 0;
+ }
+ if (!(next = parse_color_sub((s += next), &pc->alpha)))
+ pc->alpha = 0xffff;
+ s += next;
+ return s - sorig;
+ }
+
+ printfef("(\"%s\"): Unrecognized color format.", s);
+ return 0;
+}
+
+/**
+ * @brief Parse a size string.
+ */
+static int
+parse_size(const char *s, int *px, int *py) {
+ const char * const sorig = s;
+ long val = 0L;
+ char *endptr = NULL;
+ bool hasdata = false;
+
+#define T_NEXTFIELD() do { \
+ hasdata = true; \
+ if (isspace0(*s)) goto parse_size_end; \
+} while(0)
+
+ // Parse width
+ // Must be base 10, because "0x0..." may appear
+ val = strtol(s, &endptr, 10);
+ if (endptr && s != endptr) {
+ *px = val;
+ assert(*px >= 0);
+ s = endptr;
+ T_NEXTFIELD();
+ }
+
+ // Parse height
+ if ('x' == *s) {
+ ++s;
+ val = strtol(s, &endptr, 10);
+ if (endptr && s != endptr) {
+ *py = val;
+ if (*py < 0) {
+ printfef("(\"%s\"): Invalid height.", s);
+ return 0;
+ }
+ s = endptr;
+ }
+ T_NEXTFIELD();
+ }
+
+#undef T_NEXTFIELD
+
+ if (!hasdata)
+ return 0;
+
+ if (!isspace0(*s)) {
+ printfef("(\"%s\"): Trailing characters.", s);
+ return 0;
+ }
+
+parse_size_end:
+ return s - sorig;
+}
+
+/**
+ * @brief Parse an image specification.
+ */
+static bool
+parse_pictspec(session_t *ps, const char *s, pictspec_t *dest) {
+#define T_NEXTFIELD() do { \
+ s += next; \
+ while (isspace(*s)) ++s; \
+ if (!*s) goto parse_pictspec_end; \
+} while (0)
+
+ int next = 0;
+ T_NEXTFIELD();
+ if (!(next = parse_size(s, &dest->twidth, &dest->theight)))
+ dest->twidth = dest->theight = 0;
+ T_NEXTFIELD();
+ if (!(next = parse_pict_posp_mode(ps, s, &dest->mode)))
+ dest->mode = PICTPOSP_ORIG;
+ T_NEXTFIELD();
+ if (!(next = parse_align(ps, s, &dest->alg)))
+ dest->alg = ALIGN_MID;
+ T_NEXTFIELD();
+ if (!(next && (next = parse_align(ps, s, &dest->valg))))
+ dest->valg = ALIGN_MID;
+ T_NEXTFIELD();
+ next = parse_color(ps, s, &dest->c);
+ T_NEXTFIELD();
+ if (*s)
+ dest->path = mstrdup(s);
+#undef T_NEXTFIELD
+
+parse_pictspec_end:
+ return true;
}
static dlist *
@@ -77,7 +251,7 @@ update_clients(MainWin *mw, dlist *clients, Bool *touched)
{
dlist *stack, *iter;
- stack = dlist_first(wm_get_stack(mw->ps->dpy));
+ stack = dlist_first(wm_get_stack(mw->ps));
iter = clients = dlist_first(clients);
if(touched)
@@ -86,7 +260,7 @@ update_clients(MainWin *mw, dlist *clients, Bool *touched)
/* Terminate clients that are no longer managed */
while (iter) {
ClientWin *cw = (ClientWin *)iter->data;
- if (!dlist_find_data(stack, (void *) cw->client.window)) {
+ if (!dlist_find_data(stack, (void *) cw->src.window)) {
dlist *tmp = iter->next;
clientwin_destroy((ClientWin *)iter->data, True);
clients = dlist_remove(iter);
@@ -103,7 +277,8 @@ update_clients(MainWin *mw, dlist *clients, Bool *touched)
/* Add new clients */
for(iter = dlist_first(stack); iter; iter = iter->next)
{
- ClientWin *cw = (ClientWin*)dlist_find(clients, clientwin_cmp_func, iter->data);
+ ClientWin *cw = (ClientWin *)
+ dlist_find(clients, clientwin_cmp_func, iter->data);
if(! cw && (Window)iter->data != mw->window)
{
cw = clientwin_create(mw, (Window)iter->data);
@@ -121,11 +296,10 @@ update_clients(MainWin *mw, dlist *clients, Bool *touched)
}
static dlist *
-do_layout(MainWin *mw, dlist *clients, Window focus, Window leader)
-{
+do_layout(MainWin *mw, dlist *clients, Window focus, Window leader) {
session_t * const ps = mw->ps;
- CARD32 desktop = wm_get_current_desktop(ps->dpy);
+ long desktop = wm_get_current_desktop(ps);
unsigned int width, height;
float factor;
int xoff, yoff;
@@ -133,19 +307,27 @@ do_layout(MainWin *mw, dlist *clients, Window focus, Window leader)
/* Update the client table, pick the ones we want and sort them */
clients = update_clients(mw, clients, 0);
-
- if(mw->cod)
- dlist_free(mw->cod);
-
- tmp = dlist_first(dlist_find_all(clients, (dlist_match_func)clientwin_validate_func, &desktop));
- if(leader != None)
- {
+ if (!clients) {
+ printfef("(): No client windows found.");
+ return clients;
+ }
+
+ dlist_free(mw->cod);
+ mw->cod = NULL;
+
+ tmp = dlist_first(dlist_find_all(clients,
+ (dlist_match_func) clientwin_validate_func, &desktop));
+ if (!tmp) {
+ printfef("(): No client window on current desktop found.");
+ return clients;
+ }
+ if (leader) {
mw->cod = dlist_first(dlist_find_all(tmp, clientwin_check_group_leader_func, (void*)&leader));
dlist_free(tmp);
} else
mw->cod = tmp;
- if(! mw->cod)
+ if (!mw->cod)
return clients;
dlist_sort(mw->cod, clientwin_sort_func, 0);
@@ -163,7 +345,7 @@ do_layout(MainWin *mw, dlist *clients, Window focus, Window leader)
clientwin_move((ClientWin*)iter->data, factor, xoff, yoff);
/* Get the currently focused window and select which mini-window to focus */
- iter = dlist_find(mw->cod, clientwin_cmp_func, (void *)focus);
+ iter = dlist_find(mw->cod, clientwin_cmp_func, (void *) focus);
if(! iter)
iter = mw->cod;
mw->focus = (ClientWin*)iter->data;
@@ -266,6 +448,7 @@ ev_window(session_t *ps, const XEvent *ev) {
static inline void
ev_dump(session_t *ps, const MainWin *mw, const XEvent *ev) {
if (!ev || (ps->xinfo.damage_ev_base + XDamageNotify) == ev->type) return;
+ // if (MotionNotify == ev->type) return;
const char *name = ev_dumpstr_type(ev);
@@ -303,7 +486,7 @@ skippy_run(MainWin *mw, dlist *clients, Window focus, Window leader, Bool all_xi
clients = do_layout(mw, clients, focus, leader);
if (!mw->cod) {
- printfef("(): No client windows found.");
+ printfef("(): Failed to build layout.");
return clients;
}
@@ -312,30 +495,24 @@ skippy_run(MainWin *mw, dlist *clients, Window focus, Window leader, Bool all_xi
mainwin_map(mw);
XFlush(ps->dpy);
+ bool pending_damage = false;
int last_rendered = time_in_millis();
while (!die) {
- int i, now, timeout;
- struct pollfd r_fd;
-
- XFlush(ps->dpy);
-
- r_fd.fd = ConnectionNumber(ps->dpy);
- r_fd.events = POLLIN;
- if(mw->poll_time > 0)
- timeout = MAX(0, mw->poll_time + last_rendered - time_in_millis());
- else
- timeout = -1;
- i = poll(&r_fd, 1, timeout);
-
- now = time_in_millis();
- if(now >= last_rendered + mw->poll_time)
+ // Poll for events
{
- REDUCE(if( ((ClientWin*)iter->data)->damaged ) clientwin_repair(iter->data), mw->cod);
- last_rendered = now;
+ int timeout = -1;
+ struct pollfd r_fd = {
+ .fd = ConnectionNumber(ps->dpy),
+ .events = POLLIN,
+ };
+ if (mw->poll_time > 0 && pending_damage)
+ timeout = MAX(0,
+ mw->poll_time + last_rendered - time_in_millis());
+ poll(&r_fd, 1, timeout);
}
- i = XPending(ps->dpy);
- for (int j = 0; j < i && !die; ++j) {
+ // Process events
+ while (XEventsQueued(ps->dpy, QueuedAfterReading)) {
XNextEvent(ps->dpy, &ev);
#ifdef DEBUG_EVENTS
ev_dump(ps, mw, &ev);
@@ -357,18 +534,19 @@ skippy_run(MainWin *mw, dlist *clients, Window focus, Window leader, Bool all_xi
mw->cod = dlist_first(dlist_remove(iter));
clientwin_destroy(cw, true);
if (!mw->cod) {
- printfef("(): Last client window destroyed/unmapped, exiting.");
+ printfef("(): Last client window destroyed/unmapped, "
+ "exiting.");
die = 1;
}
}
}
- else if (mw->poll_time >= 0 && ev.type == ps->xinfo.damage_ev_base + XDamageNotify)
- {
- XDamageNotifyEvent *d_ev = (XDamageNotifyEvent *)&ev;
- dlist *iter = dlist_find(mw->cod, clientwin_cmp_func, (void *)d_ev->drawable);
- if(iter)
- {
- if(mw->poll_time == 0)
+ else if (ps->xinfo.damage_ev_base + XDamageNotify == ev.type) {
+ // XDamageNotifyEvent *d_ev = (XDamageNotifyEvent *) &ev;
+ dlist *iter = dlist_find(mw->cod, clientwin_cmp_func,
+ (void *) wid);
+ pending_damage = true;
+ if (iter) {
+ if (!mw->poll_time)
clientwin_repair((ClientWin *)iter->data);
else
((ClientWin *)iter->data)->damaged = true;
@@ -387,9 +565,10 @@ skippy_run(MainWin *mw, dlist *clients, Window focus, Window leader, Bool all_xi
}
else if (wid == mw->window)
die = mainwin_handle(mw, &ev);
- else if(ev.type == PropertyNotify) {
- if(ev.xproperty.atom == ESETROOT_PMAP_ID
- || ev.xproperty.atom == _XROOTPMAP_ID) {
+ else if (PropertyNotify == ev.type) {
+ if (!ps->o.backgroundImg &&
+ (ESETROOT_PMAP_ID == ev.xproperty.atom
+ || _XROOTPMAP_ID == ev.xproperty.atom)) {
mainwin_update_background(mw);
REDUCE(clientwin_render((ClientWin *)iter->data), mw->cod);
}
@@ -406,8 +585,23 @@ skippy_run(MainWin *mw, dlist *clients, Window focus, Window leader, Bool all_xi
}
}
}
+
+ // Do delayed painting if it's active
+ if (mw->poll_time && pending_damage && !die) {
+ long now = time_in_millis();
+ if (now >= last_rendered + mw->poll_time) {
+ pending_damage = false;
+ foreach_dlist(mw->cod) {
+ if (((ClientWin *) iter->data)->damaged)
+ clientwin_repair(iter->data);
+ }
+ last_rendered = now;
+ }
+ }
+
+ XFlush(ps->dpy);
}
-
+
/* Unmap the main window and clean up */
mainwin_unmap(mw);
XFlush(ps->dpy);
@@ -519,8 +713,7 @@ xerror(Display *dpy, XErrorEvent *ev) {
#define SKIPPYXD_VERSION "unknown"
#endif
-void show_help()
-{
+void show_help() {
fputs("skippy-xd (" SKIPPYXD_VERSION ")\n"
"Usage: skippy-xd [command]\n\n"
"The available commands are:\n"
@@ -531,6 +724,9 @@ void show_help()
"\t--help - show this message.\n"
"\t-S - Synchronize X operation (debugging).\n"
, stdout);
+#ifdef CFG_LIBPNG
+ spng_about(stdout);
+#endif
}
static inline bool
@@ -727,6 +923,25 @@ int main(int argc, char *argv[]) {
// First pass
parse_args(ps, argc, argv, true);
+ // Open connection to X
+ if (!(ps->dpy = dpy = XOpenDisplay(NULL))) {
+ printfef("(): FATAL: Couldn't connect to display.");
+ ret = RET_XFAIL;
+ goto main_end;
+ }
+ if (!init_xexts(ps)) {
+ ret = RET_XFAIL;
+ goto main_end;
+ }
+ if (ps->o.synchronize)
+ XSynchronize(ps->dpy, True);
+ XSetErrorHandler(xerror);
+
+ ps->screen = DefaultScreen(dpy);
+ ps->root = RootWindow(dpy, ps->screen);
+
+ wm_get_atoms(ps);
+
// Load configuration file
{
dlist *config = NULL;
@@ -755,12 +970,17 @@ int main(int argc, char *argv[]) {
|| !parse_cliop(ps, config_get(config, "bindings", "miwMouse3", "iconify"), &ps->o.bindings_miwMouse[3]))
return RET_BADARG;
if (config) {
+ char *lc_numeric_old = mstrdup(setlocale(LC_NUMERIC, NULL));
+ setlocale(LC_NUMERIC, "C");
config_get_int_wrap(config, "general", "distance", &ps->o.distance, 1, INT_MAX);
config_get_bool_wrap(config, "general", "useNetWMFullscreen", &ps->o.useNetWMFullscreen);
config_get_bool_wrap(config, "general", "ignoreSkipTaskbar", &ps->o.ignoreSkipTaskbar);
+ config_get_bool_wrap(config, "general", "acceptOvRedir", &ps->o.acceptOvRedir);
+ config_get_bool_wrap(config, "general", "acceptWMWin", &ps->o.acceptWMWin);
config_get_double_wrap(config, "general", "updateFreq", &ps->o.updateFreq, -1000.0, 1000.0);
config_get_bool_wrap(config, "general", "lazyTrans", &ps->o.lazyTrans);
config_get_bool_wrap(config, "general", "useNameWindowPixmap", &ps->o.useNameWindowPixmap);
+ config_get_bool_wrap(config, "general", "includeFrame", &ps->o.includeFrame);
config_get_bool_wrap(config, "general", "movePointerOnStart", &ps->o.movePointerOnStart);
config_get_bool_wrap(config, "xinerama", "showAll", &ps->o.xinerama_showAll);
config_get_int_wrap(config, "normal", "tintOpacity", &ps->o.normal_tintOpacity, 0, 256);
@@ -771,11 +991,36 @@ int main(int argc, char *argv[]) {
config_get_bool_wrap(config, "tooltip", "followsMouse", &ps->o.tooltip_followsMouse);
config_get_int_wrap(config, "tooltip", "offsetX", &ps->o.tooltip_offsetX, INT_MIN, INT_MAX);
config_get_int_wrap(config, "tooltip", "offsetY", &ps->o.tooltip_offsetY, INT_MIN, INT_MAX);
- if (!parse_align(ps, config_get(config, "tooltip", "align", "left"), &ps->o.tooltip_align))
+ if (!parse_align_full(ps, config_get(config, "tooltip", "align", "left"), &ps->o.tooltip_align))
return RET_BADARG;
config_get_int_wrap(config, "tooltip", "tintOpacity", &ps->o.highlight_tintOpacity, 0, 256);
config_get_int_wrap(config, "tooltip", "opacity", &ps->o.tooltip_opacity, 0, 256);
+ {
+ const char *sspec = config_get(config, "general", "backgroundImg", "");
+ if (sspec && strlen(sspec)) {
+ pictspec_t spec = PICTSPECT_INIT;
+ if (!parse_pictspec(ps, sspec, &spec))
+ return RET_BADARG;
+ int root_width = DisplayWidth(ps->dpy, ps->screen),
+ root_height = DisplayHeight(ps->dpy, ps->screen);
+ if (!(spec.twidth || spec.theight)) {
+ spec.twidth = root_width;
+ spec.theight = root_height;
+ }
+ pictw_t *p = simg_load_s(ps, &spec);
+ if (!p)
+ exit(1);
+ if (p->width != root_width || p->height != root_height)
+ ps->o.backgroundImg = simg_postprocess(ps, p, PICTPOSP_ORIG,
+ root_width, root_height, ALIGN_LEFT, ALIGN_LEFT, &spec.c);
+ else
+ ps->o.backgroundImg = p;
+ free_pictspec(ps, &spec);
+ }
+ }
+ setlocale(LC_NUMERIC, lc_numeric_old);
+ free(lc_numeric_old);
config_free(config);
}
}
@@ -796,36 +1041,11 @@ int main(int argc, char *argv[]) {
goto main_end;
}
- // Open connection to X
- ps->dpy = dpy = XOpenDisplay(NULL);
- if(!dpy) {
- printfef("(): FATAL: Couldn't connect to display.");
- ret = RET_XFAIL;
- goto main_end;
- }
- if (!init_xexts(ps)) {
- ret = RET_XFAIL;
- goto main_end;
- }
- if (ps->o.synchronize)
- XSynchronize(ps->dpy, True);
- XSetErrorHandler(xerror);
-
- ps->screen = DefaultScreen(dpy);
- ps->root = RootWindow(dpy, ps->screen);
-
- wm_get_atoms(ps);
-
- if (!wm_check(dpy)) {
- printfef("(): WM not NETWM or GNOME WM Spec compliant. "
- "Troubles ahead.");
+ if (!wm_check(ps)) {
/* ret = 1;
goto main_end; */
}
- wm_use_netwm_fullscreen(ps->o.useNetWMFullscreen);
- wm_ignore_skip_taskbar(ps->o.ignoreSkipTaskbar);
-
mw = mainwin_create(ps);
if (!mw) {
fprintf(stderr, "FATAL: Couldn't create main window.\n");
@@ -935,6 +1155,7 @@ main_end:
free(ps->o.tooltip_text);
free(ps->o.tooltip_textShadow);
free(ps->o.tooltip_font);
+ free_pictw(ps, &ps->o.backgroundImg);
}
if (ps->dpy)
diff --git a/src/skippy.h b/src/skippy.h
index d3e3eaf..41bd642 100644
--- a/src/skippy.h
+++ b/src/skippy.h
@@ -59,11 +59,48 @@
#include <time.h>
#include <regex.h>
#include <string.h>
+#include <inttypes.h>
+#include <ctype.h>
#include "dlist.h"
#define MAX_MOUSE_BUTTONS 4
+/**
+ * @brief Dump raw bytes in HEX format.
+ *
+ * @param data pointer to raw data
+ * @param len length of data
+ */
+static inline void
+hexdump(const char *data, int len) {
+ static const int BYTE_PER_LN = 16;
+
+ if (len <= 0)
+ return;
+
+ // Print header
+ printf("%10s:", "Offset");
+ for (int i = 0; i < BYTE_PER_LN; ++i)
+ printf(" %2d", i);
+ putchar('\n');
+
+ // Dump content
+ for (int offset = 0; offset < len; ++offset) {
+ if (!(offset % BYTE_PER_LN))
+ printf("0x%08x:", offset);
+
+ printf(" %02hhx", data[offset]);
+
+ if ((BYTE_PER_LN - 1) == offset % BYTE_PER_LN)
+ putchar('\n');
+ }
+ if (len % BYTE_PER_LN)
+ putchar('\n');
+
+ fflush(stdout);
+}
+
/// @brief Possible return values.
enum {
RET_SUCCESS = 0,
@@ -95,6 +132,48 @@ enum align {
ALIGN_RIGHT,
};
+enum buttons {
+ BUTN_CLOSE,
+ BUTN_MINIMIZE,
+ BUTN_SHADE,
+ NUM_BUTN,
+};
+
+enum pict_posp_mode {
+ PICTPOSP_ORIG,
+ PICTPOSP_SCALE,
+ PICTPOSP_SCALEK,
+ PICTPOSP_TILE,
+};
+
+typedef enum {
+ WMPSN_NONE,
+ WMPSN_EWMH,
+ WMPSN_GNOME,
+} wmpsn_t;
+
+typedef struct {
+ char *path;
+ enum pict_posp_mode mode;
+ int twidth;
+ int theight;
+ enum align alg;
+ enum align valg;
+ XRenderColor c;
+} pictspec_t;
+
+#define PICTSPECT_INIT { \
+ .path = NULL, \
+}
+
+typedef struct {
+ Pixmap pxmap;
+ Picture pict;
+ int height;
+ int width;
+ int depth;
+} pictw_t;
+
/// @brief Option structure.
typedef struct {
char *config_path;
@@ -105,11 +184,16 @@ typedef struct {
int distance;
bool useNetWMFullscreen;
bool ignoreSkipTaskbar;
+ bool acceptOvRedir;
+ bool acceptWMWin;
double updateFreq;
bool lazyTrans;
bool useNameWindowPixmap;
+ bool includeFrame;
char *pipePath;
bool movePointerOnStart;
+ char *buttonImgs[NUM_BUTN];
+ pictw_t *backgroundImg;
bool xinerama_showAll;
@@ -145,11 +229,16 @@ typedef struct {
.distance = 50, \
.useNetWMFullscreen = true, \
.ignoreSkipTaskbar = false, \
+ .acceptOvRedir = false, \
+ .acceptWMWin = false, \
.updateFreq = 10.0, \
.lazyTrans = false, \
.useNameWindowPixmap = false, \
+ .includeFrame = false, \
.pipePath = NULL, \
.movePointerOnStart = true, \
+ .buttonImgs = { NULL }, \
+ .backgroundImg = NULL, \
.xinerama_showAll = false, \
.normal_tint = NULL, \
.normal_tintOpacity = 0, \
@@ -204,6 +293,10 @@ typedef struct {
xinfo_t xinfo;
/// @brief Time the program was started, in milliseconds.
struct timeval time_start;
+ /// @brief WM personality.
+ wmpsn_t wmpsn;
+ /// @brief Whether we have EWMH fullscreen support.
+ bool has_ewmh_fullscreen;
} session_t;
#define SESSIONT_INIT { \
@@ -216,20 +309,29 @@ typedef struct {
#define printfd(format, ...) \
(fprintf(stdout, format "\n", ## __VA_ARGS__), fflush(stdout))
+/// @brief Print out a debug message with function name.
+#define printfdf(format, ...) \
+ (fprintf(stdout, "%s" format "\n", __func__, ## __VA_ARGS__), fflush(stdout))
+
/// @brief Print out an error message.
#define printfe(format, ...) \
(fprintf(stderr, format "\n", ## __VA_ARGS__), fflush(stderr))
/// @brief Print out an error message with function name.
#define printfef(format, ...) \
- printfe("%s" format, __func__, ## __VA_ARGS__)
+ printfe("%s" format, __func__, ## __VA_ARGS__)
+
+/// @brief Wrapper for gcc branch prediction builtin, for likely branch.
+#define likely(x) __builtin_expect(!!(x), 1)
+/// @brief Wrapper for gcc branch prediction builtin, for unlikely branch.
+#define unlikely(x) __builtin_expect(!!(x), 0)
/**
* @brief Quit with RETV_BADALLOC if the passed-in pointer is empty.
*/
static inline void *
allocchk_(void *ptr, const char *func_name) {
- if (!ptr) {
+ if (unlikely(!ptr)) {
printfe("%s(): Failed to allocate memory.", func_name);
exit(RET_BADALLOC);
}
@@ -244,14 +346,18 @@ allocchk_(void *ptr, const char *func_name) {
/// Use #s here to prevent macro expansion
#define CASESTRRET(s) case s: return #s
+/// @brief Return number of elements in a constant array.
+#define CARR_LEN(a) sizeof(a) / sizeof(a[0])
+
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
#define MIN(a,b) (((a) > (b)) ? (b) : (a))
+#define foreach_dlist(l) \
+ for (dlist *iter = dlist_first(l); iter; iter = iter->next)
#define REDUCE(statement, l) \
-{ \
- dlist *iter = dlist_first(l); \
- for(; iter; iter = iter->next) \
- statement; \
-}
+ do { \
+ foreach_dlist(l) \
+ statement; \
+ } while (0)
/**
* @brief Get current time, in milliseconds.
@@ -350,6 +456,18 @@ mstrdup(const char *src) {
}
/**
+ * @brief Copy a number of characters to a newly allocated string.
+ */
+static inline char *
+mstrncpy(const char *src, unsigned len) {
+ char *str = allocchk(malloc(sizeof(char) * (len + 1)));
+ strncpy(str, src, len);
+ str[len] = '\0';
+
+ return str;
+}
+
+/**
* @brief Copy and place a string to somewhere.
*/
static inline void
@@ -359,23 +477,62 @@ strplace(char **dst, const char *src) {
}
/**
- * @brief Destroy a <code>Picture</code>.
+ * @brief Check if a character symbolizes end of a word.
+ */
+static inline bool
+isspace0(char c) {
+ return !c || isspace(c);
+}
+
+/**
+ * @brief Check if a string ends with something, ignore case.
+ */
+static inline bool
+str_endwith(const char *haystick, const char *needle) {
+ int haystick_len = strlen(haystick);
+ int needle_len = strlen(needle);
+ return haystick_len >= needle_len
+ && !strcasecmp(haystick + haystick_len - needle_len, needle);
+}
+
+/**
+ * @brief Check if a string starts with some words, ignore case.
+ */
+static inline bool
+str_startswithword(const char *haystick, const char *needle) {
+ const int needle_len = strlen(needle);
+ return !strncmp(haystick, needle, needle_len)
+ && isspace0(haystick[needle_len]);
+}
+
+/**
+ * @brief Check if a string starts with some words, ignore case.
+ */
+static inline bool
+str_startswithwordi(const char *haystick, const char *needle) {
+ const int needle_len = strlen(needle);
+ return !strncasecmp(haystick, needle, needle_len)
+ && isspace0(haystick[needle_len]);
+}
+
+/**
+ * @brief Destroy a <code>Pixmap</code>.
*/
static inline void
-free_picture(session_t *ps, Picture *p) {
+free_pixmap(session_t *ps, Pixmap *p) {
if (*p) {
- XRenderFreePicture(ps->dpy, *p);
+ XFreePixmap(ps->dpy, *p);
*p = None;
}
}
/**
- * @brief Destroy a <code>Pixmap</code>.
+ * @brief Destroy a <code>Picture</code>.
*/
static inline void
-free_pixmap(session_t *ps, Pixmap *p) {
+free_picture(session_t *ps, Picture *p) {
if (*p) {
- XFreePixmap(ps->dpy, *p);
+ XRenderFreePicture(ps->dpy, *p);
*p = None;
}
}
@@ -403,6 +560,14 @@ free_region(session_t *ps, XserverRegion *p) {
}
}
+/**
+ * @brief Destroy a <code>pictspec_t</code>.
+ */
+static inline void
+free_pictspec(session_t *ps, pictspec_t *p) {
+ free(p->path);
+}
+
static inline unsigned short
alphaconv(int alpha) {
return MIN(alpha * 256, 65535);
@@ -420,6 +585,15 @@ sxfree(void *data) {
}
/**
+ * @brief Wrapper to sxfree() to turn the pointer NULL as well.
+ */
+static inline void
+spxfree(void *data) {
+ sxfree(*(void **) data);
+ *(void **) data = NULL;
+}
+
+/**
* @brief Return a string representation for the keycode in a KeyEvent.
*/
static inline const char *
@@ -442,6 +616,18 @@ ev_key_str(XKeyEvent *ev) {
#include "focus.h"
#include "config.h"
#include "tooltip.h"
+#include "img.h"
+#ifdef CFG_LIBPNG
+// FreeType uses setjmp.h and libpng-1.2 feels crazy about this...
+#define PNG_SKIP_SETJMP_CHECK 1
+#include "img-png.h"
+#endif
+#ifdef CFG_JPEG
+#include "img-jpeg.h"
+#endif
+#ifdef CFG_GIFLIB
+#include "img-gif.h"
+#endif
extern session_t *ps_g;
diff --git a/src/wm.c b/src/wm.c
index 04c80f4..a7f0947 100644
--- a/src/wm.c
+++ b/src/wm.c
@@ -25,6 +25,7 @@ Atom
ESETROOT_PMAP_ID,
// ICCWM atoms
+ WM_STATE,
WM_PROTOCOLS,
WM_DELETE_WINDOW,
@@ -72,9 +73,6 @@ static Atom
_WIN_STATE,
_WIN_HINTS;
-#define WM_PERSONALITY_NETWM 0
-#define WM_PERSONALITY_GNOME 1
-
/* From WindowMaker's gnome.c */
#define WIN_HINTS_SKIP_FOCUS (1<<0) /*"alt-tab" skips this win*/
#define WIN_HINTS_SKIP_WINLIST (1<<1) /*do not show in window list*/
@@ -96,11 +94,6 @@ static Atom
#define WIN_STATE_FIXED_POSITION (1<<8) /*window is fixed in position even*/
#define WIN_STATE_ARRANGE_IGNORE (1<<9) /*ignore for auto arranging*/
-
-static int WM_PERSONALITY = WM_PERSONALITY_NETWM,
- NETWM_HAS_FULLSCREEN = 0,
- IGNORE_SKIP_TASKBAR = 0;
-
/**
* @brief Wrapper of XInternAtom().
*/
@@ -122,6 +115,7 @@ wm_get_atoms(session_t *ps) {
T_GETATOM(_XROOTPMAP_ID);
T_GETATOM(ESETROOT_PMAP_ID);
+ T_GETATOM(WM_STATE),
T_GETATOM(WM_PROTOCOLS),
T_GETATOM(WM_DELETE_WINDOW),
@@ -159,182 +153,233 @@ wm_get_atoms(session_t *ps) {
#undef T_GETATOM
}
-char
-wm_check_netwm(Display *dpy)
-{
- Window wm_check;
- unsigned char *data, *data2;
-
- int status, real_format;
- Atom real_type;
- unsigned long items_read, items_left, i;
-
- char req = 0;
-
- status = XGetWindowProperty(dpy, DefaultRootWindow(dpy), _NET_SUPPORTING_WM_CHECK,
- 0L, 1L, False, XA_WINDOW, &real_type, &real_format,
- &items_read, &items_left, &data);
- if(status != Success || ! items_read) {
- if(status == Success)
- XFree(data);
- return 0;
- }
-
- wm_check = ((Window*)data)[0];
- XFree(data);
-
- status = XGetWindowProperty(dpy, wm_check, _NET_SUPPORTING_WM_CHECK,
- 0L, 1L, False, XA_WINDOW, &real_type, &real_format,
- &items_read, &items_left, &data);
-
- if(status != Success && ! items_read) {
- if(status == Success)
- XFree(data);
- return 0;
- }
-
- if(wm_check != ((Window*)data)[0]) {
- XFree(data);
- return 0;
- }
-
- XFree(data);
-
- status = XGetWindowProperty(dpy, DefaultRootWindow(dpy), _NET_SUPPORTED,
+bool
+wm_check_netwm(session_t *ps) {
+ Display *dpy = ps->dpy;
+
+ Window wm_check = None;
+ unsigned char *data = NULL;
+
+ int real_format = 0;
+ Atom real_type = None;
+ unsigned long items_read = 0, items_left = 0;
+
+ bool success = (Success == XGetWindowProperty(dpy, ps->root,
+ _NET_SUPPORTING_WM_CHECK,
+ 0L, 1L, False, XA_WINDOW, &real_type, &real_format,
+ &items_read, &items_left, &data)
+ && items_read && data && 32 == real_format);
+ if (success)
+ wm_check = *((long *)data);
+ spxfree(&data);
+ if (!success) return false;
+
+ success = (Success == XGetWindowProperty(dpy, wm_check,
+ _NET_SUPPORTING_WM_CHECK,
+ 0L, 1L, False, XA_WINDOW, &real_type, &real_format,
+ &items_read, &items_left, &data)
+ && items_read && data && 32 == real_format
+ && wm_check == *((long *)data));
+ spxfree(&data);
+ if (!success) return false;
+
+ success = (Success == XGetWindowProperty(dpy, ps->root, _NET_SUPPORTED,
0L, 8192L, False, XA_ATOM, &real_type, &real_format,
- &items_read, &items_left, &data2);
-
- if(status != Success || ! items_read) {
- if(status == Success)
- XFree(data2);
- return 0;
- }
-
- for(i = 0; i < items_read; i++) {
- if(((Atom*)data2)[i] == _NET_NUMBER_OF_DESKTOPS)
+ &items_read, &items_left, &data)
+ && items_read && data && 32 == real_format);
+ if (!success)
+ items_read = 0;
+
+ long *ldata = (long *) data;
+ int req = 0;
+ for (int i = 0; i < items_read; i++) {
+ if (_NET_NUMBER_OF_DESKTOPS == ldata[i])
req |= 1;
- else if(((Atom*)data2)[i] == _NET_CURRENT_DESKTOP)
+ else if (_NET_CURRENT_DESKTOP == ldata[i])
req |= 2;
- else if(((Atom*)data2)[i] == _NET_WM_STATE)
+ else if (_NET_WM_STATE == ldata[i])
req |= 4;
- else if(((Atom*)data2)[i] == _NET_CLIENT_LIST)
+ else if (_NET_CLIENT_LIST == ldata[i])
req |= 8;
- else if(((Atom*)data2)[i] == _NET_CLIENT_LIST_STACKING)
- req |= 16;
- else if(((Atom*)data2)[i] == _NET_WM_STATE_FULLSCREEN)
- NETWM_HAS_FULLSCREEN = 1;
+ else if (_NET_CLIENT_LIST_STACKING == ldata[i]) {
+ req |= 8;
+ _NET_CLIENT_LIST = _NET_CLIENT_LIST_STACKING;
+ }
+ else if (_NET_WM_STATE_FULLSCREEN == ldata[i])
+ ps->has_ewmh_fullscreen = true;
}
- XFree(data2);
- if(req & 16) {
- req |= 8;
- _NET_CLIENT_LIST = _NET_CLIENT_LIST_STACKING;
- }
-
+
+ spxfree(&data);
+
return ((req & 15) == 15);
}
-char
-wm_check_gnome(Display *dpy)
-{
- Window wm_check;
- unsigned char *data, *data2;
-
- int status, real_format;
- Atom real_type;
- unsigned long items_read, items_left, i;
-
- char req = 0;
-
- WM_PERSONALITY = WM_PERSONALITY_GNOME;
-
- status = XGetWindowProperty(dpy, DefaultRootWindow(dpy), _WIN_SUPPORTING_WM_CHECK,
- 0L, 1L, False, XA_CARDINAL, &real_type, &real_format,
- &items_read, &items_left, &data);
- if(status != Success || ! items_read) {
- if(status == Success)
- XFree(data);
- return 0;
- }
-
- wm_check = ((Window*)data)[0];
- XFree(data);
-
- status = XGetWindowProperty(dpy, wm_check, _WIN_SUPPORTING_WM_CHECK,
- 0L, 1L, False, XA_CARDINAL, &real_type, &real_format,
- &items_read, &items_left, &data);
-
- if(status != Success && ! items_read) {
- if(status == Success)
- XFree(data);
- return 0;
- }
-
- if(wm_check != ((Window*)data)[0]) {
- XFree(data);
- return 0;
- }
-
- XFree(data);
+bool
+wm_check_gnome(session_t *ps) {
+ Display *dpy = ps->dpy;
+ unsigned char *data = NULL;
- status = XGetWindowProperty(dpy, DefaultRootWindow(dpy), _WIN_PROTOCOLS,
- 0L, 8192L, False, XA_ATOM, &real_type, &real_format,
- &items_read, &items_left, &data2);
+ Window wm_check = None;
+ int real_format = 0;
+ Atom real_type = None;
+ unsigned long items_read = 0, items_left = 0;
- if(status != Success || ! items_read) {
- if(status == Success)
- XFree(data2);
- return 0;
- }
+ // Make sure _WIN_SUPPORTING_WM_CHECK is present on root window
+ bool success = (Success ==
+ XGetWindowProperty(dpy, ps->root, _WIN_SUPPORTING_WM_CHECK,
+ 0L, 1L, False, XA_CARDINAL, &real_type, &real_format,
+ &items_read, &items_left, &data)
+ && items_read && data && 32 == real_format);
- for(i = 0; i < items_read; i++) {
- if(((Atom*)data2)[i] == _WIN_WORKSPACE)
+ if (success)
+ wm_check = ((long *)data)[0];
+ spxfree(&data);
+ if (!success)
+ return success;
+
+ /*
+ // Make sure _WIN_SUPPORTING_WM_CHECK is present on the WM check window
+ // as well
+ success = (Success == XGetWindowProperty(dpy, wm_check, _WIN_SUPPORTING_WM_CHECK,
+ 0L, 1L, False, XA_CARDINAL, &real_type, &real_format,
+ &items_read, &items_left, &data) && items_real
+ && wm_check == *((long *) data));
+ spxfree(&data);
+ if (!success)
+ return success;
+ */
+
+ // Check supported protocols
+ success = (Success == XGetWindowProperty(dpy, ps->root, _WIN_PROTOCOLS,
+ 0L, 8192L, False, XA_ATOM, &real_type, &real_format,
+ &items_read, &items_left, &data)
+ && items_read && data && 32 == real_format);
+ if (!success)
+ items_read = 0;
+
+ long *ldata = (long *) data;
+ int req = 0;
+ for (int i = 0; i < items_read; i++) {
+ if (_WIN_WORKSPACE == ldata[i])
req |= 1;
- else if(((Atom*)data2)[i] == _WIN_WORKSPACE_COUNT)
+ else if (_WIN_WORKSPACE_COUNT == ldata[i])
req |= 2;
- else if(((Atom*)data2)[i] == _WIN_STATE)
+ else if (_WIN_STATE == ldata[i])
req |= 4;
- else if(((Atom*)data2)[i] == _WIN_CLIENT_LIST)
+ else if (_WIN_CLIENT_LIST == ldata[i])
req |= 8;
}
- XFree(data2);
-
+ spxfree(&data);
+
return ((req & 15) == 15);
}
-char
-wm_check(Display *dpy)
-{
- return wm_check_netwm(dpy) || wm_check_gnome(dpy);
+/**
+ * @brief Find the client window under a specific frame window.
+ *
+ * Using a depth-first search.
+ */
+static Window
+wm_find_client(session_t *ps, Window wid) {
+ dlist *stack = dlist_add(NULL, (void *) wid);
+ Window result = None;
+ while (stack) {
+ dlist *stack2 = NULL;
+ foreach_dlist (stack) {
+ Window cur = (Window) iter->data;
+ if (wid_has_prop(ps, cur, WM_STATE)) {
+ result = cur;
+ break;
+ }
+ Window *children = NULL;
+ unsigned nchildren = 0;
+ Window rroot = None, rparent = None;
+ if (XQueryTree(ps->dpy, cur, &rroot, &rparent,
+ &children, &nchildren) && nchildren && children)
+ for (int i = 0; i < nchildren; ++i)
+ stack2 = dlist_add(stack2, (void *) children[i]);
+ sxfree(children);
+ }
+ dlist_free(stack);
+ if (result) {
+ free(stack2);
+ break;
+ }
+ else {
+ stack = stack2;
+ }
+ }
+
+ return result;
+}
+
+static inline dlist *
+wm_get_stack_fromprop(session_t *ps, Atom a) {
+ dlist *l = NULL;
+ unsigned char *data = NULL;
+ int real_format = 0;
+ Atom real_type = None;
+ unsigned long items_read = 0, items_left = 0;
+ int status = XGetWindowProperty(ps->dpy, ps->root, a,
+ 0L, 8192L, False, XA_WINDOW, &real_type, &real_format,
+ &items_read, &items_left, &data);
+ if (Success == status && 32 == real_format && data)
+ for (int i = 0; i < items_read; i++) {
+ l = dlist_add(l, (void *) ((long *) data)[i]);
+ }
+
+ sxfree(data);
+ return l;
}
dlist *
-wm_get_stack(Display *dpy)
-{
- dlist *l = 0;
- unsigned char *data;
- int status, real_format;
- Atom real_type;
- unsigned long items_read, items_left, i;
-
- if (WM_PERSONALITY == WM_PERSONALITY_NETWM)
- status = XGetWindowProperty(dpy, DefaultRootWindow(dpy), _NET_CLIENT_LIST,
- 0L, 8192L, False, XA_WINDOW, &real_type, &real_format,
- &items_read, &items_left, &data);
- else
- status = XGetWindowProperty(dpy, DefaultRootWindow(dpy), _WIN_CLIENT_LIST,
- 0L, 8192L, False, XA_CARDINAL, &real_type, &real_format,
- &items_read, &items_left, &data);
-
- if(status != Success)
- return 0;
-
- for(i = 0; i < items_read; i++)
+wm_get_stack(session_t *ps) {
+ dlist *l = NULL;
+
+ if (!(ps->o.acceptOvRedir || ps->o.acceptWMWin)) {
+ // EWMH
+ l = wm_get_stack_fromprop(ps, _NET_CLIENT_LIST);
+ if (l) {
+ printfdf("(): Retrieved window stack from _NET_CLIENT_LIST.");
+ return l;
+ }
+
+ // GNOME WM
+ l = wm_get_stack_fromprop(ps, _WIN_CLIENT_LIST);
+ if (l) {
+ printfdf("(): Retrieved window stack from _WIN_CLIENT_LIST.");
+ return l;
+ }
+ }
+
+ // Stupid method
{
- l = dlist_add(l, (void *)((long *)data)[i]);
+ Window *children = NULL;
+ unsigned nchildren = 0;
+ Window rroot = None, rparent = None;
+ if (XQueryTree(ps->dpy, ps->root, &rroot, &rparent,
+ &children, &nchildren) && nchildren && children) {
+ // Fluxbox sets override-redirect on its frame windows,
+ // so we can't skip override-redirect windows.
+ for (int i = 0; i < nchildren; ++i) {
+ Window wid = children[i];
+ Window client = wm_find_client(ps, wid);
+ if (!client && (ps->o.acceptOvRedir || ps->o.acceptWMWin)) {
+ XWindowAttributes attr = { };
+ if (XGetWindowAttributes(ps->dpy, wid, &attr)
+ && ((attr.override_redirect && ps->o.acceptOvRedir)
+ || (!attr.override_redirect && ps->o.acceptWMWin))) {
+ client = wid;
+ }
+ }
+ if (client)
+ l = dlist_add(l, (void *) client);
+ }
+ }
+ sxfree(children);
+ printfdf("(): Retrieved window stack by querying all children.");
}
-
- XFree(data);
-
+
return l;
}
@@ -366,25 +411,21 @@ wm_get_root_pmap(Display *dpy)
return rootpmap;
}
-CARD32
-wm_get_current_desktop(Display *dpy)
-{
- CARD32 desktop = 0;
- unsigned char *data;
- int status, real_format;
- Atom real_type;
- unsigned long items_read, items_left;
-
- status = XGetWindowProperty(dpy, DefaultRootWindow(dpy),
- (WM_PERSONALITY == WM_PERSONALITY_NETWM) ? _NET_CURRENT_DESKTOP : _WIN_WORKSPACE,
- 0L, 1L, False, XA_CARDINAL, &real_type, &real_format,
- &items_read, &items_left, &data);
- if(status != Success)
- return 0;
- if(items_read)
- desktop = ((CARD32*)data)[0];
- XFree(data);
-
+long
+wm_get_current_desktop(session_t *ps) {
+ winprop_t prop = { };
+ long desktop = 0;
+
+ prop = wid_get_prop(ps, ps->root, _NET_CURRENT_DESKTOP,
+ 1, XA_CARDINAL, 0);
+ desktop = winprop_get_int(&prop);
+ free_winprop(&prop);
+ if (!desktop) {
+ prop = wid_get_prop(ps, ps->root, _WIN_WORKSPACE, 1, XA_CARDINAL, 0);
+ desktop = winprop_get_int(&prop);
+ free_winprop(&prop);
+ }
+
return desktop;
}
@@ -445,36 +486,27 @@ wm_get_group_leader(Display *dpy, Window window)
}
void
-wm_use_netwm_fullscreen(Bool b)
-{
- NETWM_HAS_FULLSCREEN = b ? NETWM_HAS_FULLSCREEN : False;
-}
-
-void
-wm_ignore_skip_taskbar(Bool b)
-{
- IGNORE_SKIP_TASKBAR = b;
-}
-
-void
-wm_set_fullscreen(Display *dpy, Window window, int x, int y, unsigned int width, unsigned int height)
-{
- if(WM_PERSONALITY == WM_PERSONALITY_NETWM && NETWM_HAS_FULLSCREEN)
- {
- Atom props[6];
- CARD32 desktop = (CARD32)-1;
+wm_set_fullscreen(session_t *ps, Window window,
+ int x, int y, unsigned width, unsigned height) {
+ Display *dpy = ps->dpy;
+ if (ps->o.useNetWMFullscreen && ps->has_ewmh_fullscreen) {
+ Atom props[] = {
+ _NET_WM_STATE_FULLSCREEN,
+ _NET_WM_STATE_SKIP_TASKBAR,
+ _NET_WM_STATE_SKIP_PAGER,
+ _NET_WM_STATE_ABOVE,
+ _NET_WM_STATE_STICKY,
+ 0,
+ };
+ long desktop = -1L;
- props[0] = _NET_WM_STATE_FULLSCREEN;
- props[1] = _NET_WM_STATE_SKIP_TASKBAR;
- props[2] = _NET_WM_STATE_SKIP_PAGER;
- props[3] = _NET_WM_STATE_ABOVE;
- props[4] = _NET_WM_STATE_STICKY;
- props[5] = 0;
- XChangeProperty(dpy, window, _NET_WM_STATE, XA_ATOM, 32, PropModeReplace, (unsigned char*)props, 5);
- XChangeProperty(dpy, window, _NET_WM_DESKTOP, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&desktop, 1);
+ XChangeProperty(dpy, window, _NET_WM_STATE, XA_ATOM, 32,
+ PropModeReplace, (unsigned char *) props,
+ sizeof(props) / sizeof(props[0]) - 1);
+ XChangeProperty(dpy, window, _NET_WM_DESKTOP, XA_CARDINAL, 32,
+ PropModeReplace, (unsigned char *) &desktop, 1);
}
- else
- {
+ else {
XSetWindowAttributes wattr;
wattr.override_redirect = True;
XChangeWindowAttributes(dpy, window, CWOverrideRedirect, &wattr);
@@ -482,140 +514,84 @@ wm_set_fullscreen(Display *dpy, Window window, int x, int y, unsigned int width,
}
}
-int
-wm_validate_window(Display *dpy, Window win)
-{
- unsigned char *data;
- Atom *atoms;
- int status, real_format;
- Atom real_type;
- unsigned long items_read, items_left, i;
- int result = 1;
-
- if(WM_PERSONALITY == WM_PERSONALITY_NETWM)
- {
- status = XGetWindowProperty(dpy, win, _NET_WM_STATE,
- 0L, 8192L, False, XA_ATOM, &real_type, &real_format,
- &items_read, &items_left, &data);
-
- if(status != Success)
- return 0;
-
- atoms = (Atom *)data;
-
- for(i = 0; result && i < items_read; i++) {
- if(atoms[i] == _NET_WM_STATE_HIDDEN)
- result = 0;
- else if(! IGNORE_SKIP_TASKBAR && atoms[i] == _NET_WM_STATE_SKIP_TASKBAR)
- result = 0;
- else if(atoms[i] == _NET_WM_STATE_SHADED)
- result = 0;
- if(! result)
- break;
- }
- XFree(data);
-
- if(! result)
- return 0;
-
- status = XGetWindowProperty(dpy, win, _NET_WM_WINDOW_TYPE,
- 0L, 1L, False, XA_ATOM, &real_type, &real_format,
- &items_read, &items_left, &data);
- if(status != Success)
- return 1;
-
- atoms = (Atom *)data;
-
- if(items_read && (atoms[0] == _NET_WM_WINDOW_TYPE_DESKTOP || atoms[0] == _NET_WM_WINDOW_TYPE_DOCK))
- result = 0;
-
- XFree(data);
-
- return result;
- } else {
- CARD32 attr;
-
- status = XGetWindowProperty(dpy, win, _WIN_STATE,
- 0L, 1L, False, XA_CARDINAL, &real_type, &real_format,
- &items_read, &items_left, &data);
- if(status != Success || ! items_read)
- {
- if(status == Success)
- XFree(data);
- return 0;
+bool
+wm_validate_window(session_t *ps, Window wid) {
+ winprop_t prop = { };
+ bool result = true;
+
+ // Check _NET_WM_WINDOW_TYPE
+ prop = wid_get_prop(ps, wid, _NET_WM_WINDOW_TYPE, 1, XA_ATOM, 32);
+ long v = winprop_get_int(&prop);
+ if ((_NET_WM_WINDOW_TYPE_DESKTOP == v
+ || _NET_WM_WINDOW_TYPE_DOCK == v))
+ result = false;
+ free_winprop(&prop);
+
+ if (!result) return result;
+
+ if (WMPSN_EWMH == ps->wmpsn) {
+ // Check _NET_WM_STATE
+ prop = wid_get_prop(ps, wid, _NET_WM_STATE, 8192, XA_ATOM, 32);
+ for (int i = 0; result && i < prop.nitems; i++) {
+ long v = prop.data32[i];
+ if (_NET_WM_STATE_HIDDEN == v)
+ result = false;
+ else if (ps->o.ignoreSkipTaskbar
+ && _NET_WM_STATE_SKIP_TASKBAR == v)
+ result = false;
+ else if (_NET_WM_STATE_SHADED == v)
+ result = false;
}
- attr = (((CARD32*)data)[0]) & (WIN_STATE_MINIMIZED |
- WIN_STATE_SHADED |
- WIN_STATE_HIDDEN);
- if(attr)
- result = 0;
- XFree(data);
- if(! result)
- return 0;
-
- if(! IGNORE_SKIP_TASKBAR)
- {
- status = XGetWindowProperty(dpy, win, _WIN_HINTS,
- 0L, 1L, False, XA_CARDINAL, &real_type, &real_format,
- &items_read, &items_left, &data);
- if(status != Success || ! items_read)
- {
- if(status == Success)
- XFree(data);
- return 1; /* If there's no _WIN_HINTS, assume it's 0, thus valid */
- }
- attr = ((CARD32*)data)[0];
- if(attr & WIN_HINTS_SKIP_TASKBAR)
- result = 0;
- XFree(data);
+ free_winprop(&prop);
+
+ }
+ else if (WMPSN_GNOME == ps->wmpsn) {
+ // Check _WIN_STATE
+ prop = wid_get_prop(ps, wid, _WIN_STATE, 1, XA_CARDINAL, 0);
+ if (winprop_get_int(&prop)
+ & (WIN_STATE_MINIMIZED | WIN_STATE_SHADED | WIN_STATE_HIDDEN))
+ result = false;
+ free_winprop(&prop);
+
+ if (result && ps->o.ignoreSkipTaskbar) {
+ prop = wid_get_prop(ps, wid, _WIN_HINTS, 1, XA_CARDINAL, 0);
+ if (winprop_get_int(&prop) & WIN_HINTS_SKIP_TASKBAR)
+ result = false;
+ free_winprop(&prop);
}
-
- return result;
}
+
+ return result;
}
-CARD32
-wm_get_window_desktop(Display *dpy, Window win)
-{
- int status, real_format;
- Atom real_type;
- unsigned long items_read, items_left;
- unsigned char *data;
- CARD32 desktop = 0;
-
- if(WM_PERSONALITY == WM_PERSONALITY_GNOME)
- {
- status = XGetWindowProperty(dpy, win, _WIN_STATE,
- 0L, 1L, False, XA_CARDINAL, &real_type, &real_format,
- &items_read, &items_left, &data);
- if(status == Success)
- {
- if(items_read)
- desktop = (((CARD32*)data)[0] & WIN_STATE_STICKY) ? (CARD32)-1 : 0;
-
- XFree(data);
-
- if(desktop)
- return desktop;
- }
+long
+wm_get_window_desktop(session_t *ps, Window wid) {
+ long desktop = LONG_MIN;
+ winprop_t prop = { };
+
+ // Check for sticky window
+ if (WMPSN_GNOME == ps->wmpsn) {
+ prop = wid_get_prop(ps, wid, _WIN_STATE, 1, XA_CARDINAL, 0);
+ if (WIN_STATE_STICKY & winprop_get_int(&prop))
+ desktop = -1;
+ free_winprop(&prop);
+ if (LONG_MIN != desktop)
+ return desktop;
}
-
- status = XGetWindowProperty(dpy, win,
- (WM_PERSONALITY == WM_PERSONALITY_NETWM) ? _NET_WM_DESKTOP : _WIN_WORKSPACE,
- 0L, 1L, False, XA_CARDINAL, &real_type, &real_format,
- &items_read, &items_left, &data);
-
- if(status != Success)
- return wm_get_current_desktop(dpy);
-
- if(items_read)
- desktop = ((CARD32*)data)[0];
- else
- desktop = wm_get_current_desktop(dpy);
-
- XFree(data);
-
- return desktop;
+
+ prop = wid_get_prop(ps, wid, _NET_WM_DESKTOP, 1, XA_CARDINAL, 0);
+ if (prop.nitems)
+ desktop = winprop_get_int(&prop);
+ free_winprop(&prop);
+ if (LONG_MIN != desktop) return desktop;
+
+ prop = wid_get_prop(ps, wid, _WIN_WORKSPACE, 1, XA_CARDINAL, 0);
+ if (prop.nitems)
+ desktop = winprop_get_int(&prop);
+ free_winprop(&prop);
+ if (LONG_MIN != desktop) return desktop;
+
+ return wm_get_current_desktop(ps);
}
/* Get focused window and traverse towards the root window until a window with WM_STATE is found */
@@ -772,7 +748,7 @@ wm_send_clientmsg(session_t *ps, Window twid, Window wid, Atom msg_type,
* @param wid window ID
* @return window ID of the frame window
*/
-static Window
+Window
wm_find_frame(session_t *ps, Window wid) {
// We traverse through its ancestors to find out the frame
for (Window cwid = wid; cwid && cwid != ps->root; ) {
@@ -789,3 +765,49 @@ wm_find_frame(session_t *ps, Window wid) {
return wid;
}
+/**
+ * Get a specific attribute of a window.
+ *
+ * Returns a blank structure if the returned type and format does not
+ * match the requested type and format.
+ *
+ * @param ps current session
+ * @param w window
+ * @param atom atom of attribute to fetch
+ * @param length length to read
+ * @param rtype atom of the requested type
+ * @param rformat requested format
+ * @return a <code>winprop_t</code> structure containing the attribute
+ * and number of items. A blank one on failure.
+ */
+winprop_t
+wid_get_prop_adv(const session_t *ps, Window w, Atom atom, long offset,
+ long length, Atom rtype, int rformat) {
+ Atom type = None;
+ int format = 0;
+ unsigned long nitems = 0, after = 0;
+ unsigned char *data = NULL;
+
+ if (Success == XGetWindowProperty(ps->dpy, w, atom, offset, length,
+ False, rtype, &type, &format, &nitems, &after, &data)
+ && nitems && (AnyPropertyType == type || type == rtype)
+ && (!rformat || format == rformat)
+ && (8 == format || 16 == format || 32 == format)) {
+ return (winprop_t) {
+ .data8 = data,
+ .nitems = nitems,
+ .type = type,
+ .format = format,
+ };
+ }
+
+ sxfree(data);
+
+ return (winprop_t) {
+ .data8 = NULL,
+ .nitems = 0,
+ .type = AnyPropertyType,
+ .format = 0
+ };
+}
+
diff --git a/src/wm.h b/src/wm.h
index 8bc3aac..88fb124 100644
--- a/src/wm.h
+++ b/src/wm.h
@@ -40,19 +40,50 @@ extern Atom
_NET_WM_STATE,
_NET_WM_STATE_SHADED;
+/// Structure representing Window property value.
+typedef struct {
+ // All pointers have the same length, right?
+ union {
+ unsigned char *data8;
+ short *data16;
+ long *data32;
+ };
+ unsigned long nitems;
+ Atom type;
+ int format;
+} winprop_t;
+
void wm_get_atoms(session_t *ps);
-char wm_check(Display *dpy);
-void wm_use_netwm_fullscreen(Bool b);
-dlist *wm_get_stack(Display *dpy);
+
+bool wm_check_netwm(session_t *ps);
+bool wm_check_gnome(session_t *ps);
+static inline bool
+wm_check(session_t *ps) {
+ if (wm_check_netwm(ps)) {
+ ps->wmpsn = WMPSN_EWMH;
+ printfdf("(): Your WM looks EWMH compliant.");
+ return true;
+ }
+ if (wm_check_gnome(ps)) {
+ ps->wmpsn = WMPSN_GNOME;
+ printfdf("(): Your WM looks GNOME compliant.");
+ return true;
+ }
+ printfef("(): Your WM is neither EWMH nor GNOME WM compliant. "
+ "Troubles ahead.");
+ return false;
+}
+
+dlist *wm_get_stack(session_t *ps);
Pixmap wm_get_root_pmap(Display *dpy);
-CARD32 wm_get_current_desktop(Display *dpy);
+long wm_get_current_desktop(session_t *ps);
FcChar8 *wm_get_window_title(session_t *ps, Window wid, int *length_return);
Window wm_get_group_leader(Display *dpy, Window window);
-void wm_set_fullscreen(Display *dpy, Window window, int x, int y, unsigned int width, unsigned int height);
-int wm_validate_window(Display *dpy, Window win);
-CARD32 wm_get_window_desktop(Display *dpy, Window win);
+void wm_set_fullscreen(session_t *ps, Window window,
+ int x, int y, unsigned width, unsigned height);
+bool wm_validate_window(session_t *ps, Window wid);
+long wm_get_window_desktop(session_t *ps, Window wid);
Window wm_get_focused(Display *dpy);
-void wm_ignore_skip_taskbar(Bool b);
char *wm_wid_get_prop_rstr(session_t *ps, Window wid, Atom prop);
char *wm_wid_get_prop_utf8(session_t *ps, Window wid, Atom prop);
@@ -97,4 +128,80 @@ wm_shade_window_ewmh(session_t *ps, Window wid) {
sizeof(data) / sizeof(data[0]), data);
}
+Window wm_find_frame(session_t *ps, Window wid);
+
+/**
+ * Determine if a window has a specific property.
+ *
+ * @param ps current session
+ * @param w window to check
+ * @param atom atom of property to check
+ * @return 1 if it has the attribute, 0 otherwise
+ */
+static inline bool
+wid_has_prop(const session_t *ps, Window w, Atom atom) {
+ Atom type = None;
+ int format;
+ unsigned long nitems, after;
+ unsigned char *data;
+
+ if (Success == XGetWindowProperty(ps->dpy, w, atom, 0, 0, False,
+ AnyPropertyType, &type, &format, &nitems, &after, &data)) {
+ sxfree(data);
+ if (type) return true;
+ }
+
+ return false;
+}
+
+winprop_t
+wid_get_prop_adv(const session_t *ps, Window w, Atom atom, long offset,
+ long length, Atom rtype, int rformat);
+
+/**
+ * Wrapper of wid_get_prop_adv().
+ */
+static inline winprop_t
+wid_get_prop(const session_t *ps, Window wid, Atom atom, long length,
+ Atom rtype, int rformat) {
+ return wid_get_prop_adv(ps, wid, atom, 0L, length, rtype, rformat);
+}
+
+/**
+ * Get the numeric property value from a win_prop_t.
+ */
+static inline long
+winprop_get_int(const winprop_t *pprop) {
+ long tgt = 0;
+
+ if (!pprop->nitems)
+ return 0;
+
+ switch (pprop->format) {
+ case 8: tgt = *(pprop->data8); break;
+ case 16: tgt = *(pprop->data16); break;
+ case 32: tgt = *(pprop->data32); break;
+ default: assert(0);
+ break;
+ }
+
+ return tgt;
+}
+
+bool
+wid_get_text_prop(session_t *ps, Window wid, Atom prop,
+ char ***pstrlst, int *pnstr);
+
+/**
+ * Free a <code>winprop_t</code>.
+ *
+ * @param pprop pointer to the <code>winprop_t</code> to free.
+ */
+static inline void
+free_winprop(winprop_t *pprop) {
+ // Empty the whole structure to avoid possible issues
+ spxfree(&pprop->data8);
+ pprop->nitems = 0;
+}
+
#endif /* SKIPPY_WM_H */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment