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