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.
33 #include <X11/Xutil.h>
34 #include <X11/Xatom.h>
35 #include <X11/extensions/shape.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"
55 #define LED_POS_RADIUS 8
56 #define KNOB_CENTER_X 49
57 #define KNOB_CENTER_Y 48
61 typedef struct _Dockapp Dockapp
;
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, \
89 /* local prototypes */
90 static Cursor
create_null_cursor(Display
*x_display
);
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(void);
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
;
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
;
112 void dockapp_init(Display
*x_display
, Bool randr
)
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);
133 draw_knob(mixer_get_volume());
134 draw_slider(mixer_get_balance());
138 void knob_turn(float delta
)
140 mixer_set_volume_rel(delta
);
141 draw_knob(mixer_get_volume());
145 void slider_move(float delta
)
147 mixer_set_balance_rel(delta
);
148 draw_slider(mixer_get_balance());
152 int blit_string(const char *text
)
159 copy_xpm_area(0, 87, 256, 9, 0, 96);
161 for (i
= 0; text
[i
] || i
> 31; i
++) {
162 c
= toupper(text
[i
]);
164 copy_xpm_area(60, 67, 6, 8, k
, 96);
168 copy_xpm_area(66, 67, 6, 8, k
, 96);
172 copy_xpm_area(72, 67, 6, 8, k
, 96);
175 if (c
>= 'A' && c
<= 'Z') { /* letter */
177 copy_xpm_area(c
* 6, 77, 6, 8, k
, 96);
179 } else if (c
>= '0' && c
<= '9') { /* number */
181 copy_xpm_area(c
* 6, 67, 6, 8, k
, 96);
185 dockapp
.ctlength
= k
;
189 void scroll_text(int x
, int y
, int width
, bool reset
)
195 /* no text scrolling at all */
196 if (!config
.scrolltext
) {
199 copy_xpm_area(0, 96, 58, 9, x
, y
);
208 copy_xpm_area(0, 87, width
, 9, x
, y
);
215 if ((first
== 0) && pos
== 0) {
220 if (pos
< -(dockapp
.ctlength
)) {
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
);
238 void new_window(char *name
, int width
, int height
)
243 XSizeHints sizehints
;
244 XClassHint classhint
;
248 dockapp
.width
= width
;
249 dockapp
.height
= height
;
251 sizehints
.flags
= USSize
| USPosition
;
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
,
265 iconwin
= XCreateSimpleWindow(display
, win
, sizehints
.x
, sizehints
.y
,
266 sizehints
.width
, sizehints
.height
, 1, fg
,
269 XSetWMNormalHints(display
, win
, &sizehints
);
270 classhint
.res_name
= name
;
271 classhint
.res_class
= name
;
272 XSetClassHint(display
, win
, &classhint
);
277 | ButtonReleaseMask \
278 | PointerMotionMask \
280 | StructureNotifyMask
282 XSelectInput(display
, win
, INPUT_MASK
);
283 XSelectInput(display
, iconwin
, 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;
295 XCreateGC(display
, win
,
296 GCForeground
| GCBackground
| GCGraphicsExposures
,
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
);
316 XShapeCombineMask(display
, win
, ShapeBounding
, 0, 0, dockapp
.mask
,
318 XShapeCombineMask(display
, iconwin
, ShapeBounding
, 0, 0, dockapp
.mask
,
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
;
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 XMapWindow(display
, win
);
338 XRRCrtcInfo
*crtc_info_by_output_name(char *monitor
)
340 XRRScreenResources
*screen
= XRRGetScreenResources(display
, DefaultRootWindow(display
));
341 for (int i
= 0; i
< screen
->noutput
; i
++) {
342 XRROutputInfo
*output_info
= XRRGetOutputInfo(display
, screen
, screen
->outputs
[i
]);
343 if (!strcmp(monitor
, output_info
->name
)) {
344 XRRCrtcInfo
*crtc_info
= XRRGetCrtcInfo(display
, screen
, output_info
->crtc
);
345 XRRFreeOutputInfo(output_info
);
346 XRRFreeScreenResources(screen
);
349 XRRFreeOutputInfo(output_info
);
351 XRRFreeScreenResources(screen
);
355 XRRCrtcInfo
*crtc_info_by_output_number(int monitor
)
357 XRRScreenResources
*screen
= XRRGetScreenResources(display
, DefaultRootWindow(display
));
358 if (monitor
>= screen
->ncrtc
) {
359 fprintf(stderr
, "wmix:warning: Requested osd monitor number is out of range, clamping\n");
360 monitor
= screen
->ncrtc
- 1;
362 XRRCrtcInfo
*crtc_info
= XRRGetCrtcInfo(display
, screen
, screen
->crtcs
[monitor
]);
363 XRRFreeScreenResources(screen
);
367 void new_osd(int height
)
373 XSizeHints sizehints
;
374 XSetWindowAttributes xattributes
;
376 XFontStruct
*fs
= NULL
;
377 XRRCrtcInfo
*crtc_info
;
383 if (config
.osd_monitor_name
) {
384 crtc_info
= crtc_info_by_output_name(config
.osd_monitor_name
);
385 if (crtc_info
== NULL
) {
386 fprintf(stderr
, "wmix:warning: Requested osd monitor not found, falling back to default\n");
387 crtc_info
= crtc_info_by_output_number(0);
391 crtc_info
= crtc_info_by_output_number(config
.osd_monitor_number
);
394 width
= crtc_info
->width
- 200;
395 x
= crtc_info
->x
+ 100;
396 y
= crtc_info
->y
+ crtc_info
->height
- 120;
397 XRRFreeCrtcInfo(crtc_info
);
399 width
== dockapp
.osd_width
&&
400 x
== dockapp
.osd_x
&&
401 y
== dockapp
.osd_y
) {
402 // Nothing important has changed.
406 width
= DisplayWidth(display
, DefaultScreen(display
)) - 200;
408 y
= DisplayHeight(display
, 0) - 120;
411 sizehints
.flags
= USSize
| USPosition
;
414 sizehints
.width
= width
;
415 sizehints
.height
= height
;
416 xattributes
.save_under
= True
;
417 xattributes
.override_redirect
= True
;
418 xattributes
.cursor
= None
;
420 fg
= WhitePixel(display
, DefaultScreen(display
));
421 bg
= BlackPixel(display
, DefaultScreen(display
));
424 XDestroyWindow(display
, dockapp
.osd
);
425 osd
= XCreateSimpleWindow(display
, DefaultRootWindow(display
),
426 sizehints
.x
, sizehints
.y
, width
, height
,
429 XSetWMNormalHints(display
, osd
, &sizehints
);
430 XChangeWindowAttributes(display
, osd
, CWSaveUnder
| CWOverrideRedirect
,
432 XStoreName(display
, osd
, "osd");
433 XSelectInput(display
, osd
, ExposureMask
);
435 XChangeProperty(display
, osd
, XInternAtom(display
, "_WIN_LAYER", False
),
436 XA_CARDINAL
, 32, PropModeReplace
, (unsigned char *)&win_layer
, 1);
438 gcval
.foreground
= get_color(display
, config
.osd_color
);
439 gcval
.background
= bg
;
440 gcval
.graphics_exposures
= 0;
443 * -sony-fixed-medium-r-normal--24-170-100-100-c-120-iso8859-1
444 * -misc-fixed-medium-r-normal--36-*-75-75-c-*-iso8859-* */
446 /* try our cool scaled 36pt fixed font */
447 fs
= XLoadQueryFont(display
,
448 "-misc-fixed-medium-r-normal--36-*-75-75-c-*-iso8859-*");
451 /* they don't have it! */
452 /* try our next preferred font (100dpi sony) */
453 fprintf(stderr
, "Trying alternate font\n");
454 fs
= XLoadQueryFont(display
,
455 "-sony-fixed-medium-r-normal--24-*-100-100-c-*-iso8859-*");
457 /* they don't have the sony font either */
459 fprintf(stderr
, "Trying \"fixed\" font\n");
460 fs
= XLoadQueryFont(display
,
462 /* if they don't have the fixed font, we've got different kind
465 fprintf(stderr
, "Your X server is probably broken\n");
472 XFreeGC(display
, dockapp
.osd_gc
);
474 XCreateGC(display
, osd
,
475 GCForeground
| GCBackground
| GCGraphicsExposures
,
477 XSetFont(display
, gc
, fs
->fid
);
481 dockapp
.osd_width
= width
;
484 dockapp
.osd_mapped
= false;
487 void update_osd(float volume
, bool up
)
494 foo
= (dockapp
.osd_width
- 20) * volume
/ 20.0;
497 for (i
= 1; i
<= foo
; i
++)
498 XFillRectangle(display
, dockapp
.osd
, dockapp
.osd_gc
,
500 } else if (foo
< bar
) {
501 XClearArea(display
, dockapp
.osd
, ((foo
+1) * 20), 30,
502 ((bar
-foo
) * 20), 25, 1);
503 } else if (foo
> bar
) {
504 for (i
= (bar
> 0 ? bar
: 1); i
<= foo
; i
++)
505 XFillRectangle(display
, dockapp
.osd
, dockapp
.osd_gc
,
515 XUnmapWindow(display
, dockapp
.osd
);
517 dockapp
.osd_mapped
= false;
524 XMapRaised(display
, dockapp
.osd
);
525 XDrawString(display
, dockapp
.osd
, dockapp
.osd_gc
, 1, 25,
526 mixer_get_channel_name(), strlen(mixer_get_channel_name()));
527 update_osd(mixer_get_volume(), true);
529 dockapp
.osd_mapped
= true;
533 bool osd_mapped(void)
535 return dockapp
.osd_mapped
;
538 void set_cursor(int type
)
547 XDefineCursor(display
, win
, null_cursor
);
548 XDefineCursor(display
, iconwin
, null_cursor
);
551 XDefineCursor(display
, win
, norm_cursor
);
552 XDefineCursor(display
, iconwin
, norm_cursor
);
555 XDefineCursor(display
, win
, hand_cursor
);
556 XDefineCursor(display
, iconwin
, hand_cursor
);
559 XDefineCursor(display
, win
, bar_cursor
);
560 XDefineCursor(display
, iconwin
, bar_cursor
);
567 static void draw_stereo_led(void)
569 if (mixer_is_stereo()) /* stereo capable */
570 copy_xpm_area(78, 0, 9, 7, 28, 14); /* light up LCD */
571 else /* mono channel */
572 copy_xpm_area(78, 7, 9, 7, 28, 14); /* turn off LCD */
575 static void draw_rec_led(void)
577 if (mixer_is_rec()) /* record enabled */
578 copy_xpm_area(65, 0, 13, 7, 4, 14); /* Light up LCD */
579 else /* record disabled */
580 copy_xpm_area(65, 7, 13, 7, 4, 14); /* turn off LCD */
583 static void draw_mute_led(void)
585 if (mixer_is_muted()) /* mute */
586 copy_xpm_area(65, 14, 20, 7, 39, 14); /* light up LCD */
588 copy_xpm_area(65, 21, 20, 7, 39, 14); /* turn off LCD */
591 static void draw_percent(void)
593 int volume
= (int)(mixer_get_volume() * 100);
595 copy_xpm_area(0, 87, 18, 9, 41, 22); /* clear percentage */
599 copy_xpm_area((volume
/ 10) * 6, 67, 6, 9, 47, 22);
600 copy_xpm_area((volume
% 10) * 6, 67, 6, 9, 53, 22);
602 copy_xpm_area(6, 67, 6, 9, 41, 22);
603 copy_xpm_area(0, 67, 6, 9, 47, 22);
604 copy_xpm_area(0, 67, 6, 9, 53, 22);
608 static void draw_knob(float volume
)
610 float bearing
, led_x
, led_y
;
611 int led_topleft_x
, led_topleft_y
;
614 bearing
= (1.25 * PI
) - (1.5 * PI
) * volume
;
616 led_x
= KNOB_CENTER_X
+ LED_POS_RADIUS
* cos(bearing
);
617 led_y
= KNOB_CENTER_Y
- LED_POS_RADIUS
* sin(bearing
);
619 led_topleft_x
= (int)(led_x
- (LED_WIDTH
/ 2.0) + 0.5);
620 led_topleft_y
= (int)(led_y
- (LED_HEIGHT
/ 2.0) + 0.5);
622 /* clear previous knob picture */
623 copy_xpm_area(87, 0, 26, 26, 36, 35);
625 if (mixer_is_muted())
626 led_pixmap
= led_off_pixmap
;
628 led_pixmap
= led_on_pixmap
;
630 XCopyArea(display
, led_pixmap
, dockapp
.pixmap
, dockapp
.gc
,
631 0, 0, LED_WIDTH
, LED_HEIGHT
, led_topleft_x
, led_topleft_y
);
635 static void draw_slider(float offset
)
637 int x
= (offset
* 50) / 5;
639 copy_xpm_area(65, 45, 27, 20, 4, 40); /* repair region. move */
640 copy_xpm_area(65, 29, 7, 15, 14 + x
, 43); /* slider */
643 static Cursor
create_null_cursor(Display
*x_display
)
651 cursor_mask
= XCreatePixmap(x_display
, DefaultRootWindow(x_display
), 1, 1, 1);
652 gcval
.function
= GXclear
;
653 gc
= XCreateGC(x_display
, cursor_mask
, GCFunction
, &gcval
);
654 XFillRectangle(x_display
, cursor_mask
, gc
, 0, 0, 1, 1);
655 dummy_color
.pixel
= 0;
657 dummy_color
.flags
= 04;
658 cursor
= XCreatePixmapCursor(x_display
, cursor_mask
, cursor_mask
,
659 &dummy_color
, &dummy_color
, 0, 0);
660 XFreePixmap(x_display
, cursor_mask
);
661 XFreeGC(x_display
, gc
);
666 unsigned long get_color(Display
*display
, char *color_name
)
669 XWindowAttributes winattr
;
672 XGetWindowAttributes(display
,
673 RootWindow(display
, DefaultScreen(display
)), &winattr
);
675 status
= XParseColor(display
, winattr
.colormap
, color_name
, &color
);
677 fprintf(stderr
, "wmix:warning: Could not get color \"%s\" for OSD, falling back to default\n", color_name
);
679 if (color_name
!= default_osd_color
)
680 status
= XParseColor(display
, winattr
.colormap
, default_osd_color
, &color
);
682 return WhitePixel(display
, DefaultScreen(display
));
685 color
.flags
= DoRed
| DoGreen
| DoBlue
;
686 XAllocColor(display
, winattr
.colormap
, &color
);