Last active
December 18, 2015 00:19
-
-
Save richardgv/5696281 to your computer and use it in GitHub Desktop.
richardgv/skippy-xd #10: Possible fix & other WIP things
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(®2, ®, &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