Krolyxon
Commit e2fa49e

Branches

Refs →

nsxivwindow.c

580 lines · 14.0 KB · C

1/* Copyright 2011-2013 Bert Muennich
2 *
3 * This file is part of sxiv.
4 *
5 * sxiv 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 * sxiv 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 sxiv. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#define _POSIX_C_SOURCE 200112L
20#define _WINDOW_CONFIG
21
22#include <stdlib.h>
23#include <string.h>
24#include <locale.h>
25#include <X11/cursorfont.h>
26#include <X11/Xatom.h>
27
28#include "options.h"
29#include "util.h"
30#include "window.h"
31#include "config.h"
32#include "icon/data.h"
33
34enum {
35 H_TEXT_PAD = 5,
36 V_TEXT_PAD = 1
37};
38
39static Cursor carrow;
40static Cursor cnone;
41static Cursor chand;
42static Cursor cwatch;
43static GC gc;
44
45static struct {
46 int ascent;
47 int descent;
48 XFontStruct *xfont;
49 XFontSet set;
50} font;
51
52static int fontheight;
53static int barheight;
54
55Atom atoms[ATOM_COUNT];
56
57void win_init_font(Display *dpy, const char *fontstr)
58{
59 int n;
60 char *def, **missing;
61
62 font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def);
63 if (missing)
64 XFreeStringList(missing);
65 if (font.set) {
66 XFontStruct **xfonts;
67 char **font_names;
68
69 font.ascent = font.descent = 0;
70 XExtentsOfFontSet(font.set);
71 n = XFontsOfFontSet(font.set, &xfonts, &font_names);
72 while (n--) {
73 font.ascent = MAX(font.ascent, (*xfonts)->ascent);
74 font.descent = MAX(font.descent,(*xfonts)->descent);
75 xfonts++;
76 }
77 } else {
78 if ((font.xfont = XLoadQueryFont(dpy, fontstr)) == NULL &&
79 (font.xfont = XLoadQueryFont(dpy, "fixed")) == NULL)
80 {
81 die("could not load font: %s", fontstr);
82 }
83 font.ascent = font.xfont->ascent;
84 font.descent = font.xfont->descent;
85 }
86 fontheight = font.ascent + font.descent;
87 barheight = fontheight + 2 * V_TEXT_PAD;
88}
89
90unsigned long win_alloc_color(win_t *win, const char *name)
91{
92 XColor col;
93
94 if (win == NULL)
95 return 0UL;
96 if (XAllocNamedColor(win->env.dpy,
97 DefaultColormap(win->env.dpy, win->env.scr),
98 name, &col, &col) == 0)
99 {
100 die("could not allocate color: %s", name);
101 }
102 return col.pixel;
103}
104
105#define INIT_ATOM_(atom) \
106 atoms[ATOM_##atom] = XInternAtom(e->dpy, #atom, False);
107
108void win_init(win_t *win)
109{
110 win_env_t *e;
111
112 if (win == NULL)
113 return;
114
115 memset(win, 0, sizeof(win_t));
116
117 e = &win->env;
118 if ((e->dpy = XOpenDisplay(NULL)) == NULL)
119 die("could not open display");
120
121 e->scr = DefaultScreen(e->dpy);
122 e->scrw = DisplayWidth(e->dpy, e->scr);
123 e->scrh = DisplayHeight(e->dpy, e->scr);
124 e->vis = DefaultVisual(e->dpy, e->scr);
125 e->cmap = DefaultColormap(e->dpy, e->scr);
126 e->depth = DefaultDepth(e->dpy, e->scr);
127
128 if (setlocale(LC_CTYPE, "") == NULL || XSupportsLocale() == 0)
129 warn("no locale support");
130
131 win_init_font(e->dpy, BAR_FONT);
132
133 win->white = WhitePixel(e->dpy, e->scr);
134 win->bgcol = win_alloc_color(win, WIN_BG_COLOR);
135 win->fscol = win_alloc_color(win, WIN_FS_COLOR);
136 win->selcol = win_alloc_color(win, SEL_COLOR);
137 win->bar.bgcol = win_alloc_color(win, BAR_BG_COLOR);
138 win->bar.fgcol = win_alloc_color(win, BAR_FG_COLOR);
139 win->bar.h = options->hide_bar ? 0 : barheight;
140
141 win->sizehints.flags = PWinGravity;
142 win->sizehints.win_gravity = NorthWestGravity;
143 if (options->fixed_win)
144 /* actual min/max values set in win_update_sizehints() */
145 win->sizehints.flags |= PMinSize | PMaxSize;
146
147 INIT_ATOM_(WM_DELETE_WINDOW);
148 INIT_ATOM_(_NET_WM_NAME);
149 INIT_ATOM_(_NET_WM_ICON_NAME);
150 INIT_ATOM_(_NET_WM_ICON);
151 INIT_ATOM_(_NET_WM_STATE);
152 INIT_ATOM_(_NET_WM_STATE_FULLSCREEN);
153}
154
155void win_update_sizehints(win_t *win)
156{
157 if (win == NULL || win->xwin == None)
158 return;
159
160 if ((win->sizehints.flags & USSize) != 0) {
161 win->sizehints.width = win->w;
162 win->sizehints.height = win->h + win->bar.h;
163 }
164 if ((win->sizehints.flags & USPosition) != 0) {
165 win->sizehints.x = win->x;
166 win->sizehints.y = win->y;
167 }
168 if (options->fixed_win) {
169 win->sizehints.min_width = win->sizehints.max_width = win->w;
170 win->sizehints.min_height = win->sizehints.max_height = win->h + win->bar.h;
171 }
172 XSetWMNormalHints(win->env.dpy, win->xwin, &win->sizehints);
173}
174
175void win_open(win_t *win)
176{
177 int c, i, j, n;
178 win_env_t *e;
179 XClassHint classhint;
180 XSetWindowAttributes attr;
181 unsigned long attr_mask;
182 unsigned long *icon_data;
183 XColor col;
184 char none_data[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
185 Pixmap none;
186 int gmask;
187
188 if (win == NULL)
189 return;
190
191 e = &win->env;
192
193 /* determine window offsets, width & height */
194 if (options->geometry == NULL)
195 gmask = 0;
196 else
197 gmask = XParseGeometry(options->geometry, &win->x, &win->y,
198 &win->w, &win->h);
199 if ((gmask & WidthValue) != 0)
200 win->sizehints.flags |= USSize;
201 else
202 win->w = WIN_WIDTH;
203 if ((gmask & HeightValue) != 0)
204 win->sizehints.flags |= USSize;
205 else
206 win->h = WIN_HEIGHT;
207 if ((gmask & XValue) != 0) {
208 if ((gmask & XNegative) != 0) {
209 win->x += e->scrw - win->w;
210 win->sizehints.win_gravity = NorthEastGravity;
211 }
212 win->sizehints.flags |= USPosition;
213 } else {
214 win->x = (e->scrw - win->w) / 2;
215 }
216 if ((gmask & YValue) != 0) {
217 if ((gmask & YNegative) != 0) {
218 win->y += e->scrh - win->h;
219 if (win->sizehints.win_gravity == NorthEastGravity)
220 win->sizehints.win_gravity = SouthEastGravity;
221 else
222 win->sizehints.win_gravity = SouthWestGravity;
223 }
224 win->sizehints.flags |= USPosition;
225 } else {
226 win->y = (e->scrh - win->h) / 2;
227 }
228
229 attr.background_pixel = win->bgcol;
230 attr_mask = CWBackPixel;
231
232 win->xwin = XCreateWindow(e->dpy, RootWindow(e->dpy, e->scr),
233 win->x, win->y, win->w, win->h, 0,
234 e->depth, InputOutput, e->vis, attr_mask, &attr);
235 if (win->xwin == None)
236 die("could not create window");
237
238 XSelectInput(e->dpy, win->xwin,
239 ExposureMask | ButtonReleaseMask | ButtonPressMask |
240 KeyPressMask | PointerMotionMask | StructureNotifyMask);
241
242 carrow = XCreateFontCursor(e->dpy, XC_left_ptr);
243 chand = XCreateFontCursor(e->dpy, XC_fleur);
244 cwatch = XCreateFontCursor(e->dpy, XC_watch);
245
246 if (XAllocNamedColor(e->dpy, DefaultColormap(e->dpy, e->scr), "black",
247 &col, &col) == 0)
248 {
249 die("could not allocate color: black");
250 }
251 none = XCreateBitmapFromData(e->dpy, win->xwin, none_data, 8, 8);
252 cnone = XCreatePixmapCursor(e->dpy, none, none, &col, &col, 0, 0);
253
254 gc = XCreateGC(e->dpy, win->xwin, 0, None);
255
256 n = icons[ARRLEN(icons)-1].size;
257 icon_data = s_malloc((n * n + 2) * sizeof(*icon_data));
258
259 for (i = 0; i < ARRLEN(icons); i++) {
260 n = 0;
261 icon_data[n++] = icons[i].size;
262 icon_data[n++] = icons[i].size;
263
264 for (j = 0; j < icons[i].cnt; j++) {
265 for (c = icons[i].data[j] >> 4; c >= 0; c--)
266 icon_data[n++] = icon_colors[icons[i].data[j] & 0x0F];
267 }
268 XChangeProperty(e->dpy, win->xwin,
269 atoms[ATOM__NET_WM_ICON], XA_CARDINAL, 32,
270 i == 0 ? PropModeReplace : PropModeAppend,
271 (unsigned char *) icon_data, n);
272 }
273 free(icon_data);
274
275 win_set_title(win, "sxiv");
276
277 classhint.res_class = "Sxiv";
278 classhint.res_name = options->res_name != NULL ? options->res_name : "sxiv";
279 XSetClassHint(e->dpy, win->xwin, &classhint);
280
281 XSetWMProtocols(e->dpy, win->xwin, &atoms[ATOM_WM_DELETE_WINDOW], 1);
282
283 win->h -= win->bar.h;
284 win_update_sizehints(win);
285
286 XMapWindow(e->dpy, win->xwin);
287 XFlush(e->dpy);
288
289 if (options->fullscreen)
290 win_toggle_fullscreen(win);
291}
292
293void win_close(win_t *win)
294{
295 if (win == NULL || win->xwin == None)
296 return;
297
298 XFreeCursor(win->env.dpy, carrow);
299 XFreeCursor(win->env.dpy, cnone);
300 XFreeCursor(win->env.dpy, chand);
301 XFreeCursor(win->env.dpy, cwatch);
302
303 XFreeGC(win->env.dpy, gc);
304
305 XDestroyWindow(win->env.dpy, win->xwin);
306 XCloseDisplay(win->env.dpy);
307}
308
309bool win_configure(win_t *win, XConfigureEvent *c)
310{
311 bool changed;
312
313 if (win == NULL || c == NULL)
314 return false;
315
316 if ((changed = win->w != c->width || win->h + win->bar.h != c->height)) {
317 if (win->pm != None) {
318 XFreePixmap(win->env.dpy, win->pm);
319 win->pm = None;
320 }
321 }
322
323 win->x = c->x;
324 win->y = c->y;
325 win->w = c->width;
326 win->h = c->height - win->bar.h;
327 win->bw = c->border_width;
328
329 return changed;
330}
331
332void win_expose(win_t *win, XExposeEvent *e)
333{
334 if (win == NULL || win->xwin == None || win->pm == None || e == NULL)
335 return;
336
337 XCopyArea(win->env.dpy, win->pm, win->xwin, gc,
338 e->x, e->y, e->width, e->height, e->x, e->y);
339}
340
341bool win_moveresize(win_t *win, int x, int y, unsigned int w, unsigned int h)
342{
343 if (win == NULL || win->xwin == None)
344 return false;
345
346 /* caller knows nothing about the bar */
347 h += win->bar.h;
348
349 x = MAX(0, x);
350 y = MAX(0, y);
351 w = MIN(w, win->env.scrw - 2 * win->bw);
352 h = MIN(h, win->env.scrh - 2 * win->bw);
353
354 if (win->x == x && win->y == y && win->w == w && win->h + win->bar.h == h)
355 return false;
356
357 win->x = x;
358 win->y = y;
359 win->w = w;
360 win->h = h - win->bar.h;
361
362 win_update_sizehints(win);
363
364 XMoveResizeWindow(win->env.dpy, win->xwin, x, y, w, h);
365
366 if (win->pm != None) {
367 XFreePixmap(win->env.dpy, win->pm);
368 win->pm = None;
369 }
370
371 return true;
372}
373
374void win_toggle_fullscreen(win_t *win)
375{
376 XEvent ev;
377 XClientMessageEvent *cm;
378
379 if (win == NULL || win->xwin == None)
380 return;
381
382 win->fullscreen = !win->fullscreen;
383
384 memset(&ev, 0, sizeof(ev));
385 ev.type = ClientMessage;
386
387 cm = &ev.xclient;
388 cm->window = win->xwin;
389 cm->message_type = atoms[ATOM__NET_WM_STATE];
390 cm->format = 32;
391 cm->data.l[0] = win->fullscreen;
392 cm->data.l[1] = atoms[ATOM__NET_WM_STATE_FULLSCREEN];
393 cm->data.l[2] = cm->data.l[3] = 0;
394
395 XSendEvent(win->env.dpy, DefaultRootWindow(win->env.dpy), False,
396 SubstructureNotifyMask | SubstructureRedirectMask, &ev);
397}
398
399void win_toggle_bar(win_t *win)
400{
401 if (win == NULL || win->xwin == None)
402 return;
403
404 if (win->bar.h != 0) {
405 win->h += win->bar.h;
406 win->bar.h = 0;
407 } else {
408 win->bar.h = barheight;
409 win->h -= win->bar.h;
410 }
411}
412
413void win_clear(win_t *win)
414{
415 int h;
416 win_env_t *e;
417
418 if (win == NULL || win->xwin == None)
419 return;
420
421 h = win->h + win->bar.h;
422 e = &win->env;
423
424 if (win->pm == None)
425 win->pm = XCreatePixmap(e->dpy, win->xwin, win->w, h, e->depth);
426
427 XSetForeground(e->dpy, gc, win->fullscreen ? win->fscol : win->bgcol);
428 XFillRectangle(e->dpy, win->pm, gc, 0, 0, win->w, h);
429}
430
431void win_draw_bar(win_t *win)
432{
433 int len, olen, x, y, w, tw;
434 char rest[3];
435 const char *dots = "...";
436 win_env_t *e;
437
438 if (win == NULL || win->xwin == None || win->pm == None)
439 return;
440
441 e = &win->env;
442 y = win->h + font.ascent + V_TEXT_PAD;
443 w = win->w;
444
445 XSetForeground(e->dpy, gc, win->bar.bgcol);
446 XFillRectangle(e->dpy, win->pm, gc, 0, win->h, win->w, win->bar.h);
447
448 XSetForeground(e->dpy, gc, win->bar.fgcol);
449 XSetBackground(e->dpy, gc, win->bar.bgcol);
450
451 if ((len = strlen(win->bar.r)) > 0) {
452 if ((tw = win_textwidth(win->bar.r, len, true)) > w)
453 return;
454 x = win->w - tw + H_TEXT_PAD;
455 w -= tw;
456 if (font.set)
457 XmbDrawString(e->dpy, win->pm, font.set, gc, x, y, win->bar.r, len);
458 else
459 XDrawString(e->dpy, win->pm, gc, x, y, win->bar.r, len);
460 }
461 if ((len = strlen(win->bar.l)) > 0) {
462 olen = len;
463 while (len > 0 && (tw = win_textwidth(win->bar.l, len, true)) > w)
464 len--;
465 if (len > 0) {
466 if (len != olen) {
467 w = strlen(dots);
468 if (len <= w)
469 return;
470 memcpy(rest, win->bar.l + len - w, w);
471 memcpy(win->bar.l + len - w, dots, w);
472 }
473 x = H_TEXT_PAD;
474 if (font.set)
475 XmbDrawString(e->dpy, win->pm, font.set, gc, x, y, win->bar.l, len);
476 else
477 XDrawString(e->dpy, win->pm, gc, x, y, win->bar.l, len);
478 if (len != olen)
479 memcpy(win->bar.l + len - w, rest, w);
480 }
481 }
482}
483
484void win_draw(win_t *win)
485{
486 if (win == NULL || win->xwin == None || win->pm == None)
487 return;
488
489 if (win->bar.h > 0)
490 win_draw_bar(win);
491
492 XCopyArea(win->env.dpy, win->pm, win->xwin, gc,
493 0, 0, win->w, win->h + win->bar.h, 0, 0);
494}
495
496void win_draw_rect(win_t *win, Pixmap pm, int x, int y, int w, int h,
497 bool fill, int lw, unsigned long col)
498{
499 XGCValues gcval;
500
501 if (win == NULL || pm == None)
502 return;
503
504 gcval.line_width = lw;
505 gcval.foreground = col;
506 XChangeGC(win->env.dpy, gc, GCForeground | GCLineWidth, &gcval);
507
508 if (fill)
509 XFillRectangle(win->env.dpy, pm, gc, x, y, w, h);
510 else
511 XDrawRectangle(win->env.dpy, pm, gc, x, y, w, h);
512}
513
514void win_update_bar(win_t *win)
515{
516 if (win == NULL || win->xwin == None || win->pm == None)
517 return;
518
519 if (win->bar.h > 0) {
520 win_draw_bar(win);
521 XCopyArea(win->env.dpy, win->pm, win->xwin, gc,
522 0, win->h, win->w, win->bar.h, 0, win->h);
523 }
524}
525
526int win_textwidth(const char *text, unsigned int len, bool with_padding)
527{
528 XRectangle r;
529 int padding = with_padding ? 2 * H_TEXT_PAD : 0;
530
531 if (font.set) {
532 XmbTextExtents(font.set, text, len, NULL, &r);
533 return r.width + padding;
534 } else {
535 return XTextWidth(font.xfont, text, len) + padding;
536 }
537}
538
539void win_set_title(win_t *win, const char *title)
540{
541 if (win == NULL || win->xwin == None)
542 return;
543
544 if (title == NULL)
545 title = "sxiv";
546
547 XStoreName(win->env.dpy, win->xwin, title);
548 XSetIconName(win->env.dpy, win->xwin, title);
549
550 XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_NAME],
551 XInternAtom(win->env.dpy, "UTF8_STRING", False), 8,
552 PropModeReplace, (unsigned char *) title, strlen(title));
553 XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_ICON_NAME],
554 XInternAtom(win->env.dpy, "UTF8_STRING", False), 8,
555 PropModeReplace, (unsigned char *) title, strlen(title));
556}
557
558void win_set_cursor(win_t *win, cursor_t cursor)
559{
560 if (win == NULL || win->xwin == None)
561 return;
562
563 switch (cursor) {
564 case CURSOR_NONE:
565 XDefineCursor(win->env.dpy, win->xwin, cnone);
566 break;
567 case CURSOR_HAND:
568 XDefineCursor(win->env.dpy, win->xwin, chand);
569 break;
570 case CURSOR_WATCH:
571 XDefineCursor(win->env.dpy, win->xwin, cwatch);
572 break;
573 case CURSOR_ARROW:
574 default:
575 XDefineCursor(win->env.dpy, win->xwin, carrow);
576 break;
577 }
578
579 XFlush(win->env.dpy);
580}