Krolyxon
Commit 1449bfc

Branches

Refs →

nsxivwindow.c

543 lines · 14.4 KB · C

1/* Copyright 2011-2013 Bert Muennich
2 *
3 * This file is a part of nsxiv.
4 *
5 * nsxiv is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published
7 * by the Free Software Foundation; either version 2 of the License,
8 * or (at your option) any later version.
9 *
10 * nsxiv is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with nsxiv. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "nsxiv.h"
20#define _WINDOW_CONFIG
21#include "config.h"
22#include "icon/data.h"
23
24#include <stdlib.h>
25#include <string.h>
26#include <locale.h>
27#include <unistd.h>
28#include <X11/cursorfont.h>
29#include <X11/Xatom.h>
30#include <X11/Xresource.h>
31
32#if HAVE_LIBFONTS
33#include "utf8.h"
34static XftFont *font;
35static double fontsize;
36#define TEXTWIDTH(win, text, len) \
37 win_draw_text(win, NULL, NULL, 0, 0, text, len, 0)
38#endif
39
40#define RES_CLASS "Nsxiv"
41
42#define INIT_ATOM_(atom) \
43 atoms[ATOM_##atom] = XInternAtom(e->dpy, #atom, False);
44
45enum {
46 H_TEXT_PAD = 5,
47 V_TEXT_PAD = 1
48};
49
50static struct {
51 int name;
52 Cursor icon;
53} cursors[CURSOR_COUNT] = {
54 { XC_left_ptr }, { XC_dotbox }, { XC_watch },
55 { XC_sb_left_arrow }, { XC_sb_right_arrow }
56};
57
58static GC gc;
59
60static int barheight;
61
62Atom atoms[ATOM_COUNT];
63
64#if HAVE_LIBFONTS
65void win_init_font(const win_env_t *e, const char *fontstr)
66{
67 int fontheight = 0;
68 if ((font = XftFontOpenName(e->dpy, e->scr, fontstr)) == NULL)
69 error(EXIT_FAILURE, 0, "Error loading font '%s'", fontstr);
70 fontheight = font->ascent + font->descent;
71 FcPatternGetDouble(font->pattern, FC_SIZE, 0, &fontsize);
72 barheight = fontheight + 2 * V_TEXT_PAD;
73 XftFontClose(e->dpy, font);
74}
75
76void xft_alloc_color(const win_env_t *e, const char *name, XftColor *col)
77{
78 if (!XftColorAllocName(e->dpy, e->vis, e->cmap, name, col))
79 error(EXIT_FAILURE, 0, "Error allocating color '%s'", name);
80}
81#endif /* HAVE_LIBFONTS */
82
83void win_alloc_color(const win_env_t *e, const char *name, unsigned long *pixel)
84{
85 XColor screen, exact;
86 if (!XAllocNamedColor(e->dpy, e->cmap, name, &screen, &exact))
87 error(EXIT_FAILURE, 0, "Error allocating color '%s'", name);
88 *pixel = exact.pixel;
89}
90
91const char* win_res(XrmDatabase db, const char *name, const char *def)
92{
93 char *type;
94 XrmValue ret;
95
96 if (db != None &&
97 XrmGetResource(db, name, name, &type, &ret) &&
98 STREQ(type, "String"))
99 {
100 return ret.addr;
101 } else {
102 return def;
103 }
104}
105
106void win_init(win_t *win)
107{
108 win_env_t *e;
109 const char *win_bg, *win_fg, *mrk_fg;
110#if HAVE_LIBFONTS
111 const char *bar_fg, *bar_bg, *f;
112#endif
113 char *res_man;
114 XrmDatabase db;
115 XVisualInfo vis;
116 XWindowAttributes attr;
117 Window parent;
118
119 memset(win, 0, sizeof(win_t));
120
121 e = &win->env;
122 if ((e->dpy = XOpenDisplay(NULL)) == NULL)
123 error(EXIT_FAILURE, 0, "Error opening X display");
124
125 e->scr = DefaultScreen(e->dpy);
126 e->scrw = DisplayWidth(e->dpy, e->scr);
127 e->scrh = DisplayHeight(e->dpy, e->scr);
128
129 parent = options->embed != 0 ? options->embed : RootWindow(e->dpy, e->scr);
130
131 if (options->embed == 0) {
132 e->depth = DefaultDepth(e->dpy, e->scr);
133 } else {
134 XGetWindowAttributes(e->dpy, parent, &attr);
135 e->depth = attr.depth;
136 }
137
138 XMatchVisualInfo(e->dpy, e->scr, e->depth, TrueColor, &vis);
139 e->vis = vis.visual;
140 e->cmap = XCreateColormap(e->dpy, parent, e->vis, None);
141
142 if (setlocale(LC_CTYPE, "") == NULL || XSupportsLocale() == 0)
143 error(0, 0, "No locale support");
144
145 XrmInitialize();
146 res_man = XResourceManagerString(e->dpy);
147 db = res_man != NULL ? XrmGetStringDatabase(res_man) : None;
148
149 win_bg = win_res(db, RES_CLASS ".window.background", "white");
150 win_fg = win_res(db, RES_CLASS ".window.foreground", "black");
151 mrk_fg = win_res(db, RES_CLASS ".mark.foreground", win_fg);
152 win_alloc_color(e, win_bg, &win->win_bg);
153 win_alloc_color(e, win_fg, &win->win_fg);
154 win_alloc_color(e, mrk_fg, &win->mrk_fg);
155
156#if HAVE_LIBFONTS
157 bar_bg = win_res(db, RES_CLASS ".bar.background", win_bg);
158 bar_fg = win_res(db, RES_CLASS ".bar.foreground", win_fg);
159 xft_alloc_color(e, bar_bg, &win->bar_bg);
160 xft_alloc_color(e, bar_fg, &win->bar_fg);
161
162 f = win_res(db, RES_CLASS ".bar.font", "monospace-8");
163 win_init_font(e, f);
164
165 win->bar.l.size = BAR_L_LEN;
166 win->bar.r.size = BAR_R_LEN;
167 /* 3 padding bytes needed by utf8_decode */
168 win->bar.l.buf = emalloc(win->bar.l.size + 3);
169 win->bar.l.buf[0] = '\0';
170 win->bar.r.buf = emalloc(win->bar.r.size + 3);
171 win->bar.r.buf[0] = '\0';
172 win->bar.h = options->hide_bar ? 0 : barheight;
173#endif /* HAVE_LIBFONTS */
174
175 INIT_ATOM_(WM_DELETE_WINDOW);
176 INIT_ATOM_(_NET_WM_NAME);
177 INIT_ATOM_(_NET_WM_ICON_NAME);
178 INIT_ATOM_(_NET_WM_ICON);
179 INIT_ATOM_(_NET_WM_STATE);
180 INIT_ATOM_(_NET_WM_PID);
181 INIT_ATOM_(_NET_WM_STATE_FULLSCREEN);
182}
183
184void win_open(win_t *win)
185{
186 int c, i, j, n;
187 long parent;
188 win_env_t *e;
189 XClassHint classhint;
190 unsigned long *icon_data;
191 XColor col;
192 Cursor *cnone = &cursors[CURSOR_NONE].icon;
193 char none_data[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
194 Pixmap none;
195 int gmask;
196 XSizeHints sizehints;
197 XWMHints hints;
198 pid_t pid;
199 char hostname[256];
200
201 e = &win->env;
202 parent = options->embed != 0 ? options->embed : RootWindow(e->dpy, e->scr);
203
204 sizehints.flags = PWinGravity;
205 sizehints.win_gravity = NorthWestGravity;
206
207 /* determine window offsets, width & height */
208 if (options->geometry == NULL)
209 gmask = 0;
210 else
211 gmask = XParseGeometry(options->geometry, &win->x, &win->y,
212 &win->w, &win->h);
213 if ((gmask & WidthValue) != 0)
214 sizehints.flags |= USSize;
215 else
216 win->w = WIN_WIDTH;
217 if ((gmask & HeightValue) != 0)
218 sizehints.flags |= USSize;
219 else
220 win->h = WIN_HEIGHT;
221 if ((gmask & XValue) != 0) {
222 if ((gmask & XNegative) != 0) {
223 win->x += e->scrw - win->w;
224 sizehints.win_gravity = NorthEastGravity;
225 }
226 sizehints.flags |= USPosition;
227 } else {
228 win->x = 0;
229 }
230 if ((gmask & YValue) != 0) {
231 if ((gmask & YNegative) != 0) {
232 win->y += e->scrh - win->h;
233 sizehints.win_gravity = sizehints.win_gravity == NorthEastGravity
234 ? SouthEastGravity : SouthWestGravity;
235 }
236 sizehints.flags |= USPosition;
237 } else {
238 win->y = 0;
239 }
240
241 win->xwin = XCreateWindow(e->dpy, parent, win->x, win->y, win->w, win->h, 0,
242 e->depth, InputOutput, e->vis, 0, NULL);
243 if (win->xwin == None)
244 error(EXIT_FAILURE, 0, "Error creating X window");
245
246 /* set the _NET_WM_PID */
247 pid = getpid();
248 XChangeProperty(e->dpy, win->xwin, atoms[ATOM__NET_WM_PID], XA_CARDINAL,
249 sizeof(pid_t) * 8, PropModeReplace, (unsigned char *) &pid, 1);
250 if (gethostname(hostname, ARRLEN(hostname)) == 0) {
251 XTextProperty tp;
252 tp.value = (unsigned char *)hostname;
253 tp.nitems = strnlen(hostname, ARRLEN(hostname));
254 tp.encoding = XA_STRING;
255 tp.format = 8;
256 XSetWMClientMachine(e->dpy, win->xwin, &tp);
257 }
258
259 XSelectInput(e->dpy, win->xwin,
260 ButtonReleaseMask | ButtonPressMask | KeyPressMask |
261 PointerMotionMask | StructureNotifyMask);
262
263 for (i = 0; i < ARRLEN(cursors); i++) {
264 if (i != CURSOR_NONE)
265 cursors[i].icon = XCreateFontCursor(e->dpy, cursors[i].name);
266 }
267 if (XAllocNamedColor(e->dpy, e->cmap, "black", &col, &col) == 0)
268 error(EXIT_FAILURE, 0, "Error allocating color 'black'");
269
270 none = XCreateBitmapFromData(e->dpy, win->xwin, none_data, 8, 8);
271 *cnone = XCreatePixmapCursor(e->dpy, none, none, &col, &col, 0, 0);
272
273 gc = XCreateGC(e->dpy, win->xwin, 0, None);
274
275 n = icons[ARRLEN(icons)-1].size;
276 icon_data = emalloc((n * n + 2) * sizeof(*icon_data));
277
278 for (i = 0; i < ARRLEN(icons); i++) {
279 n = 0;
280 icon_data[n++] = icons[i].size;
281 icon_data[n++] = icons[i].size;
282
283 for (j = 0; j < icons[i].cnt; j++) {
284 for (c = icons[i].data[j] >> 4; c >= 0; c--)
285 icon_data[n++] = icon_colors[icons[i].data[j] & 0x0F];
286 }
287 XChangeProperty(e->dpy, win->xwin, atoms[ATOM__NET_WM_ICON], XA_CARDINAL, 32,
288 i == 0 ? PropModeReplace : PropModeAppend,
289 (unsigned char *) icon_data, n);
290 }
291 free(icon_data);
292
293 /* These two atoms won't change and thus only need to be set once. */
294 XStoreName(win->env.dpy, win->xwin, "nsxiv");
295 XSetIconName(win->env.dpy, win->xwin, "nsxiv");
296
297 classhint.res_class = RES_CLASS;
298 classhint.res_name = options->res_name != NULL ? options->res_name : "nsxiv";
299 XSetClassHint(e->dpy, win->xwin, &classhint);
300
301 XSetWMProtocols(e->dpy, win->xwin, &atoms[ATOM_WM_DELETE_WINDOW], 1);
302
303 sizehints.width = win->w;
304 sizehints.height = win->h;
305 sizehints.x = win->x;
306 sizehints.y = win->y;
307 XSetWMNormalHints(win->env.dpy, win->xwin, &sizehints);
308
309 hints.flags = InputHint | StateHint;
310 hints.input = 1;
311 hints.initial_state = NormalState;
312 XSetWMHints(win->env.dpy, win->xwin, &hints);
313
314 win->h -= win->bar.h;
315
316 win->buf.w = e->scrw;
317 win->buf.h = e->scrh;
318 win->buf.pm = XCreatePixmap(e->dpy, win->xwin, win->buf.w, win->buf.h, e->depth);
319
320 XSetForeground(e->dpy, gc, win->win_bg);
321 XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h);
322 XSetWindowBackgroundPixmap(e->dpy, win->xwin, win->buf.pm);
323 XMapWindow(e->dpy, win->xwin);
324 XFlush(e->dpy);
325
326 if (options->fullscreen)
327 win_toggle_fullscreen(win);
328}
329
330CLEANUP void win_close(win_t *win)
331{
332 int i;
333
334 for (i = 0; i < ARRLEN(cursors); i++)
335 XFreeCursor(win->env.dpy, cursors[i].icon);
336
337 XFreeGC(win->env.dpy, gc);
338
339 XDestroyWindow(win->env.dpy, win->xwin);
340 XCloseDisplay(win->env.dpy);
341}
342
343bool win_configure(win_t *win, XConfigureEvent *c)
344{
345 bool changed;
346
347 changed = win->w != c->width || win->h + win->bar.h != c->height;
348
349 win->x = c->x;
350 win->y = c->y;
351 win->w = c->width;
352 win->h = c->height - win->bar.h;
353 win->bw = c->border_width;
354
355 return changed;
356}
357
358void win_toggle_fullscreen(win_t *win)
359{
360 XEvent ev;
361 XClientMessageEvent *cm;
362
363 memset(&ev, 0, sizeof(ev));
364 ev.type = ClientMessage;
365
366 cm = &ev.xclient;
367 cm->window = win->xwin;
368 cm->message_type = atoms[ATOM__NET_WM_STATE];
369 cm->format = 32;
370 cm->data.l[0] = 2; // toggle
371 cm->data.l[1] = atoms[ATOM__NET_WM_STATE_FULLSCREEN];
372
373 XSendEvent(win->env.dpy, DefaultRootWindow(win->env.dpy), False,
374 SubstructureNotifyMask | SubstructureRedirectMask, &ev);
375}
376
377void win_toggle_bar(win_t *win)
378{
379 if (win->bar.h != 0) {
380 win->h += win->bar.h;
381 win->bar.h = 0;
382 } else {
383 win->bar.h = barheight;
384 win->h -= win->bar.h;
385 }
386}
387
388void win_clear(win_t *win)
389{
390 win_env_t *e;
391
392 e = &win->env;
393
394 if (win->w > win->buf.w || win->h + win->bar.h > win->buf.h) {
395 XFreePixmap(e->dpy, win->buf.pm);
396 win->buf.w = MAX(win->buf.w, win->w);
397 win->buf.h = MAX(win->buf.h, win->h + win->bar.h);
398 win->buf.pm = XCreatePixmap(e->dpy, win->xwin,
399 win->buf.w, win->buf.h, e->depth);
400 }
401 XSetForeground(e->dpy, gc, win->win_bg);
402 XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h);
403}
404
405#if HAVE_LIBFONTS
406int win_draw_text(win_t *win, XftDraw *d, const XftColor *color, int x, int y,
407 char *text, int len, int w)
408{
409 int err, tw = 0;
410 char *t, *next;
411 uint32_t rune;
412 XftFont *f;
413 FcCharSet *fccharset;
414 XGlyphInfo ext;
415
416 for (t = text; t - text < len; t = next) {
417 next = utf8_decode(t, &rune, &err);
418 if (XftCharExists(win->env.dpy, font, rune)) {
419 f = font;
420 } else { /* fallback font */
421 fccharset = FcCharSetCreate();
422 FcCharSetAddChar(fccharset, rune);
423 f = XftFontOpen(win->env.dpy, win->env.scr, FC_CHARSET, FcTypeCharSet,
424 fccharset, FC_SCALABLE, FcTypeBool, FcTrue,
425 FC_SIZE, FcTypeDouble, fontsize, NULL);
426 FcCharSetDestroy(fccharset);
427 }
428 XftTextExtentsUtf8(win->env.dpy, f, (XftChar8*)t, next - t, &ext);
429 tw += ext.xOff;
430 if (tw <= w) {
431 XftDrawStringUtf8(d, color, f, x, y, (XftChar8*)t, next - t);
432 x += ext.xOff;
433 }
434 if (f != font)
435 XftFontClose(win->env.dpy, f);
436 }
437 return tw;
438}
439
440void win_draw_bar(win_t *win)
441{
442 int len, x, y, w, tw;
443 win_env_t *e;
444 win_bar_t *l, *r;
445 XftDraw *d;
446
447 if ((l = &win->bar.l)->buf == NULL || (r = &win->bar.r)->buf == NULL)
448 return;
449
450 e = &win->env;
451 y = win->h + font->ascent + V_TEXT_PAD;
452 w = win->w - 2*H_TEXT_PAD;
453 d = XftDrawCreate(e->dpy, win->buf.pm, e->vis, e->cmap);
454
455 XSetForeground(e->dpy, gc, win->bar_bg.pixel);
456 XFillRectangle(e->dpy, win->buf.pm, gc, 0, win->h, win->w, win->bar.h);
457
458 XSetForeground(e->dpy, gc, win->win_bg);
459 XSetBackground(e->dpy, gc, win->bar_bg.pixel);
460
461 if ((len = strlen(r->buf)) > 0) {
462 if ((tw = TEXTWIDTH(win, r->buf, len)) > w)
463 return;
464 x = win->w - tw - H_TEXT_PAD;
465 w -= tw;
466 win_draw_text(win, d, &win->bar_fg, x, y, r->buf, len, tw);
467 }
468 if ((len = strlen(l->buf)) > 0) {
469 x = H_TEXT_PAD;
470 w -= 2 * H_TEXT_PAD; /* gap between left and right parts */
471 win_draw_text(win, d, &win->bar_fg, x, y, l->buf, len, w);
472 }
473 XftDrawDestroy(d);
474}
475#else
476void win_draw_bar(win_t *win){}
477#endif /* HAVE_LIBFONTS */
478
479void win_draw(win_t *win)
480{
481 if (win->bar.h > 0)
482 win_draw_bar(win);
483
484 XSetWindowBackgroundPixmap(win->env.dpy, win->xwin, win->buf.pm);
485 XClearWindow(win->env.dpy, win->xwin);
486 XFlush(win->env.dpy);
487}
488
489void win_draw_rect(win_t *win, int x, int y, int w, int h, bool fill, int lw,
490 unsigned long col)
491{
492 XGCValues gcval;
493
494 gcval.line_width = lw;
495 gcval.foreground = col;
496 XChangeGC(win->env.dpy, gc, GCForeground | GCLineWidth, &gcval);
497
498 if (fill)
499 XFillRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h);
500 else
501 XDrawRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h);
502}
503
504void win_set_title(win_t *win, const char *path)
505{
506 enum { title_max = 512 };
507 char title[title_max];
508 const char *basename = strrchr(path, '/') + 1;
509
510 /* Return if window is not ready yet */
511 if (win->xwin == None)
512 return;
513
514 snprintf(title, title_max, "%s%s", options->title_prefix,
515 options->title_suffixmode == SUFFIX_BASENAME ? basename : path);
516 if (options->title_suffixmode == SUFFIX_EMPTY)
517 *(title+strlen(options->title_prefix)) = '\0';
518
519 XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_NAME],
520 XInternAtom(win->env.dpy, "UTF8_STRING", False), 8,
521 PropModeReplace, (unsigned char *) title, strlen(title));
522 XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_ICON_NAME],
523 XInternAtom(win->env.dpy, "UTF8_STRING", False), 8,
524 PropModeReplace, (unsigned char *) title, strlen(title));
525}
526
527void win_set_cursor(win_t *win, cursor_t cursor)
528{
529 if (cursor >= 0 && cursor < ARRLEN(cursors)) {
530 XDefineCursor(win->env.dpy, win->xwin, cursors[cursor].icon);
531 XFlush(win->env.dpy);
532 }
533}
534
535void win_cursor_pos(win_t *win, int *x, int *y)
536{
537 int i;
538 unsigned int ui;
539 Window w;
540
541 if (!XQueryPointer(win->env.dpy, win->xwin, &w, &w, &i, &i, x, y, &ui))
542 *x = *y = 0;
543}