status.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. /*
  2. * wmfs2 by Martin Duquesnoy <xorg62@gmail.com> { for(i = 2011; i < 2111; ++i) ©(i); }
  3. * For license, see COPYING.
  4. */
  5. #include "status.h"
  6. #include "barwin.h"
  7. #include "config.h"
  8. #include "infobar.h"
  9. #include "util.h"
  10. #include "draw.h"
  11. #include <string.h>
  12. struct status_seq*
  13. status_new_seq(char type, int narg, int minarg, char *args[], int *shift)
  14. {
  15. struct status_seq *sq = xcalloc(1, sizeof(struct status_seq));
  16. SLIST_INIT(&sq->mousebinds);
  17. sq->type = type;
  18. *shift = 0;
  19. if(narg == minarg || !strcmp(args[0], "right") || !strcmp(args[0], "left"))
  20. sq->align = str_to_position(args[0]);
  21. else
  22. {
  23. sq->align = NoAlign;
  24. sq->geo.x = ATOI(args[0]);
  25. sq->geo.y = ATOI(args[1]);
  26. *shift = 1;
  27. }
  28. return sq;
  29. }
  30. struct status_ctx
  31. status_new_ctx(struct barwin *b, struct theme *t)
  32. {
  33. struct status_ctx ctx = { .barwin = b, .theme = t };
  34. SLIST_INIT(&ctx.statushead);
  35. SLIST_INIT(&ctx.gcache);
  36. return ctx;
  37. }
  38. static void
  39. status_gcache_free(struct status_ctx *ctx)
  40. {
  41. struct status_gcache *gc;
  42. while(!SLIST_EMPTY(&ctx->gcache))
  43. {
  44. gc = SLIST_FIRST(&ctx->gcache);
  45. SLIST_REMOVE_HEAD(&ctx->gcache, next);
  46. free(gc->datas);
  47. free(gc->name);
  48. free(gc);
  49. }
  50. }
  51. void
  52. status_free_ctx(struct status_ctx *ctx)
  53. {
  54. free(ctx->status);
  55. status_flush_list(ctx);
  56. status_gcache_free(ctx);
  57. }
  58. static void
  59. status_graph_draw(struct status_ctx *ctx, struct status_seq *sq, struct status_gcache *gc)
  60. {
  61. int max = 0;
  62. int i, j, y;
  63. float c;
  64. int ys = sq->geo.y + sq->geo.h - 1;
  65. /* If invalid maximum, we have to compute it ourselves */
  66. if(sq->data[2] <= 0)
  67. {
  68. for(i = sq->geo.x + sq->geo.w - 1, j = gc->ndata - 1;
  69. j >= 0 && i >= sq->geo.x;
  70. --j, --i)
  71. {
  72. if(gc->datas[j] > max)
  73. max = gc->datas[j];
  74. }
  75. }
  76. else
  77. max = sq->data[2];
  78. XSetForeground(W->dpy, W->gc, sq->color2);
  79. for(i = sq->geo.x + sq->geo.w - 1, j = gc->ndata - 1;
  80. j >= 0 && i >= sq->geo.x;
  81. --j, --i)
  82. {
  83. /* You divided by zero didn't you? */
  84. if(gc->datas[j])
  85. {
  86. c = (float)max / (float)gc->datas[j];
  87. y = ys - (sq->geo.h / (c > 1 ? c : 1)) + 1;
  88. draw_line(ctx->barwin->dr, i, y, i, ys);
  89. }
  90. }
  91. }
  92. static void
  93. status_graph_process(struct status_ctx *ctx, struct status_seq *sq, char *name)
  94. {
  95. int j;
  96. struct status_gcache *gc;
  97. /* Graph already exist and have a cache */
  98. SLIST_FOREACH(gc, &ctx->gcache, next)
  99. if(!strcmp(name, gc->name))
  100. {
  101. /* shift buffer to remove unused old value */
  102. if(gc->ndata > (sq->geo.w << 1))
  103. for(gc->ndata /= 2, j = 0;
  104. j < gc->ndata;
  105. gc->datas[j] = gc->datas[j + gc->ndata], ++j);
  106. gc->datas[gc->ndata++] = sq->data[1];
  107. status_graph_draw(ctx, sq, gc);
  108. return;
  109. }
  110. if(sq->data[1] > sq->data[2])
  111. sq->data[1] = sq->data[2];
  112. /* No? Make a cache for it */
  113. gc = xcalloc(1, sizeof(struct status_gcache));
  114. gc->name = xstrdup(name);
  115. gc->ndata = 1;
  116. gc->datas = xcalloc(sq->geo.w << 2, sizeof(int));
  117. gc->datas[0] = sq->data[1];
  118. SLIST_INSERT_HEAD(&ctx->gcache, gc, next);
  119. status_graph_draw(ctx, sq, gc);
  120. }
  121. /* Parse mousebind sequence next normal sequence: \<seq>[](button;func;cmd) */
  122. static char*
  123. status_parse_mouse(struct status_seq *sq, char *str)
  124. {
  125. struct mousebind *m;
  126. char *end, *arg[3] = { NULL };
  127. int i;
  128. if(*str != '(' || !(end = strchr(str, ')')))
  129. return str;
  130. i = parse_args(++str, ';', ')', 3, arg);
  131. m = xcalloc(1, sizeof(struct mousebind));
  132. m->use_area = true;
  133. m->button = ATOI(arg[0]);
  134. m->func = uicb_name_func(arg[1]);
  135. m->cmd = (i > 1 ? xstrdup(arg[2]) : NULL);
  136. SLIST_INSERT_HEAD(&sq->mousebinds, m, snext);
  137. return end + 1;
  138. }
  139. #define STATUS_CHECK_ARGS(i, n1, n2, str, end) \
  140. if(i != n1 && i != n2) \
  141. { \
  142. str = end + 2; \
  143. continue; \
  144. }
  145. void
  146. status_parse(struct status_ctx *ctx)
  147. {
  148. struct status_seq *sq, *prev = NULL;
  149. int i, tmp, shift = 0;
  150. char *dstr = xstrdup(ctx->status), *sauv = dstr;
  151. char type, *p, *pp, *end, *arg[10] = { NULL };
  152. for(; *dstr; ++dstr)
  153. {
  154. /* Check if this is a sequence */
  155. if(*dstr != '^' && *dstr != '\\')
  156. continue;
  157. p = ++dstr;
  158. /* Search for correct end of sequence (] without \ behind) */
  159. if((end = strchr(p, ']')))
  160. while(*(end - 1) == '\\')
  161. end = strchr(end + 1, ']');
  162. if(!(strchr("sRpPig", *p)) || !end)
  163. continue;
  164. /* Then parse & list it */
  165. switch((type = *p))
  166. {
  167. /*
  168. * Text sequence: \s[left/right;#color;text] OR \s[x;y;#color;text]
  169. */
  170. case 's':
  171. i = parse_args(p + 2, ';', ']', 4, arg);
  172. STATUS_CHECK_ARGS(i, 2, 3, dstr, end);
  173. sq = status_new_seq(type, i, 2, arg, &shift);
  174. sq->color = color_atoh(arg[1 + shift]);
  175. sq->str = xstrdup(arg[2 + shift]);
  176. /* Remove \ from string */
  177. for(pp = sq->str; (pp = strchr(sq->str, '\\'));)
  178. memmove(pp, pp + 1, strlen(pp));
  179. break;
  180. /*
  181. * Rectangle sequence: \R[left/right;w;h;#color] OR \R[x;y;w;h;#color]
  182. */
  183. case 'R':
  184. i = parse_args(p + 2, ';', ']', 5, arg);
  185. STATUS_CHECK_ARGS(i, 3, 4, dstr, end);
  186. sq = status_new_seq(type, i, 3, arg, &shift);
  187. sq->geo.w = ATOI(arg[1 + shift]);
  188. sq->geo.h = ATOI(arg[2 + shift]);
  189. sq->color = color_atoh(arg[3 + shift]);
  190. break;
  191. /*
  192. * Progress bar sequence: \p[left/right;w;h;bord;val;valmax;bg;fg] OR x;y
  193. * Position bar sequence: \P[left/right;w;h;tickbord;val;valmax;bg;fg] OR x;y
  194. */
  195. case 'p':
  196. case 'P':
  197. i = parse_args(p + 2, ';', ']', 9, arg);
  198. STATUS_CHECK_ARGS(i, 7, 8, dstr, end);
  199. sq = status_new_seq(type, i, 7, arg, &shift);
  200. sq->geo.w = ATOI(arg[1 + shift]);
  201. sq->geo.h = ATOI(arg[2 + shift]);
  202. sq->data[0] = ATOI(arg[3 + shift]); /* Border */
  203. sq->data[1] = ((tmp = ATOI(arg[4 + shift])) ? tmp : 1); /* Value */
  204. sq->data[2] = ATOI(arg[5 + shift]); /* Value Max */
  205. sq->color = color_atoh(arg[6 + shift]);
  206. sq->color2 = color_atoh(arg[7 + shift]);
  207. break;
  208. /*
  209. * Graph sequence: \g[left/right;w;h;val;valmax;bg;fg;name] OR x;y
  210. */
  211. case 'g':
  212. i = parse_args(p + 2, ';', ']', 9, arg);
  213. STATUS_CHECK_ARGS(i, 7, 8, dstr, end);
  214. sq = status_new_seq(type, i, 7, arg, &shift);
  215. sq->geo.w = ATOI(arg[1 + shift]);
  216. sq->geo.h = ATOI(arg[2 + shift]);
  217. sq->data[1] = ATOI(arg[3 + shift]); /* Value */
  218. sq->data[2] = ATOI(arg[4 + shift]); /* Value Max */
  219. sq->color = color_atoh(arg[5 + shift]);
  220. sq->color2 = color_atoh(arg[6 + shift]);
  221. sq->str = xstrdup(arg[7 + shift]);
  222. break;
  223. /*
  224. * Image sequence: \i[left/right;w;h;/path/img] OR \i[x;y;w;h;/path/img]
  225. */
  226. #ifdef HAVE_IMLIB2
  227. case 'i':
  228. i = parse_args(p + 2, ';', ']', 5, arg);
  229. STATUS_CHECK_ARGS(i, 3, 4, dstr, end);
  230. sq = status_new_seq(type, i, 3, arg, &shift);
  231. sq->geo.w = ATOI(arg[1 + shift]);
  232. sq->geo.h = ATOI(arg[2 + shift]);
  233. sq->str = xstrdup(arg[3 + shift]);
  234. break;
  235. #endif /* HAVE_IMLIB2 */
  236. }
  237. if(sq->align == Right)
  238. SLIST_INSERT_HEAD(&ctx->statushead, sq, next);
  239. else
  240. SLIST_INSERT_TAIL(&ctx->statushead, sq, next, prev);
  241. /*
  242. * Optional mousebind sequence(s) \<seq>[](button;func;cmd)
  243. * Parse it while there is a mousebind sequence.
  244. */
  245. dstr = end + 1;
  246. do
  247. dstr = status_parse_mouse(sq, dstr);
  248. while(*dstr == '(');
  249. --dstr;
  250. prev = sq;
  251. }
  252. free(sauv);
  253. }
  254. #define STATUS_ALIGN(align) \
  255. if(align == Left) \
  256. { \
  257. sq->geo.x = left; \
  258. left += sq->geo.w; \
  259. } \
  260. else if(align == Right) \
  261. { \
  262. sq->geo.x = ctx->barwin->geo.w - right - sq->geo.w; \
  263. right += sq->geo.w; \
  264. }
  265. #define STORE_MOUSEBIND() \
  266. if(!SLIST_EMPTY(&sq->mousebinds)) \
  267. SLIST_FOREACH(m, &sq->mousebinds, snext) \
  268. m->area = sq->geo;
  269. #define NOALIGN_Y() \
  270. if(sq->align != NoAlign) \
  271. sq->geo.y = (ctx->barwin->geo.h >> 1) - (sq->geo.h >> 1);
  272. static void
  273. status_apply_list(struct status_ctx *ctx)
  274. {
  275. struct status_seq *sq;
  276. struct mousebind *m;
  277. struct geo g;
  278. int left = 0, right = 0, w, h;
  279. SLIST_FOREACH(sq, &ctx->statushead, next)
  280. {
  281. switch(sq->type)
  282. {
  283. /* Text */
  284. case 's':
  285. sq->geo.w = draw_textw(ctx->theme, sq->str);
  286. sq->geo.h = ctx->theme->font.height;
  287. if(sq->align != NoAlign)
  288. sq->geo.y = TEXTY(ctx->theme, ctx->barwin->geo.h);
  289. STATUS_ALIGN(sq->align);
  290. draw_text(ctx->barwin->dr, ctx->theme, sq->geo.x, sq->geo.y, sq->color, sq->str);
  291. if(!SLIST_EMPTY(&sq->mousebinds))
  292. SLIST_FOREACH(m, &sq->mousebinds, snext)
  293. {
  294. m->area = sq->geo;
  295. m->area.y -= sq->geo.h;
  296. }
  297. break;
  298. /* Rectangle */
  299. case 'R':
  300. NOALIGN_Y();
  301. STATUS_ALIGN(sq->align);
  302. draw_rect(ctx->barwin->dr, &sq->geo, sq->color);
  303. STORE_MOUSEBIND();
  304. break;
  305. /* Progress */
  306. case 'p':
  307. NOALIGN_Y();
  308. STATUS_ALIGN(sq->align);
  309. draw_rect(ctx->barwin->dr, &sq->geo, sq->color);
  310. /* Progress bar geo */
  311. g.x = sq->geo.x + sq->data[0];
  312. g.y = sq->geo.y + sq->data[0];
  313. g.w = sq->geo.w - sq->data[0] - sq->data[0];
  314. g.h = sq->geo.h - sq->data[0] - sq->data[0];
  315. if(sq->data[1] > sq->data[2])
  316. sq->data[1] = sq->data[2];
  317. if(sq->geo.w > sq->geo.h)
  318. g.w /= ((float)sq->data[2] / (float)sq->data[1]);
  319. else
  320. {
  321. g.y += g.h;
  322. g.h /= ((float)sq->data[2] / (float)sq->data[1]);
  323. g.y -= g.h;
  324. }
  325. draw_rect(ctx->barwin->dr, &g, sq->color2);
  326. STORE_MOUSEBIND();
  327. break;
  328. /* Position */
  329. case 'P':
  330. NOALIGN_Y();
  331. STATUS_ALIGN(sq->align);
  332. draw_rect(ctx->barwin->dr, &sq->geo, sq->color);
  333. if(sq->data[1] > sq->data[2])
  334. sq->data[1] = sq->data[2];
  335. g.x = sq->geo.x + ((sq->geo.w - sq->data[0]) / ((float)sq->data[2] / (float)sq->data[1]));
  336. g.y = sq->geo.y;
  337. g.w = sq->data[0];
  338. g.h = sq->geo.h;
  339. draw_rect(ctx->barwin->dr, &g, sq->color2);
  340. STORE_MOUSEBIND();
  341. break;
  342. /* Graph */
  343. case 'g':
  344. NOALIGN_Y();
  345. STATUS_ALIGN(sq->align);
  346. draw_rect(ctx->barwin->dr, &sq->geo, sq->color);
  347. status_graph_process(ctx, sq, sq->str);
  348. STORE_MOUSEBIND();
  349. break;
  350. /* Image */
  351. #ifdef HAVE_IMLIB2
  352. case 'i':
  353. draw_image_load(sq->str, &w, &h);
  354. if(sq->geo.w <= 0)
  355. sq->geo.w = w;
  356. if(sq->geo.h <= 0)
  357. sq->geo.h = h;
  358. if(sq->align != NoAlign)
  359. sq->geo.y = (ctx->barwin->geo.h >> 1) - (sq->geo.h >> 1);
  360. STATUS_ALIGN(sq->align);
  361. draw_image(ctx->barwin->dr, &sq->geo);
  362. STORE_MOUSEBIND();
  363. break;
  364. #endif /* HAVE_IMLIB2 */
  365. }
  366. }
  367. }
  368. /* Render current statustext of an element */
  369. void
  370. status_render(struct status_ctx *ctx)
  371. {
  372. if(!ctx->status)
  373. return;
  374. if(!(ctx->flags & STATUS_BLOCK_REFRESH))
  375. barwin_refresh_color(ctx->barwin);
  376. /* Use simple text instead sequence if no sequence found */
  377. if(SLIST_EMPTY(&ctx->statushead))
  378. {
  379. int l = draw_textw(ctx->theme, ctx->status);
  380. draw_text(ctx->barwin->dr, ctx->theme, ctx->barwin->geo.w - l,
  381. TEXTY(ctx->theme, ctx->barwin->geo.h), ctx->barwin->fg, ctx->status);
  382. }
  383. else
  384. status_apply_list(ctx);
  385. barwin_refresh(ctx->barwin);
  386. }
  387. void
  388. status_flush_list(struct status_ctx *ctx)
  389. {
  390. struct status_seq *sq;
  391. struct mousebind *m;
  392. /* Flush previous linked list of status sequences */
  393. while(!SLIST_EMPTY(&ctx->statushead))
  394. {
  395. sq = SLIST_FIRST(&ctx->statushead);
  396. SLIST_REMOVE_HEAD(&ctx->statushead, next);
  397. while(!SLIST_EMPTY(&sq->mousebinds))
  398. {
  399. m = SLIST_FIRST(&sq->mousebinds);
  400. SLIST_REMOVE_HEAD(&sq->mousebinds, snext);
  401. free((void*)m->cmd);
  402. free(m);
  403. }
  404. free(sq->str);
  405. free(sq);
  406. }
  407. SLIST_INIT(&ctx->statushead);
  408. }
  409. void
  410. status_copy_mousebind(struct status_ctx *ctx)
  411. {
  412. struct mousebind *m;
  413. struct status_seq *sq;
  414. if(!ctx->barwin)
  415. return;
  416. /* Flush barwin head of status mousebinds */
  417. SLIST_INIT(&ctx->barwin->statusmousebinds);
  418. SLIST_FOREACH(sq, &ctx->statushead, next)
  419. {
  420. SLIST_FOREACH(m, &sq->mousebinds, snext)
  421. SLIST_INSERT_HEAD(&ctx->barwin->statusmousebinds, m, next);
  422. }
  423. }
  424. /* Parse and render statustext */
  425. void
  426. status_manage(struct status_ctx *ctx)
  427. {
  428. if(!ctx->status)
  429. return;
  430. ctx->update = false;
  431. status_flush_list(ctx);
  432. status_parse(ctx);
  433. status_render(ctx);
  434. status_copy_mousebind(ctx);
  435. }
  436. void
  437. status_flush_surface(void)
  438. {
  439. struct barwin *b;
  440. while(!SLIST_EMPTY(&W->h.vbarwin))
  441. {
  442. b = SLIST_FIRST(&W->h.vbarwin);
  443. SLIST_REMOVE_HEAD(&W->h.vbarwin, vnext);
  444. barwin_remove(b);
  445. }
  446. }
  447. static void
  448. status_surface(int x, int y, int w, int h, Color bg, char *status)
  449. {
  450. struct barwin *b;
  451. struct screen *s;
  452. struct status_ctx ctx;
  453. int d;
  454. Window rw;
  455. if(!status)
  456. return;
  457. if(x + y < 0)
  458. XQueryPointer(W->dpy, W->root, &rw, &rw, &x, &y, &d, &d, (unsigned int *)&d);
  459. s = screen_gb_geo(x, y);
  460. if(x + w > s->geo.x + s->geo.w)
  461. x -= w;
  462. if(y + h > s->geo.y + s->geo.h)
  463. y -= h;
  464. b = barwin_new(W->root, x, y, w, h, 0, bg, false);
  465. barwin_map(b);
  466. /* Use client theme */
  467. ctx = status_new_ctx(b, W->ctheme);
  468. ctx.status = xstrdup(status);
  469. SLIST_INSERT_HEAD(&W->h.vbarwin, b, vnext);
  470. status_manage(&ctx);
  471. status_free_ctx(&ctx);
  472. }
  473. void
  474. uicb_status_surface(Uicb cmd)
  475. {
  476. char *p, *ccmd = xstrdup(cmd);
  477. int s, w, h, x = -1, y = -1;
  478. Color bg;
  479. if(!ccmd || !(p = strchr(ccmd, ' ')))
  480. return;
  481. *p = '\0';
  482. ++p;
  483. if(!(((s = sscanf(ccmd, "%d,%d,#%x", &w, &h, &bg)) == 3)
  484. || (s = sscanf(ccmd, "%d,%d,%d,%d,#%x", &x, &y, &w, &h, &bg)) == 5))
  485. {
  486. free(ccmd);
  487. return;
  488. }
  489. status_surface(x, y, w, h, bg, p);
  490. free(ccmd);
  491. }
  492. /* Syntax: "<infobar name> <status string>" */
  493. void
  494. uicb_status(Uicb cmd)
  495. {
  496. struct infobar *ib;
  497. struct screen *s;
  498. char *p;
  499. if(!cmd || !(p = strchr(cmd, ' ')))
  500. return;
  501. /* Get infobar name & status */
  502. *p = '\0';
  503. ++p;
  504. SLIST_FOREACH(s, &W->h.screen, next)
  505. {
  506. SLIST_FOREACH(ib, &s->infobars, next)
  507. if(!strcmp(cmd, ib->name))
  508. {
  509. free(ib->statusctx.status);
  510. ib->statusctx.status = xstrdup(p);
  511. ib->statusctx.update = true;
  512. infobar_elem_screen_update(s, ElemStatus);
  513. }
  514. }
  515. }