A Simple X Image Viewer
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 
 
 

522 行
13 KiB

  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. #include <stdlib.h>
  19. #include <string.h>
  20. #include <locale.h>
  21. #include <X11/cursorfont.h>
  22. #include <X11/Xatom.h>
  23. #include "options.h"
  24. #include "util.h"
  25. #include "window.h"
  26. #include "icon/data.h"
  27. #define _WINDOW_CONFIG
  28. #include "config.h"
  29. enum {
  30. H_TEXT_PAD = 5,
  31. V_TEXT_PAD = 1
  32. };
  33. static Cursor carrow;
  34. static Cursor cnone;
  35. static Cursor chand;
  36. static Cursor cwatch;
  37. static GC gc;
  38. static struct {
  39. int ascent;
  40. int descent;
  41. XFontStruct *xfont;
  42. XFontSet set;
  43. } font;
  44. static int fontheight;
  45. static int barheight;
  46. Atom atoms[ATOM_COUNT];
  47. static Bool fs_support;
  48. static Bool fs_warned;
  49. void win_init_font(Display *dpy, const char *fontstr)
  50. {
  51. int n;
  52. char *def, **missing;
  53. font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def);
  54. if (missing)
  55. XFreeStringList(missing);
  56. if (font.set) {
  57. XFontStruct **xfonts;
  58. char **font_names;
  59. font.ascent = font.descent = 0;
  60. XExtentsOfFontSet(font.set);
  61. n = XFontsOfFontSet(font.set, &xfonts, &font_names);
  62. while (n--) {
  63. font.ascent = MAX(font.ascent, (*xfonts)->ascent);
  64. font.descent = MAX(font.descent,(*xfonts)->descent);
  65. xfonts++;
  66. }
  67. } else {
  68. if ((font.xfont = XLoadQueryFont(dpy, fontstr)) == NULL &&
  69. (font.xfont = XLoadQueryFont(dpy, "fixed")) == NULL)
  70. {
  71. error(EXIT_FAILURE, 0, "Error loading font '%s'", fontstr);
  72. }
  73. font.ascent = font.xfont->ascent;
  74. font.descent = font.xfont->descent;
  75. }
  76. fontheight = font.ascent + font.descent;
  77. barheight = fontheight + 2 * V_TEXT_PAD;
  78. }
  79. unsigned long win_alloc_color(win_t *win, const char *name)
  80. {
  81. XColor col;
  82. if (XAllocNamedColor(win->env.dpy,
  83. DefaultColormap(win->env.dpy, win->env.scr),
  84. name, &col, &col) == 0)
  85. {
  86. error(EXIT_FAILURE, 0, "Error allocating color '%s'", name);
  87. }
  88. return col.pixel;
  89. }
  90. void win_check_wm_support(Display *dpy, Window root)
  91. {
  92. int format;
  93. long offset = 0, length = 16;
  94. Atom *data, type;
  95. unsigned long i, nitems, bytes_left;
  96. Bool found = False;
  97. while (!found && length > 0) {
  98. if (XGetWindowProperty(dpy, root, atoms[ATOM__NET_SUPPORTED],
  99. offset, length, False, XA_ATOM, &type, &format,
  100. &nitems, &bytes_left, (unsigned char**) &data))
  101. {
  102. break;
  103. }
  104. if (type == XA_ATOM && format == 32) {
  105. for (i = 0; i < nitems; i++) {
  106. if (data[i] == atoms[ATOM__NET_WM_STATE_FULLSCREEN]) {
  107. found = True;
  108. fs_support = True;
  109. break;
  110. }
  111. }
  112. }
  113. XFree(data);
  114. offset += nitems;
  115. length = MIN(length, bytes_left / 4);
  116. }
  117. }
  118. #define INIT_ATOM_(atom) \
  119. atoms[ATOM_##atom] = XInternAtom(e->dpy, #atom, False);
  120. void win_init(win_t *win)
  121. {
  122. win_env_t *e;
  123. memset(win, 0, sizeof(win_t));
  124. e = &win->env;
  125. if ((e->dpy = XOpenDisplay(NULL)) == NULL)
  126. error(EXIT_FAILURE, 0, "Error opening X display");
  127. e->scr = DefaultScreen(e->dpy);
  128. e->scrw = DisplayWidth(e->dpy, e->scr);
  129. e->scrh = DisplayHeight(e->dpy, e->scr);
  130. e->vis = DefaultVisual(e->dpy, e->scr);
  131. e->cmap = DefaultColormap(e->dpy, e->scr);
  132. e->depth = DefaultDepth(e->dpy, e->scr);
  133. if (setlocale(LC_CTYPE, "") == NULL || XSupportsLocale() == 0)
  134. error(0, 0, "No locale support");
  135. win_init_font(e->dpy, BAR_FONT);
  136. win->bgcol = win_alloc_color(win, WIN_BG_COLOR);
  137. win->fscol = win_alloc_color(win, WIN_FS_COLOR);
  138. win->selcol = win_alloc_color(win, SEL_COLOR);
  139. win->bar.bgcol = win_alloc_color(win, BAR_BG_COLOR);
  140. win->bar.fgcol = win_alloc_color(win, BAR_FG_COLOR);
  141. win->bar.l.size = BAR_L_LEN;
  142. win->bar.r.size = BAR_R_LEN;
  143. win->bar.l.buf = emalloc(win->bar.l.size);
  144. win->bar.r.buf = emalloc(win->bar.r.size);
  145. win->bar.h = options->hide_bar ? 0 : barheight;
  146. INIT_ATOM_(WM_DELETE_WINDOW);
  147. INIT_ATOM_(_NET_WM_NAME);
  148. INIT_ATOM_(_NET_WM_ICON_NAME);
  149. INIT_ATOM_(_NET_WM_ICON);
  150. INIT_ATOM_(_NET_WM_STATE);
  151. INIT_ATOM_(_NET_WM_STATE_FULLSCREEN);
  152. INIT_ATOM_(_NET_SUPPORTED);
  153. win_check_wm_support(e->dpy, RootWindow(e->dpy, e->scr));
  154. }
  155. void win_open(win_t *win)
  156. {
  157. int c, i, j, n;
  158. win_env_t *e;
  159. XClassHint classhint;
  160. unsigned long *icon_data;
  161. XColor col;
  162. char none_data[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
  163. Pixmap none;
  164. int gmask;
  165. XSizeHints sizehints;
  166. Bool fullscreen = options->fullscreen && fs_support;
  167. e = &win->env;
  168. sizehints.flags = PWinGravity;
  169. sizehints.win_gravity = NorthWestGravity;
  170. /* determine window offsets, width & height */
  171. if (options->geometry == NULL)
  172. gmask = 0;
  173. else
  174. gmask = XParseGeometry(options->geometry, &win->x, &win->y,
  175. &win->w, &win->h);
  176. if ((gmask & WidthValue) != 0)
  177. sizehints.flags |= USSize;
  178. else
  179. win->w = WIN_WIDTH;
  180. if ((gmask & HeightValue) != 0)
  181. sizehints.flags |= USSize;
  182. else
  183. win->h = WIN_HEIGHT;
  184. if ((gmask & XValue) != 0) {
  185. if ((gmask & XNegative) != 0) {
  186. win->x += e->scrw - win->w;
  187. sizehints.win_gravity = NorthEastGravity;
  188. }
  189. sizehints.flags |= USPosition;
  190. } else {
  191. win->x = 0;
  192. }
  193. if ((gmask & YValue) != 0) {
  194. if ((gmask & YNegative) != 0) {
  195. win->y += e->scrh - win->h;
  196. sizehints.win_gravity = sizehints.win_gravity == NorthEastGravity
  197. ? SouthEastGravity : SouthWestGravity;
  198. }
  199. sizehints.flags |= USPosition;
  200. } else {
  201. win->y = 0;
  202. }
  203. win->xwin = XCreateWindow(e->dpy, RootWindow(e->dpy, e->scr),
  204. win->x, win->y, win->w, win->h, 0,
  205. e->depth, InputOutput, e->vis, 0, NULL);
  206. if (win->xwin == None)
  207. error(EXIT_FAILURE, 0, "Error creating X window");
  208. XSelectInput(e->dpy, win->xwin,
  209. ButtonReleaseMask | ButtonPressMask | KeyPressMask |
  210. PointerMotionMask | StructureNotifyMask);
  211. carrow = XCreateFontCursor(e->dpy, XC_left_ptr);
  212. chand = XCreateFontCursor(e->dpy, XC_fleur);
  213. cwatch = XCreateFontCursor(e->dpy, XC_watch);
  214. if (XAllocNamedColor(e->dpy, DefaultColormap(e->dpy, e->scr), "black",
  215. &col, &col) == 0)
  216. {
  217. error(EXIT_FAILURE, 0, "Error allocating color 'black'");
  218. }
  219. none = XCreateBitmapFromData(e->dpy, win->xwin, none_data, 8, 8);
  220. cnone = XCreatePixmapCursor(e->dpy, none, none, &col, &col, 0, 0);
  221. gc = XCreateGC(e->dpy, win->xwin, 0, None);
  222. n = icons[ARRLEN(icons)-1].size;
  223. icon_data = emalloc((n * n + 2) * sizeof(*icon_data));
  224. for (i = 0; i < ARRLEN(icons); i++) {
  225. n = 0;
  226. icon_data[n++] = icons[i].size;
  227. icon_data[n++] = icons[i].size;
  228. for (j = 0; j < icons[i].cnt; j++) {
  229. for (c = icons[i].data[j] >> 4; c >= 0; c--)
  230. icon_data[n++] = icon_colors[icons[i].data[j] & 0x0F];
  231. }
  232. XChangeProperty(e->dpy, win->xwin,
  233. atoms[ATOM__NET_WM_ICON], XA_CARDINAL, 32,
  234. i == 0 ? PropModeReplace : PropModeAppend,
  235. (unsigned char *) icon_data, n);
  236. }
  237. free(icon_data);
  238. win_set_title(win, "sxiv");
  239. classhint.res_class = "Sxiv";
  240. classhint.res_name = options->res_name != NULL ? options->res_name : "sxiv";
  241. XSetClassHint(e->dpy, win->xwin, &classhint);
  242. XSetWMProtocols(e->dpy, win->xwin, &atoms[ATOM_WM_DELETE_WINDOW], 1);
  243. sizehints.width = win->w;
  244. sizehints.height = win->h;
  245. sizehints.x = win->x;
  246. sizehints.y = win->y;
  247. XSetWMNormalHints(win->env.dpy, win->xwin, &sizehints);
  248. win->h -= win->bar.h;
  249. win->buf.w = e->scrw;
  250. win->buf.h = e->scrh;
  251. win->buf.pm = XCreatePixmap(e->dpy, win->xwin,
  252. win->buf.w, win->buf.h, e->depth);
  253. XSetForeground(e->dpy, gc, fullscreen ? win->fscol : win->bgcol);
  254. XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h);
  255. XSetWindowBackgroundPixmap(e->dpy, win->xwin, win->buf.pm);
  256. XMapWindow(e->dpy, win->xwin);
  257. XFlush(e->dpy);
  258. if (fullscreen)
  259. win_toggle_fullscreen(win);
  260. }
  261. CLEANUP void win_close(win_t *win)
  262. {
  263. XFreeCursor(win->env.dpy, carrow);
  264. XFreeCursor(win->env.dpy, cnone);
  265. XFreeCursor(win->env.dpy, chand);
  266. XFreeCursor(win->env.dpy, cwatch);
  267. XFreeGC(win->env.dpy, gc);
  268. XDestroyWindow(win->env.dpy, win->xwin);
  269. XCloseDisplay(win->env.dpy);
  270. }
  271. bool win_configure(win_t *win, XConfigureEvent *c)
  272. {
  273. bool changed;
  274. changed = win->w != c->width || win->h + win->bar.h != c->height;
  275. win->x = c->x;
  276. win->y = c->y;
  277. win->w = c->width;
  278. win->h = c->height - win->bar.h;
  279. win->bw = c->border_width;
  280. return changed;
  281. }
  282. void win_toggle_fullscreen(win_t *win)
  283. {
  284. XEvent ev;
  285. XClientMessageEvent *cm;
  286. if (!fs_support) {
  287. if (!fs_warned) {
  288. error(0, 0, "No fullscreen support");
  289. fs_warned = True;
  290. }
  291. return;
  292. }
  293. win->fullscreen = !win->fullscreen;
  294. memset(&ev, 0, sizeof(ev));
  295. ev.type = ClientMessage;
  296. cm = &ev.xclient;
  297. cm->window = win->xwin;
  298. cm->message_type = atoms[ATOM__NET_WM_STATE];
  299. cm->format = 32;
  300. cm->data.l[0] = win->fullscreen;
  301. cm->data.l[1] = atoms[ATOM__NET_WM_STATE_FULLSCREEN];
  302. cm->data.l[2] = cm->data.l[3] = 0;
  303. XSendEvent(win->env.dpy, DefaultRootWindow(win->env.dpy), False,
  304. SubstructureNotifyMask | SubstructureRedirectMask, &ev);
  305. }
  306. void win_toggle_bar(win_t *win)
  307. {
  308. if (win->bar.h != 0) {
  309. win->h += win->bar.h;
  310. win->bar.h = 0;
  311. } else {
  312. win->bar.h = barheight;
  313. win->h -= win->bar.h;
  314. }
  315. }
  316. void win_clear(win_t *win)
  317. {
  318. win_env_t *e;
  319. e = &win->env;
  320. if (win->w > win->buf.w || win->h + win->bar.h > win->buf.h) {
  321. XFreePixmap(e->dpy, win->buf.pm);
  322. win->buf.w = MAX(win->buf.w, win->w);
  323. win->buf.h = MAX(win->buf.h, win->h + win->bar.h);
  324. win->buf.pm = XCreatePixmap(e->dpy, win->xwin,
  325. win->buf.w, win->buf.h, e->depth);
  326. }
  327. XSetForeground(e->dpy, gc, win->fullscreen ? win->fscol : win->bgcol);
  328. XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h);
  329. }
  330. void win_draw_bar(win_t *win)
  331. {
  332. int len, olen, x, y, w, tw;
  333. char rest[3];
  334. const char *dots = "...";
  335. win_env_t *e;
  336. win_bar_t *l, *r;
  337. if ((l = &win->bar.l)->buf == NULL || (r = &win->bar.r)->buf == NULL)
  338. return;
  339. e = &win->env;
  340. y = win->h + font.ascent + V_TEXT_PAD;
  341. w = win->w;
  342. XSetForeground(e->dpy, gc, win->bar.bgcol);
  343. XFillRectangle(e->dpy, win->buf.pm, gc, 0, win->h, win->w, win->bar.h);
  344. XSetForeground(e->dpy, gc, win->bar.fgcol);
  345. XSetBackground(e->dpy, gc, win->bar.bgcol);
  346. if ((len = strlen(r->buf)) > 0) {
  347. if ((tw = win_textwidth(r->buf, len, true)) > w)
  348. return;
  349. x = win->w - tw + H_TEXT_PAD;
  350. w -= tw;
  351. if (font.set)
  352. XmbDrawString(e->dpy, win->buf.pm, font.set, gc, x, y, r->buf, len);
  353. else
  354. XDrawString(e->dpy, win->buf.pm, gc, x, y, r->buf, len);
  355. }
  356. if ((len = strlen(l->buf)) > 0) {
  357. olen = len;
  358. while (len > 0 && (tw = win_textwidth(l->buf, len, true)) > w)
  359. len--;
  360. if (len > 0) {
  361. if (len != olen) {
  362. w = strlen(dots);
  363. if (len <= w)
  364. return;
  365. memcpy(rest, l->buf + len - w, w);
  366. memcpy(l->buf + len - w, dots, w);
  367. }
  368. x = H_TEXT_PAD;
  369. if (font.set)
  370. XmbDrawString(e->dpy, win->buf.pm, font.set, gc, x, y, l->buf, len);
  371. else
  372. XDrawString(e->dpy, win->buf.pm, gc, x, y, l->buf, len);
  373. if (len != olen)
  374. memcpy(l->buf + len - w, rest, w);
  375. }
  376. }
  377. }
  378. void win_draw(win_t *win)
  379. {
  380. if (win->bar.h > 0)
  381. win_draw_bar(win);
  382. XSetWindowBackgroundPixmap(win->env.dpy, win->xwin, win->buf.pm);
  383. XClearWindow(win->env.dpy, win->xwin);
  384. XFlush(win->env.dpy);
  385. }
  386. void win_draw_rect(win_t *win, int x, int y, int w, int h, bool fill, int lw,
  387. unsigned long col)
  388. {
  389. XGCValues gcval;
  390. gcval.line_width = lw;
  391. gcval.foreground = col;
  392. XChangeGC(win->env.dpy, gc, GCForeground | GCLineWidth, &gcval);
  393. if (fill)
  394. XFillRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h);
  395. else
  396. XDrawRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h);
  397. }
  398. int win_textwidth(const char *text, unsigned int len, bool with_padding)
  399. {
  400. XRectangle r;
  401. int padding = with_padding ? 2 * H_TEXT_PAD : 0;
  402. if (font.set) {
  403. XmbTextExtents(font.set, text, len, NULL, &r);
  404. return r.width + padding;
  405. } else {
  406. return XTextWidth(font.xfont, text, len) + padding;
  407. }
  408. }
  409. void win_set_title(win_t *win, const char *title)
  410. {
  411. if (title == NULL)
  412. title = "sxiv";
  413. XStoreName(win->env.dpy, win->xwin, title);
  414. XSetIconName(win->env.dpy, win->xwin, title);
  415. XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_NAME],
  416. XInternAtom(win->env.dpy, "UTF8_STRING", False), 8,
  417. PropModeReplace, (unsigned char *) title, strlen(title));
  418. XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_ICON_NAME],
  419. XInternAtom(win->env.dpy, "UTF8_STRING", False), 8,
  420. PropModeReplace, (unsigned char *) title, strlen(title));
  421. }
  422. void win_set_cursor(win_t *win, cursor_t cursor)
  423. {
  424. switch (cursor) {
  425. case CURSOR_NONE:
  426. XDefineCursor(win->env.dpy, win->xwin, cnone);
  427. break;
  428. case CURSOR_HAND:
  429. XDefineCursor(win->env.dpy, win->xwin, chand);
  430. break;
  431. case CURSOR_WATCH:
  432. XDefineCursor(win->env.dpy, win->xwin, cwatch);
  433. break;
  434. case CURSOR_ARROW:
  435. default:
  436. XDefineCursor(win->env.dpy, win->xwin, carrow);
  437. break;
  438. }
  439. XFlush(win->env.dpy);
  440. }