wmusic: Drop "-1.0" from playerctl pkg-config check.
[dockapps.git] / wmix / ui_x.c
blob40b479d20f1b3b9375f67a6f1ac7f3f577d5f894
1 /* WMix 3.0 -- a mixer using the OSS mixer API.
2 * Copyright (C) 2000, 2001
3 * Daniel Richard G. <skunk@mit.edu>,
4 * timecop <timecop@japan.co.jp>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <ctype.h>
29 #include <math.h>
31 #include <X11/X.h>
32 #include <X11/Xlib.h>
33 #include <X11/Xutil.h>
34 #include <X11/Xatom.h>
35 #include <X11/extensions/shape.h>
36 #include <X11/xpm.h>
37 #include <X11/cursorfont.h>
38 #include <X11/extensions/Xrandr.h>
40 #include "include/master.xpm"
41 #include "include/led-on.xpm"
42 #include "include/led-off.xpm"
44 #include "include/common.h"
45 #include "include/misc.h"
46 #include "include/mixer.h"
47 #include "include/ui_x.h"
48 #include "include/config.h"
51 #ifndef PI
52 #define PI M_PI
53 #endif
55 #define LED_POS_RADIUS 8
56 #define KNOB_CENTER_X 49
57 #define KNOB_CENTER_Y 48
58 #define LED_WIDTH 6
59 #define LED_HEIGHT 6
61 typedef struct _Dockapp Dockapp;
63 struct _Dockapp {
64 int width;
65 int height;
66 Pixmap pixmap;
67 Pixmap mask;
68 GC gc;
69 int ctlength;
71 Window osd;
72 GC osd_gc;
73 int osd_width;
74 int osd_x;
75 int osd_y;
76 bool osd_mapped;
80 static Pixmap led_on_pixmap;
81 static Pixmap led_on_mask;
82 static Pixmap led_off_pixmap;
83 static Pixmap led_off_mask;
85 #define copy_xpm_area(x, y, w, h, dx, dy) \
86 XCopyArea(display, dockapp.pixmap, dockapp.pixmap, dockapp.gc, \
87 x, y, w, h, dx, dy)
89 /* local prototypes */
90 static Cursor create_null_cursor(Display *x_display);
92 /* ui stuff */
93 static void draw_stereo_led(void);
94 static void draw_rec_led(void);
95 static void draw_mute_led(void);
96 static void draw_percent(float volume);
97 static void draw_knob(float volume);
98 static void draw_slider(float offset);
100 /* global variables */
101 static Dockapp dockapp;
102 static Display *display;
103 static Window win;
104 static Window iconwin;
105 static Cursor hand_cursor;
106 static Cursor null_cursor;
107 static Cursor norm_cursor;
108 static Cursor bar_cursor;
109 static Bool have_randr;
111 /* public methods */
112 void dockapp_init(Display *x_display, Bool randr)
114 display = x_display;
115 have_randr = randr;
116 dockapp.osd = 0;
117 dockapp.gc = 0;
120 void redraw_window(void)
122 XCopyArea(display, dockapp.pixmap, iconwin, dockapp.gc,
123 0, 0, dockapp.width, dockapp.height, 0, 0);
124 XCopyArea(display, dockapp.pixmap, win, dockapp.gc,
125 0, 0, dockapp.width, dockapp.height, 0, 0);
128 void ui_update(void)
130 draw_stereo_led();
131 draw_rec_led();
132 draw_mute_led();
133 draw_knob(mixer_get_volume());
134 draw_slider(mixer_get_balance());
135 redraw_window();
138 void knob_turn(float delta)
140 mixer_set_volume_rel(delta);
141 draw_knob(mixer_get_volume());
142 redraw_window();
145 void slider_move(float delta)
147 mixer_set_balance_rel(delta);
148 draw_slider(mixer_get_balance());
149 redraw_window();
152 int blit_string(const char *text)
154 register int i;
155 register int c;
156 register int k;
158 k = 0;
159 copy_xpm_area(0, 87, 256, 9, 0, 96);
161 for (i = 0; text[i] || i > 31; i++) {
162 c = toupper(text[i]);
163 if (c == '-') {
164 copy_xpm_area(60, 67, 6, 8, k, 96);
165 k += 6;
167 if (c == ' ') {
168 copy_xpm_area(66, 67, 6, 8, k, 96);
169 k += 6;
171 if (c == '.') {
172 copy_xpm_area(72, 67, 6, 8, k, 96);
173 k += 6;
175 if (c >= 'A' && c <= 'Z') { /* letter */
176 c = c - 'A';
177 copy_xpm_area(c * 6, 77, 6, 8, k, 96);
178 k += 6;
179 } else if (c >= '0' && c <= '9') { /* number */
180 c = c - '0';
181 copy_xpm_area(c * 6, 67, 6, 8, k, 96);
182 k += 6;
185 dockapp.ctlength = k;
186 return k;
189 void scroll_text(int x, int y, int width, bool reset)
191 static int pos;
192 static int first;
193 static int stop;
195 /* no text scrolling at all */
196 if (!config.scrolltext) {
197 if (!reset)
198 return;
199 copy_xpm_area(0, 96, 58, 9, x, y);
200 redraw_window();
201 return;
204 if (reset) {
205 pos = 0;
206 first = 0;
207 stop = 0;
208 copy_xpm_area(0, 87, width, 9, x, y);
211 if (stop) {
212 return;
215 if ((first == 0) && pos == 0) {
216 pos = width;
217 first = 1;
220 if (pos < -(dockapp.ctlength)) {
221 first = 1;
222 pos = width;
223 stop = 1;
224 return;
226 pos -= 2;
228 if (pos > 0) {
229 copy_xpm_area(0, 87, pos, 9, x, y); /* clear */
230 copy_xpm_area(0, 96, width - pos, 9, x + pos, y);
231 } else { /* don't need to clear, already in text */
232 copy_xpm_area(abs(pos), 96, width, 9, x, y);
234 redraw_window();
235 return;
238 void new_window(char *name, int width, int height, int argc, char **argv)
240 XpmAttributes attr;
241 Pixel fg, bg;
242 XGCValues gcval;
243 XSizeHints sizehints;
244 XClassHint classhint;
245 XWMHints wmhints;
246 XTextProperty wname;
248 dockapp.width = width;
249 dockapp.height = height;
251 sizehints.flags = USSize | USPosition;
252 sizehints.x = 0;
253 sizehints.y = 0;
254 sizehints.width = width;
255 sizehints.height = height;
257 fg = BlackPixel(display, DefaultScreen(display));
258 bg = WhitePixel(display, DefaultScreen(display));
260 win = XCreateSimpleWindow(display, DefaultRootWindow(display),
261 sizehints.x, sizehints.y,
262 sizehints.width, sizehints.height, 1, fg,
263 bg);
265 iconwin = XCreateSimpleWindow(display, win, sizehints.x, sizehints.y,
266 sizehints.width, sizehints.height, 1, fg,
267 bg);
269 XSetWMNormalHints(display, win, &sizehints);
270 classhint.res_name = name;
271 classhint.res_class = name;
272 XSetClassHint(display, win, &classhint);
274 #define INPUT_MASK \
275 ButtonPressMask \
276 | ExposureMask \
277 | ButtonReleaseMask \
278 | PointerMotionMask \
279 | LeaveWindowMask \
280 | StructureNotifyMask
282 XSelectInput(display, win, INPUT_MASK);
283 XSelectInput(display, iconwin, INPUT_MASK);
285 #undef INPUT_MASk
287 XStringListToTextProperty(&name, 1, &wname);
288 XSetWMName(display, win, &wname);
290 gcval.foreground = fg;
291 gcval.background = bg;
292 gcval.graphics_exposures = 0;
294 dockapp.gc =
295 XCreateGC(display, win,
296 GCForeground | GCBackground | GCGraphicsExposures,
297 &gcval);
299 attr.exactColors = 0;
300 attr.alloc_close_colors = 1;
301 attr.closeness = 30000;
302 attr.valuemask = (XpmExactColors | XpmAllocCloseColors | XpmCloseness);
303 if ((XpmCreatePixmapFromData(display, DefaultRootWindow(display),
304 master_xpm, &dockapp.pixmap, &dockapp.mask,
305 &attr) != XpmSuccess) ||
306 (XpmCreatePixmapFromData(display, DefaultRootWindow(display),
307 led_on_xpm, &led_on_pixmap, &led_on_mask,
308 &attr) != XpmSuccess) ||
309 (XpmCreatePixmapFromData(display, DefaultRootWindow(display),
310 led_off_xpm, &led_off_pixmap, &led_off_mask,
311 &attr) != XpmSuccess)) {
312 fputs("Cannot allocate colors for the dockapp pixmaps!\n", stderr);
313 exit(EXIT_FAILURE);
316 XShapeCombineMask(display, win, ShapeBounding, 0, 0, dockapp.mask,
317 ShapeSet);
318 XShapeCombineMask(display, iconwin, ShapeBounding, 0, 0, dockapp.mask,
319 ShapeSet);
321 wmhints.initial_state = WithdrawnState;
322 wmhints.icon_window = iconwin;
323 wmhints.icon_x = sizehints.x;
324 wmhints.icon_y = sizehints.y;
325 wmhints.window_group = win;
326 wmhints.flags =
327 StateHint | IconWindowHint | IconPositionHint | WindowGroupHint;
328 XSetWMHints(display, win, &wmhints);
330 hand_cursor = XCreateFontCursor(display, XC_hand2);
331 norm_cursor = XCreateFontCursor(display, XC_left_ptr);
332 bar_cursor = XCreateFontCursor(display, XC_sb_up_arrow);
333 null_cursor = create_null_cursor(display);
335 XSetCommand(display, win, argv, argc);
337 XMapWindow(display, win);
340 XRRCrtcInfo *crtc_info_by_output_name(char *monitor)
342 XRRScreenResources *screen = XRRGetScreenResources(display, DefaultRootWindow(display));
343 for (int i = 0; i < screen->noutput; i++) {
344 XRROutputInfo *output_info = XRRGetOutputInfo(display, screen, screen->outputs[i]);
345 if (!strcmp(monitor, output_info->name)) {
346 XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(display, screen, output_info->crtc);
347 XRRFreeOutputInfo(output_info);
348 XRRFreeScreenResources(screen);
349 return crtc_info;
351 XRRFreeOutputInfo(output_info);
353 XRRFreeScreenResources(screen);
354 return NULL;
357 XRRCrtcInfo *crtc_info_by_output_number(int monitor)
359 XRRScreenResources *screen = XRRGetScreenResources(display, DefaultRootWindow(display));
360 if (monitor >= screen->ncrtc) {
361 fprintf(stderr, "wmix:warning: Requested osd monitor number is out of range, clamping\n");
362 monitor = screen->ncrtc - 1;
364 XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(display, screen, screen->crtcs[monitor]);
365 XRRFreeScreenResources(screen);
366 return crtc_info;
369 void new_osd(int height)
371 Window osd;
372 Pixel fg, bg;
373 XGCValues gcval;
374 GC gc;
375 XSizeHints sizehints;
376 XSetWindowAttributes xattributes;
377 int win_layer = 6;
378 XFontStruct *fs = NULL;
379 XRRCrtcInfo *crtc_info;
380 int width;
381 int x;
382 int y;
384 if (have_randr) {
385 if (config.osd_monitor_name) {
386 crtc_info = crtc_info_by_output_name(config.osd_monitor_name);
387 if (crtc_info == NULL) {
388 fprintf(stderr, "wmix:warning: Requested osd monitor not found, falling back to default\n");
389 crtc_info = crtc_info_by_output_number(0);
392 else {
393 crtc_info = crtc_info_by_output_number(config.osd_monitor_number);
396 width = crtc_info->width - 200;
397 x = crtc_info->x + 100;
398 y = crtc_info->y + crtc_info->height - 120;
399 XRRFreeCrtcInfo(crtc_info);
400 if (dockapp.osd &&
401 width == dockapp.osd_width &&
402 x == dockapp.osd_x &&
403 y == dockapp.osd_y) {
404 // Nothing important has changed.
405 return;
407 } else {
408 width = DisplayWidth(display, DefaultScreen(display)) - 200;
409 x = 100;
410 y = DisplayHeight(display, 0) - 120;
413 sizehints.flags = USSize | USPosition;
414 sizehints.x = x;
415 sizehints.y = y;
416 sizehints.width = width;
417 sizehints.height = height;
418 xattributes.save_under = True;
419 xattributes.override_redirect = True;
420 xattributes.cursor = None;
422 fg = WhitePixel(display, DefaultScreen(display));
423 bg = BlackPixel(display, DefaultScreen(display));
425 if (dockapp.osd)
426 XDestroyWindow(display, dockapp.osd);
427 osd = XCreateSimpleWindow(display, DefaultRootWindow(display),
428 sizehints.x, sizehints.y, width, height,
429 0, fg, bg);
431 XSetWMNormalHints(display, osd, &sizehints);
432 XChangeWindowAttributes(display, osd, CWSaveUnder | CWOverrideRedirect,
433 &xattributes);
434 XStoreName(display, osd, "osd");
435 XSelectInput(display, osd, ExposureMask);
437 XChangeProperty(display, osd, XInternAtom(display, "_WIN_LAYER", False),
438 XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&win_layer, 1);
440 gcval.foreground = get_color(display, config.osd_color);
441 gcval.background = bg;
442 gcval.graphics_exposures = 0;
445 * -sony-fixed-medium-r-normal--24-170-100-100-c-120-iso8859-1
446 * -misc-fixed-medium-r-normal--36-*-75-75-c-*-iso8859-* */
448 /* try our cool scaled 36pt fixed font */
449 fs = XLoadQueryFont(display,
450 "-misc-fixed-medium-r-normal--36-*-75-75-c-*-iso8859-*");
452 if (fs == NULL) {
453 /* they don't have it! */
454 /* try our next preferred font (100dpi sony) */
455 fprintf(stderr, "Trying alternate font\n");
456 fs = XLoadQueryFont(display,
457 "-sony-fixed-medium-r-normal--24-*-100-100-c-*-iso8859-*");
459 /* they don't have the sony font either */
460 if (fs == NULL) {
461 fprintf(stderr, "Trying \"fixed\" font\n");
462 fs = XLoadQueryFont(display,
463 "fixed");
464 /* if they don't have the fixed font, we've got different kind
465 * of problems */
466 if (fs == NULL) {
467 fprintf(stderr, "Your X server is probably broken\n");
468 exit(1);
473 if (dockapp.osd_gc)
474 XFreeGC(display, dockapp.osd_gc);
475 gc =
476 XCreateGC(display, osd,
477 GCForeground | GCBackground | GCGraphicsExposures,
478 &gcval);
479 XSetFont(display, gc, fs->fid);
481 dockapp.osd = osd;
482 dockapp.osd_gc = gc;
483 dockapp.osd_width = width;
484 dockapp.osd_x = x;
485 dockapp.osd_y = y;
486 dockapp.osd_mapped = false;
489 void update_osd(float volume, bool up)
491 int i;
492 int foo;
493 static int bar;
495 if (config.osd) {
496 foo = (dockapp.osd_width - 20) * volume / 20.0;
498 if (up) {
499 for (i = 1; i <= foo; i++)
500 XFillRectangle(display, dockapp.osd, dockapp.osd_gc,
501 i * 20, 30, 5, 25);
502 } else if (foo < bar) {
503 XClearArea(display, dockapp.osd, ((foo+1) * 20), 30,
504 ((bar-foo) * 20), 25, 1);
505 } else if (foo > bar) {
506 for (i = (bar > 0 ? bar : 1); i <= foo; i++)
507 XFillRectangle(display, dockapp.osd, dockapp.osd_gc,
508 i * 20, 30, 5, 25);
510 bar = foo;
514 void unmap_osd(void)
516 if (config.osd) {
517 XUnmapWindow(display, dockapp.osd);
518 XFlush(display);
519 dockapp.osd_mapped = false;
523 void map_osd(void)
525 if (config.osd) {
526 XMapRaised(display, dockapp.osd);
527 XDrawString(display, dockapp.osd, dockapp.osd_gc, 1, 25,
528 mixer_get_channel_name(), strlen(mixer_get_channel_name()));
529 update_osd(mixer_get_volume(), true);
530 XFlush(display);
531 dockapp.osd_mapped = true;
535 bool osd_mapped(void)
537 return dockapp.osd_mapped;
540 void set_cursor(int type)
542 static int oldtype;
544 if (oldtype == type)
545 return;
547 switch (type) {
548 case NULL_CURSOR:
549 XDefineCursor(display, win, null_cursor);
550 XDefineCursor(display, iconwin, null_cursor);
551 break;
552 case NORMAL_CURSOR:
553 XDefineCursor(display, win, norm_cursor);
554 XDefineCursor(display, iconwin, norm_cursor);
555 break;
556 case HAND_CURSOR:
557 XDefineCursor(display, win, hand_cursor);
558 XDefineCursor(display, iconwin, hand_cursor);
559 break;
560 case BAR_CURSOR:
561 XDefineCursor(display, win, bar_cursor);
562 XDefineCursor(display, iconwin, bar_cursor);
563 break;
565 oldtype = type;
568 /* private */
569 static void draw_stereo_led(void)
571 if (mixer_is_stereo()) /* stereo capable */
572 copy_xpm_area(78, 0, 9, 7, 28, 14); /* light up LCD */
573 else /* mono channel */
574 copy_xpm_area(78, 7, 9, 7, 28, 14); /* turn off LCD */
577 static void draw_rec_led(void)
579 if (mixer_is_rec()) /* record enabled */
580 copy_xpm_area(65, 0, 13, 7, 4, 14); /* Light up LCD */
581 else /* record disabled */
582 copy_xpm_area(65, 7, 13, 7, 4, 14); /* turn off LCD */
585 static void draw_mute_led(void)
587 if (mixer_is_muted()) /* mute */
588 copy_xpm_area(65, 14, 20, 7, 39, 14); /* light up LCD */
589 else /* unmute */
590 copy_xpm_area(65, 21, 20, 7, 39, 14); /* turn off LCD */
593 static void draw_percent(float volume)
595 int vol = (int)(volume*100 + 0.5);
597 copy_xpm_area(0, 87, 18, 9, 41, 22); /* clear percentage */
599 if (vol < 100) {
600 if (vol >= 10)
601 copy_xpm_area((vol / 10) * 6, 67, 6, 9, 47, 22);
602 copy_xpm_area((vol % 10) * 6, 67, 6, 9, 53, 22);
603 } else {
604 copy_xpm_area(6, 67, 6, 9, 41, 22);
605 copy_xpm_area(0, 67, 6, 9, 47, 22);
606 copy_xpm_area(0, 67, 6, 9, 53, 22);
610 static void draw_knob(float volume)
612 float bearing, led_x, led_y;
613 int led_topleft_x, led_topleft_y;
614 Pixmap led_pixmap;
616 bearing = (1.25 * PI) - (1.5 * PI) * volume;
618 led_x = KNOB_CENTER_X + LED_POS_RADIUS * cos(bearing);
619 led_y = KNOB_CENTER_Y - LED_POS_RADIUS * sin(bearing);
621 led_topleft_x = (int)(led_x - (LED_WIDTH / 2.0) + 0.5);
622 led_topleft_y = (int)(led_y - (LED_HEIGHT / 2.0) + 0.5);
624 /* clear previous knob picture */
625 copy_xpm_area(87, 0, 26, 26, 36, 35);
627 if (mixer_is_muted())
628 led_pixmap = led_off_pixmap;
629 else
630 led_pixmap = led_on_pixmap;
632 XCopyArea(display, led_pixmap, dockapp.pixmap, dockapp.gc,
633 0, 0, LED_WIDTH, LED_HEIGHT, led_topleft_x, led_topleft_y);
634 draw_percent(volume);
637 static void draw_slider(float offset)
639 int x = (offset * 50) / 5;
641 copy_xpm_area(65, 45, 27, 20, 4, 40); /* repair region. move */
642 copy_xpm_area(65, 29, 7, 15, 14 + x, 43); /* slider */
645 static Cursor create_null_cursor(Display *x_display)
647 Pixmap cursor_mask;
648 XGCValues gcval;
649 GC gc;
650 XColor dummy_color;
651 Cursor cursor;
653 cursor_mask = XCreatePixmap(x_display, DefaultRootWindow(x_display), 1, 1, 1);
654 gcval.function = GXclear;
655 gc = XCreateGC(x_display, cursor_mask, GCFunction, &gcval);
656 XFillRectangle(x_display, cursor_mask, gc, 0, 0, 1, 1);
657 dummy_color.pixel = 0;
658 dummy_color.red = 0;
659 dummy_color.flags = 04;
660 cursor = XCreatePixmapCursor(x_display, cursor_mask, cursor_mask,
661 &dummy_color, &dummy_color, 0, 0);
662 XFreePixmap(x_display, cursor_mask);
663 XFreeGC(x_display, gc);
665 return cursor;
668 unsigned long get_color(Display *display, char *color_name)
670 XColor color;
671 XWindowAttributes winattr;
672 Status status;
674 XGetWindowAttributes(display,
675 RootWindow(display, DefaultScreen(display)), &winattr);
677 status = XParseColor(display, winattr.colormap, color_name, &color);
678 if (status == 0) {
679 fprintf(stderr, "wmix:warning: Could not get color \"%s\" for OSD, falling back to default\n", color_name);
681 if (color_name != default_osd_color)
682 status = XParseColor(display, winattr.colormap, default_osd_color, &color);
683 if (status == 0)
684 return WhitePixel(display, DefaultScreen(display));
687 color.flags = DoRed | DoGreen | DoBlue;
688 XAllocColor(display, winattr.colormap, &color);
690 return color.pixel;
693 void ui_rrnotify()
695 new_osd(60);