jcs ratpoison hax
1/* Functionality for a bar listing the windows currently managed.
2 *
3 * Copyright (C) 2000, 2001, 2002, 2003, 2004 Shawn Betts <sabetts@vcn.bc.ca>
4 *
5 * This file is part of ratpoison.
6 *
7 * ratpoison is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
11 *
12 * ratpoison is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this software; see the file COPYING. If not, write to
19 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
20 * Boston, MA 02111-1307 USA
21 */
22
23#include <X11/X.h>
24#include <X11/Xlib.h>
25#include <X11/Xutil.h>
26
27#ifdef USE_XFT_FONT
28#include <X11/Xft/Xft.h>
29#endif
30
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <unistd.h>
35
36#include "ratpoison.h"
37
38/* Possible values for bar_is_raised status. */
39#define BAR_IS_HIDDEN 0
40#define BAR_IS_WINDOW_LIST 1
41#define BAR_IS_GROUP_LIST 2
42#define BAR_IS_MESSAGE 3
43
44/* A copy of the last message displayed in the message bar. */
45static char *last_msg = NULL;
46static int last_mark_start = 0;
47static int last_mark_end = 0;
48
49static void marked_message_internal (char *msg, int mark_start, int mark_end);
50
51/* Reset the alarm to auto-hide the bar in BAR_TIMEOUT seconds. */
52static void
53reset_alarm (void)
54{
55 alarm (defaults.bar_timeout);
56 alarm_signalled = 0;
57}
58
59/* Hide the bar from sight. */
60int
61hide_bar (rp_screen *s)
62{
63 if (s->bar_is_raised)
64 {
65 s->bar_is_raised = 0;
66 XUnmapWindow (dpy, s->bar_window);
67
68 /* Possibly restore colormap. */
69 if (current_window())
70 {
71 XUninstallColormap (dpy, s->def_cmap);
72 XInstallColormap (dpy, current_window()->colormap);
73 }
74
75 return 1;
76 }
77
78 return 0;
79}
80
81/* Show window listing in bar. */
82int
83show_bar (rp_screen *s, char *fmt)
84{
85 if (!s->bar_is_raised)
86 {
87 s->bar_is_raised = BAR_IS_WINDOW_LIST;
88 XMapRaised (dpy, s->bar_window);
89 update_window_names (s, fmt);
90
91 /* Switch to the default colormap */
92 if (current_window())
93 XUninstallColormap (dpy, current_window()->colormap);
94 XInstallColormap (dpy, s->def_cmap);
95
96 reset_alarm();
97 return 1;
98 }
99
100 /* If the bar is raised we still need to display the window
101 names. */
102 update_window_names (s, fmt);
103 return 0;
104}
105
106/* Show group listing in bar. */
107int
108show_group_bar (rp_screen *s)
109{
110 if (!s->bar_is_raised)
111 {
112 s->bar_is_raised = BAR_IS_GROUP_LIST;
113 XMapRaised (dpy, s->bar_window);
114 update_group_names (s);
115
116 /* Switch to the default colormap */
117 if (current_window())
118 XUninstallColormap (dpy, current_window()->colormap);
119 XInstallColormap (dpy, s->def_cmap);
120
121 reset_alarm();
122 return 1;
123 }
124
125 /* If the bar is raised we still need to display the window
126 names. */
127 update_group_names (s);
128 return 0;
129}
130
131int
132bar_x (rp_screen *s, int width)
133{
134 int x = 0;
135
136 switch (defaults.bar_location)
137 {
138 case NorthWestGravity:
139 case WestGravity:
140 case SouthWestGravity:
141 x = s->left + (defaults.bar_in_padding ? 0 : defaults.padding_left);
142 break;
143 case NorthGravity:
144 case CenterGravity:
145 case SouthGravity:
146 x = s->left + (s->width - width - defaults.bar_border_width * 2) / 2
147 - (defaults.bar_in_padding ? 0 : defaults.padding_left);
148 break;
149 case NorthEastGravity:
150 case EastGravity:
151 case SouthEastGravity:
152 x = s->left + s->width - width - defaults.bar_border_width * 2
153 - (defaults.bar_in_padding ? 0 : defaults.padding_right);
154 break;
155 }
156
157 return x;
158}
159
160int
161bar_y (rp_screen *s, int height)
162{
163 int y = 0;
164
165 switch (defaults.bar_location)
166 {
167 case NorthEastGravity:
168 case NorthGravity:
169 case NorthWestGravity:
170 y = s->top + (defaults.bar_in_padding ? 0 : defaults.padding_top);
171 break;
172 case EastGravity:
173 case CenterGravity:
174 case WestGravity:
175 y = s->top + (s->height - height
176 - defaults.bar_border_width * 2) / 2
177 - (defaults.bar_in_padding ? 0 : defaults.padding_top);
178 break;
179 case SouthEastGravity:
180 case SouthGravity:
181 case SouthWestGravity:
182 y = s->top + (s->height - height
183 - defaults.bar_border_width * 2)
184 - (defaults.bar_in_padding ? 0 : defaults.padding_top);
185 break;
186 }
187
188 return y;
189}
190
191void
192update_bar (rp_screen *s)
193{
194 if (s->bar_is_raised == BAR_IS_WINDOW_LIST) {
195 update_window_names (s, defaults.window_fmt);
196 return;
197 }
198
199 if (s->bar_is_raised == BAR_IS_GROUP_LIST) {
200 update_group_names (s);
201 return;
202 }
203
204 if (s->bar_is_raised == BAR_IS_HIDDEN)
205 return;
206
207 redraw_last_message();
208}
209
210/* Note that we use marked_message_internal to avoid resetting the
211 alarm. */
212void
213update_window_names (rp_screen *s, char *fmt)
214{
215 struct sbuf *bar_buffer;
216 int mark_start = 0;
217 int mark_end = 0;
218 char *delimiter;
219
220 if (s->bar_is_raised != BAR_IS_WINDOW_LIST) return;
221
222 delimiter = (defaults.window_list_style == STYLE_ROW) ? " " : "\n";
223
224 bar_buffer = sbuf_new (0);
225
226 get_window_list (fmt, delimiter, bar_buffer, &mark_start, &mark_end);
227 marked_message (sbuf_get (bar_buffer), mark_start, mark_end);
228
229 sbuf_free (bar_buffer);
230}
231
232/* Note that we use marked_message_internal to avoid resetting the
233 alarm. */
234void
235update_group_names (rp_screen *s)
236{
237 struct sbuf *bar_buffer;
238 int mark_start = 0;
239 int mark_end = 0;
240 char *delimiter;
241
242 if (s->bar_is_raised != BAR_IS_GROUP_LIST) return;
243
244 delimiter = (defaults.window_list_style == STYLE_ROW) ? " " : "\n";
245
246 bar_buffer = sbuf_new (0);
247
248 get_group_list (delimiter, bar_buffer, &mark_start, &mark_end);
249 marked_message_internal (sbuf_get (bar_buffer), mark_start, mark_end);
250
251 sbuf_free (bar_buffer);
252}
253
254void
255message (char *s)
256{
257 marked_message (s, 0, 0);
258}
259
260void
261marked_message_printf (int mark_start, int mark_end, char *fmt, ...)
262{
263 char *buffer;
264 va_list ap;
265
266 va_start (ap, fmt);
267 buffer = xvsprintf (fmt, ap);
268 va_end (ap);
269
270 marked_message (buffer, mark_start, mark_end);
271 free (buffer);
272}
273
274static int
275count_lines (char* msg, int len)
276{
277 int ret = 1;
278 int i;
279
280 if (len < 1)
281 return 1;
282
283 for(i=0; i<len; i++)
284 {
285 if (msg[i] == '\n') ret++;
286 }
287
288 return ret;
289}
290
291
292static int
293max_line_length (char* msg)
294{
295 rp_screen *s = rp_current_screen;
296 size_t i;
297 size_t start;
298 int ret = 0;
299
300 /* Count each line and keep the length of the longest one. */
301 for(start=0, i=0; i <= strlen(msg); i++)
302 {
303 if(msg[i] == '\n' || msg[i] == '\0')
304 {
305 int current_width;
306
307 /* Check if this line is the longest so far. */
308 current_width = rp_text_width (s, msg + start, i - start);
309 if(current_width > ret)
310 {
311 ret = current_width;
312 }
313
314 /* Update the start of the new line. */
315 start = i + 1;
316 }
317 }
318
319 return ret;
320}
321
322static int
323pos_in_line (char* msg, int pos)
324{
325 int ret;
326 int i;
327
328 if(pos <= 0)
329 return 0;
330
331 /* Go backwards until we hit the beginning of the string or a new
332 line. */
333 ret = 0;
334 for(i=pos-1; i>=0; ret++, i--)
335 {
336 if(msg[i]=='\n')
337 break;
338 }
339
340 return ret;
341}
342
343static int
344line_beginning (char* msg, int pos)
345{
346 int ret = 0;
347 int i;
348
349 if(pos <= 0)
350 return 0;
351
352 /* Go backwards until we hit a new line or the beginning of the
353 string. */
354 for(i=pos-1; i>=0; --i)
355 {
356 if (msg[i]=='\n')
357 {
358 ret = i + 1;
359 break;
360 }
361 }
362
363 return ret;
364}
365
366static void
367draw_partial_string (rp_screen *s, char *msg, int len,
368 int x_offset, int y_offset, int style)
369{
370 rp_draw_string (s, s->bar_window, style,
371 defaults.bar_x_padding + x_offset,
372 defaults.bar_y_padding + FONT_ASCENT(s)
373 + y_offset * FONT_HEIGHT (s),
374 msg, len + 1);
375}
376
377#define REASON_NONE 0x00
378#define REASON_STYLE 0x01
379#define REASON_NEWLINE 0x02
380static void
381draw_string (rp_screen *s, char *msg, int mark_start, int mark_end)
382{
383 int i, start;
384 int x_offset, y_offset; /* Base coordinates where to print. */
385 int print_reason = REASON_NONE; /* Should we print something? */
386 int style = STYLE_NORMAL, next_style = STYLE_NORMAL;
387 int msg_len, part_len;
388
389 start = 0;
390 x_offset = y_offset = 0;
391 msg_len = strlen (msg);
392
393 /* Walk through the string, print each part. */
394 for (i = 0; i < msg_len; ++i)
395 {
396
397 /* Should we ignore style hints? */
398 if (mark_start != mark_end)
399 {
400 if (i == mark_start)
401 {
402 next_style = STYLE_INVERSE;
403 if (i > start)
404 print_reason |= REASON_STYLE;
405 }
406 else if (i == mark_end)
407 {
408 next_style = STYLE_NORMAL;
409 if (i > start)
410 print_reason |= REASON_STYLE;
411 }
412 }
413
414 if (msg[i] == '\n')
415 print_reason |= REASON_NEWLINE;
416
417 if (print_reason != REASON_NONE)
418 {
419 /* Strip the trailing newline if necessary. */
420 part_len = i - start - ((print_reason & REASON_NEWLINE) ? 1 : 0);
421
422 draw_partial_string (s, msg + start, part_len,
423 x_offset, y_offset, style);
424
425 /* Adjust coordinates. */
426 if (print_reason & REASON_NEWLINE)
427 {
428 x_offset = 0;
429 y_offset++;
430 /* Skip newline. */
431 start = i + 1;
432 }
433 else
434 {
435 x_offset += rp_text_width (s, msg + start, part_len);
436 start = i;
437 }
438
439 print_reason = REASON_NONE;
440 }
441 style = next_style;
442 }
443
444 part_len = i - start - 1;
445
446 /* Print the last line. */
447 draw_partial_string (s, msg + start, part_len, x_offset, y_offset, style);
448
449 XSync (dpy, False);
450}
451#undef REASON_NONE
452#undef REASON_STYLE
453#undef REASON_NEWLINE
454
455/* Move the marks if they are outside the string or if the start is
456 after the end. */
457static void
458correct_mark (int msg_len, int *mark_start, int *mark_end)
459{
460 /* Make sure the marks are inside the string. */
461 if (*mark_start < 0)
462 *mark_start = 0;
463
464 if (*mark_end < 0)
465 *mark_end = 0;
466
467 if (*mark_start > msg_len)
468 *mark_start = msg_len;
469
470 if (*mark_end > msg_len)
471 *mark_end = msg_len;
472
473 /* Make sure the marks aren't reversed. */
474 if (*mark_start > *mark_end)
475 {
476 int tmp;
477 tmp = *mark_start;
478 *mark_start = *mark_end;
479 *mark_end = tmp;
480 }
481
482}
483
484/* Raise the bar and put it in the right spot */
485static void
486prepare_bar (rp_screen *s, int width, int height)
487{
488 width = width < s->width ? width : s->width;
489 height = height < s->height ? height : s->height;
490 XMoveResizeWindow (dpy, s->bar_window,
491 bar_x (s, width), bar_y (s, height),
492 width, height);
493
494 /* Map the bar if needed */
495 if (!s->bar_is_raised)
496 {
497 s->bar_is_raised = BAR_IS_MESSAGE;
498 XMapRaised (dpy, s->bar_window);
499
500 /* Switch to the default colormap */
501 if (current_window())
502 XUninstallColormap (dpy, current_window()->colormap);
503 XInstallColormap (dpy, s->def_cmap);
504 }
505
506 XRaiseWindow (dpy, s->bar_window);
507 XClearWindow (dpy, s->bar_window);
508 XSync (dpy, False);
509}
510
511static void
512get_mark_box (char *msg, size_t mark_start, size_t mark_end,
513 int *x, int *y, int *width, int *height)
514{
515 rp_screen *s = rp_current_screen;
516 int start, end;
517 int mark_end_is_new_line = 0;
518 int start_line;
519 int end_line;
520 int start_pos_in_line;
521 int end_pos_in_line;
522 int start_line_beginning;
523 int end_line_beginning;
524
525 /* If the mark_end is on a new line or the end of the string, then
526 back it up one character. */
527 if (msg[mark_end-1] == '\n' || mark_end == strlen (msg))
528 {
529 mark_end--;
530 mark_end_is_new_line = 1;
531 }
532
533 start_line = count_lines(msg, mark_start);
534 end_line = count_lines(msg, mark_end);
535
536 start_pos_in_line = pos_in_line(msg, mark_start);
537 end_pos_in_line = pos_in_line(msg, mark_end);
538
539 start_line_beginning = line_beginning(msg, mark_start);
540 end_line_beginning = line_beginning(msg, mark_end);
541
542 PRINT_DEBUG (("start_line = %d, end_line = %d\n", start_line, end_line));
543 PRINT_DEBUG (("start_line_beginning = %d, end_line_beginning = %d\n",
544 start_line_beginning, end_line_beginning));
545
546 if (mark_start == 0 || start_pos_in_line == 0)
547 start = 0;
548 else
549 start = rp_text_width (s, &msg[start_line_beginning],
550 start_pos_in_line) + defaults.bar_x_padding;
551
552 end = rp_text_width (s, &msg[end_line_beginning],
553 end_pos_in_line) + defaults.bar_x_padding * 2;
554
555 if (mark_end != strlen (msg))
556 end -= defaults.bar_x_padding;
557
558 /* A little hack to highlight to the end of the line, if the
559 mark_end is at the end of a line. */
560 if (mark_end_is_new_line)
561 {
562 *width = max_line_length(msg) + defaults.bar_x_padding * 2;
563 }
564 else
565 {
566 *width = end - start;
567 }
568
569 *x = start;
570 *y = (start_line - 1) * FONT_HEIGHT (s) + defaults.bar_y_padding;
571 *height = (end_line - start_line + 1) * FONT_HEIGHT (s);
572}
573
574static void
575draw_box (rp_screen *s, int x, int y, int width, int height)
576{
577 XGCValues lgv;
578 GC lgc;
579 unsigned long mask;
580
581 lgv.foreground = rp_glob_screen.fg_color;
582 mask = GCForeground;
583 lgc = XCreateGC(dpy, s->root, mask, &lgv);
584
585 XFillRectangle (dpy, s->bar_window, lgc,
586 x, y, width, height);
587 XFreeGC (dpy, lgc);
588}
589
590static void
591draw_mark (rp_screen *s, char *msg, int mark_start, int mark_end)
592{
593 int x, y, width, height;
594
595 /* when this happens, there is no mark. */
596 if (mark_end == 0 || mark_start == mark_end)
597 return;
598
599 get_mark_box (msg, mark_start, mark_end,
600 &x, &y, &width, &height);
601 draw_box (s, x, y, width, height);
602}
603
604static void
605update_last_message (char *msg, int mark_start, int mark_end)
606{
607 free (last_msg);
608 last_msg = xstrdup (msg);
609 last_mark_start = mark_start;
610 last_mark_end = mark_end;
611}
612
613void
614marked_message (char *msg, int mark_start, int mark_end)
615{
616 /* Schedule the bar to be hidden after some amount of time. */
617 reset_alarm ();
618 marked_message_internal (msg, mark_start, mark_end);
619}
620
621static void
622marked_message_internal (char *msg, int mark_start, int mark_end)
623{
624 rp_screen *s = rp_current_screen;
625 int num_lines;
626 int width;
627 int height;
628
629 PRINT_DEBUG (("msg = %s\n", msg?msg:"NULL"));
630 PRINT_DEBUG (("mark_start = %d, mark_end = %d\n", mark_start, mark_end));
631
632 /* Calculate the width and height of the window. */
633 num_lines = count_lines (msg, strlen(msg));
634 width = defaults.bar_x_padding * 2 + max_line_length(msg);
635 height = FONT_HEIGHT (s) * num_lines + defaults.bar_y_padding * 2;
636
637 prepare_bar (s, width, height);
638
639 /* Draw the mark over the designated part of the string. */
640 correct_mark (strlen (msg), &mark_start, &mark_end);
641 draw_mark (s, msg, mark_start, mark_end);
642
643 draw_string (s, msg, mark_start, mark_end);
644
645 /* Keep a record of the message. */
646 update_last_message (msg, mark_start, mark_end);
647}
648
649/* Use this just to update the bar. show_last_message will draw it and
650 leave it up for a period of time. */
651void
652redraw_last_message (void)
653{
654 char *msg;
655
656 if (last_msg == NULL) return;
657
658 /* A little kludge to avoid last_msg in marked_message from being
659 strdup'd right after freeing the pointer. Note: in this case
660 marked_message's msg arg would have been the same as
661 last_msg. */
662 msg = xstrdup (last_msg);
663 marked_message_internal (msg, last_mark_start, last_mark_end);
664 free (msg);
665}
666
667void
668show_last_message (void)
669{
670 redraw_last_message();
671 reset_alarm();
672}
673
674/* Free any memory associated with the bar. */
675void
676free_bar (void)
677{
678 free (last_msg);
679 last_msg = NULL;
680}