/* WMix 3.0 -- a mixer using the OSS mixer API. * Copyright (C) 2000, 2001 * Daniel Richard G. , * timecop * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "include/master.xpm" #include "include/led-on.xpm" #include "include/led-off.xpm" #include "include/common.h" #include "include/misc.h" #include "include/mixer.h" #include "include/ui_x.h" #include "include/config.h" #ifndef PI #define PI M_PI #endif #define LED_POS_RADIUS 8 #define KNOB_CENTER_X 49 #define KNOB_CENTER_Y 48 #define LED_WIDTH 6 #define LED_HEIGHT 6 typedef struct _Dockapp Dockapp; struct _Dockapp { int width; int height; Pixmap pixmap; Pixmap mask; GC gc; int ctlength; Window osd; GC osd_gc; int osd_width; int osd_x; int osd_y; bool osd_mapped; }; static Pixmap led_on_pixmap; static Pixmap led_on_mask; static Pixmap led_off_pixmap; static Pixmap led_off_mask; #define copy_xpm_area(x, y, w, h, dx, dy) \ XCopyArea(display, dockapp.pixmap, dockapp.pixmap, dockapp.gc, \ x, y, w, h, dx, dy) /* local prototypes */ static Cursor create_null_cursor(Display *x_display); /* ui stuff */ static void draw_stereo_led(void); static void draw_rec_led(void); static void draw_mute_led(void); static void draw_percent(void); static void draw_knob(float volume); static void draw_slider(float offset); /* global variables */ static Dockapp dockapp; static Display *display; static Window win; static Window iconwin; static Cursor hand_cursor; static Cursor null_cursor; static Cursor norm_cursor; static Cursor bar_cursor; static Bool have_randr; /* public methods */ void dockapp_init(Display *x_display, Bool randr) { display = x_display; have_randr = randr; dockapp.osd = 0; dockapp.gc = 0; } void redraw_window(void) { XCopyArea(display, dockapp.pixmap, iconwin, dockapp.gc, 0, 0, dockapp.width, dockapp.height, 0, 0); XCopyArea(display, dockapp.pixmap, win, dockapp.gc, 0, 0, dockapp.width, dockapp.height, 0, 0); } void ui_update(void) { draw_stereo_led(); draw_rec_led(); draw_mute_led(); draw_knob(mixer_get_volume()); draw_slider(mixer_get_balance()); redraw_window(); } void knob_turn(float delta) { mixer_set_volume_rel(delta); draw_knob(mixer_get_volume()); redraw_window(); } void slider_move(float delta) { mixer_set_balance_rel(delta); draw_slider(mixer_get_balance()); redraw_window(); } int blit_string(const char *text) { register int i; register int c; register int k; k = 0; copy_xpm_area(0, 87, 256, 9, 0, 96); for (i = 0; text[i] || i > 31; i++) { c = toupper(text[i]); if (c == '-') { copy_xpm_area(60, 67, 6, 8, k, 96); k += 6; } if (c == ' ') { copy_xpm_area(66, 67, 6, 8, k, 96); k += 6; } if (c == '.') { copy_xpm_area(72, 67, 6, 8, k, 96); k += 6; } if (c >= 'A' && c <= 'Z') { /* letter */ c = c - 'A'; copy_xpm_area(c * 6, 77, 6, 8, k, 96); k += 6; } else if (c >= '0' && c <= '9') { /* number */ c = c - '0'; copy_xpm_area(c * 6, 67, 6, 8, k, 96); k += 6; } } dockapp.ctlength = k; return k; } void scroll_text(int x, int y, int width, bool reset) { static int pos; static int first; static int stop; /* no text scrolling at all */ if (!config.scrolltext) { if (!reset) return; copy_xpm_area(0, 96, 58, 9, x, y); redraw_window(); return; } if (reset) { pos = 0; first = 0; stop = 0; copy_xpm_area(0, 87, width, 9, x, y); } if (stop) { return; } if ((first == 0) && pos == 0) { pos = width; first = 1; } if (pos < -(dockapp.ctlength)) { first = 1; pos = width; stop = 1; return; } pos -= 2; if (pos > 0) { copy_xpm_area(0, 87, pos, 9, x, y); /* clear */ copy_xpm_area(0, 96, width - pos, 9, x + pos, y); } else { /* don't need to clear, already in text */ copy_xpm_area(abs(pos), 96, width, 9, x, y); } redraw_window(); return; } void new_window(char *name, int width, int height, int argc, char **argv) { XpmAttributes attr; Pixel fg, bg; XGCValues gcval; XSizeHints sizehints; XClassHint classhint; XWMHints wmhints; XTextProperty wname; dockapp.width = width; dockapp.height = height; sizehints.flags = USSize | USPosition; sizehints.x = 0; sizehints.y = 0; sizehints.width = width; sizehints.height = height; fg = BlackPixel(display, DefaultScreen(display)); bg = WhitePixel(display, DefaultScreen(display)); win = XCreateSimpleWindow(display, DefaultRootWindow(display), sizehints.x, sizehints.y, sizehints.width, sizehints.height, 1, fg, bg); iconwin = XCreateSimpleWindow(display, win, sizehints.x, sizehints.y, sizehints.width, sizehints.height, 1, fg, bg); XSetWMNormalHints(display, win, &sizehints); classhint.res_name = name; classhint.res_class = name; XSetClassHint(display, win, &classhint); #define INPUT_MASK \ ButtonPressMask \ | ExposureMask \ | ButtonReleaseMask \ | PointerMotionMask \ | LeaveWindowMask \ | StructureNotifyMask XSelectInput(display, win, INPUT_MASK); XSelectInput(display, iconwin, INPUT_MASK); #undef INPUT_MASk XStringListToTextProperty(&name, 1, &wname); XSetWMName(display, win, &wname); gcval.foreground = fg; gcval.background = bg; gcval.graphics_exposures = 0; dockapp.gc = XCreateGC(display, win, GCForeground | GCBackground | GCGraphicsExposures, &gcval); attr.exactColors = 0; attr.alloc_close_colors = 1; attr.closeness = 30000; attr.valuemask = (XpmExactColors | XpmAllocCloseColors | XpmCloseness); if ((XpmCreatePixmapFromData(display, DefaultRootWindow(display), master_xpm, &dockapp.pixmap, &dockapp.mask, &attr) != XpmSuccess) || (XpmCreatePixmapFromData(display, DefaultRootWindow(display), led_on_xpm, &led_on_pixmap, &led_on_mask, &attr) != XpmSuccess) || (XpmCreatePixmapFromData(display, DefaultRootWindow(display), led_off_xpm, &led_off_pixmap, &led_off_mask, &attr) != XpmSuccess)) { fputs("Cannot allocate colors for the dockapp pixmaps!\n", stderr); exit(EXIT_FAILURE); } XShapeCombineMask(display, win, ShapeBounding, 0, 0, dockapp.mask, ShapeSet); XShapeCombineMask(display, iconwin, ShapeBounding, 0, 0, dockapp.mask, ShapeSet); wmhints.initial_state = WithdrawnState; wmhints.icon_window = iconwin; wmhints.icon_x = sizehints.x; wmhints.icon_y = sizehints.y; wmhints.window_group = win; wmhints.flags = StateHint | IconWindowHint | IconPositionHint | WindowGroupHint; XSetWMHints(display, win, &wmhints); hand_cursor = XCreateFontCursor(display, XC_hand2); norm_cursor = XCreateFontCursor(display, XC_left_ptr); bar_cursor = XCreateFontCursor(display, XC_sb_up_arrow); null_cursor = create_null_cursor(display); XSetCommand(display, win, argv, argc); XMapWindow(display, win); } XRRCrtcInfo *crtc_info_by_output_name(char *monitor) { XRRScreenResources *screen = XRRGetScreenResources(display, DefaultRootWindow(display)); for (int i = 0; i < screen->noutput; i++) { XRROutputInfo *output_info = XRRGetOutputInfo(display, screen, screen->outputs[i]); if (!strcmp(monitor, output_info->name)) { XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(display, screen, output_info->crtc); XRRFreeOutputInfo(output_info); XRRFreeScreenResources(screen); return crtc_info; } XRRFreeOutputInfo(output_info); } XRRFreeScreenResources(screen); return NULL; } XRRCrtcInfo *crtc_info_by_output_number(int monitor) { XRRScreenResources *screen = XRRGetScreenResources(display, DefaultRootWindow(display)); if (monitor >= screen->ncrtc) { fprintf(stderr, "wmix:warning: Requested osd monitor number is out of range, clamping\n"); monitor = screen->ncrtc - 1; } XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(display, screen, screen->crtcs[monitor]); XRRFreeScreenResources(screen); return crtc_info; } void new_osd(int height) { Window osd; Pixel fg, bg; XGCValues gcval; GC gc; XSizeHints sizehints; XSetWindowAttributes xattributes; int win_layer = 6; XFontStruct *fs = NULL; XRRCrtcInfo *crtc_info; int width; int x; int y; if (have_randr) { if (config.osd_monitor_name) { crtc_info = crtc_info_by_output_name(config.osd_monitor_name); if (crtc_info == NULL) { fprintf(stderr, "wmix:warning: Requested osd monitor not found, falling back to default\n"); crtc_info = crtc_info_by_output_number(0); } } else { crtc_info = crtc_info_by_output_number(config.osd_monitor_number); } width = crtc_info->width - 200; x = crtc_info->x + 100; y = crtc_info->y + crtc_info->height - 120; XRRFreeCrtcInfo(crtc_info); if (dockapp.osd && width == dockapp.osd_width && x == dockapp.osd_x && y == dockapp.osd_y) { // Nothing important has changed. return; } } else { width = DisplayWidth(display, DefaultScreen(display)) - 200; x = 100; y = DisplayHeight(display, 0) - 120; } sizehints.flags = USSize | USPosition; sizehints.x = x; sizehints.y = y; sizehints.width = width; sizehints.height = height; xattributes.save_under = True; xattributes.override_redirect = True; xattributes.cursor = None; fg = WhitePixel(display, DefaultScreen(display)); bg = BlackPixel(display, DefaultScreen(display)); if (dockapp.osd) XDestroyWindow(display, dockapp.osd); osd = XCreateSimpleWindow(display, DefaultRootWindow(display), sizehints.x, sizehints.y, width, height, 0, fg, bg); XSetWMNormalHints(display, osd, &sizehints); XChangeWindowAttributes(display, osd, CWSaveUnder | CWOverrideRedirect, &xattributes); XStoreName(display, osd, "osd"); XSelectInput(display, osd, ExposureMask); XChangeProperty(display, osd, XInternAtom(display, "_WIN_LAYER", False), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&win_layer, 1); gcval.foreground = get_color(display, config.osd_color); gcval.background = bg; gcval.graphics_exposures = 0; /* * -sony-fixed-medium-r-normal--24-170-100-100-c-120-iso8859-1 * -misc-fixed-medium-r-normal--36-*-75-75-c-*-iso8859-* */ /* try our cool scaled 36pt fixed font */ fs = XLoadQueryFont(display, "-misc-fixed-medium-r-normal--36-*-75-75-c-*-iso8859-*"); if (fs == NULL) { /* they don't have it! */ /* try our next preferred font (100dpi sony) */ fprintf(stderr, "Trying alternate font\n"); fs = XLoadQueryFont(display, "-sony-fixed-medium-r-normal--24-*-100-100-c-*-iso8859-*"); /* they don't have the sony font either */ if (fs == NULL) { fprintf(stderr, "Trying \"fixed\" font\n"); fs = XLoadQueryFont(display, "fixed"); /* if they don't have the fixed font, we've got different kind * of problems */ if (fs == NULL) { fprintf(stderr, "Your X server is probably broken\n"); exit(1); } } } if (dockapp.osd_gc) XFreeGC(display, dockapp.osd_gc); gc = XCreateGC(display, osd, GCForeground | GCBackground | GCGraphicsExposures, &gcval); XSetFont(display, gc, fs->fid); dockapp.osd = osd; dockapp.osd_gc = gc; dockapp.osd_width = width; dockapp.osd_x = x; dockapp.osd_y = y; dockapp.osd_mapped = false; } void update_osd(float volume, bool up) { int i; int foo; static int bar; if (config.osd) { foo = (dockapp.osd_width - 20) * volume / 20.0; if (up) { for (i = 1; i <= foo; i++) XFillRectangle(display, dockapp.osd, dockapp.osd_gc, i * 20, 30, 5, 25); } else if (foo < bar) { XClearArea(display, dockapp.osd, ((foo+1) * 20), 30, ((bar-foo) * 20), 25, 1); } else if (foo > bar) { for (i = (bar > 0 ? bar : 1); i <= foo; i++) XFillRectangle(display, dockapp.osd, dockapp.osd_gc, i * 20, 30, 5, 25); } bar = foo; } } void unmap_osd(void) { if (config.osd) { XUnmapWindow(display, dockapp.osd); XFlush(display); dockapp.osd_mapped = false; } } void map_osd(void) { if (config.osd) { XMapRaised(display, dockapp.osd); XDrawString(display, dockapp.osd, dockapp.osd_gc, 1, 25, mixer_get_channel_name(), strlen(mixer_get_channel_name())); update_osd(mixer_get_volume(), true); XFlush(display); dockapp.osd_mapped = true; } } bool osd_mapped(void) { return dockapp.osd_mapped; } void set_cursor(int type) { static int oldtype; if (oldtype == type) return; switch (type) { case NULL_CURSOR: XDefineCursor(display, win, null_cursor); XDefineCursor(display, iconwin, null_cursor); break; case NORMAL_CURSOR: XDefineCursor(display, win, norm_cursor); XDefineCursor(display, iconwin, norm_cursor); break; case HAND_CURSOR: XDefineCursor(display, win, hand_cursor); XDefineCursor(display, iconwin, hand_cursor); break; case BAR_CURSOR: XDefineCursor(display, win, bar_cursor); XDefineCursor(display, iconwin, bar_cursor); break; } oldtype = type; } /* private */ static void draw_stereo_led(void) { if (mixer_is_stereo()) /* stereo capable */ copy_xpm_area(78, 0, 9, 7, 28, 14); /* light up LCD */ else /* mono channel */ copy_xpm_area(78, 7, 9, 7, 28, 14); /* turn off LCD */ } static void draw_rec_led(void) { if (mixer_is_rec()) /* record enabled */ copy_xpm_area(65, 0, 13, 7, 4, 14); /* Light up LCD */ else /* record disabled */ copy_xpm_area(65, 7, 13, 7, 4, 14); /* turn off LCD */ } static void draw_mute_led(void) { if (mixer_is_muted()) /* mute */ copy_xpm_area(65, 14, 20, 7, 39, 14); /* light up LCD */ else /* unmute */ copy_xpm_area(65, 21, 20, 7, 39, 14); /* turn off LCD */ } static void draw_percent(void) { int volume = (int)(mixer_get_volume() * 100); copy_xpm_area(0, 87, 18, 9, 41, 22); /* clear percentage */ if (volume < 100) { if (volume >= 10) copy_xpm_area((volume / 10) * 6, 67, 6, 9, 47, 22); copy_xpm_area((volume % 10) * 6, 67, 6, 9, 53, 22); } else { copy_xpm_area(6, 67, 6, 9, 41, 22); copy_xpm_area(0, 67, 6, 9, 47, 22); copy_xpm_area(0, 67, 6, 9, 53, 22); } } static void draw_knob(float volume) { float bearing, led_x, led_y; int led_topleft_x, led_topleft_y; Pixmap led_pixmap; bearing = (1.25 * PI) - (1.5 * PI) * volume; led_x = KNOB_CENTER_X + LED_POS_RADIUS * cos(bearing); led_y = KNOB_CENTER_Y - LED_POS_RADIUS * sin(bearing); led_topleft_x = (int)(led_x - (LED_WIDTH / 2.0) + 0.5); led_topleft_y = (int)(led_y - (LED_HEIGHT / 2.0) + 0.5); /* clear previous knob picture */ copy_xpm_area(87, 0, 26, 26, 36, 35); if (mixer_is_muted()) led_pixmap = led_off_pixmap; else led_pixmap = led_on_pixmap; XCopyArea(display, led_pixmap, dockapp.pixmap, dockapp.gc, 0, 0, LED_WIDTH, LED_HEIGHT, led_topleft_x, led_topleft_y); draw_percent(); } static void draw_slider(float offset) { int x = (offset * 50) / 5; copy_xpm_area(65, 45, 27, 20, 4, 40); /* repair region. move */ copy_xpm_area(65, 29, 7, 15, 14 + x, 43); /* slider */ } static Cursor create_null_cursor(Display *x_display) { Pixmap cursor_mask; XGCValues gcval; GC gc; XColor dummy_color; Cursor cursor; cursor_mask = XCreatePixmap(x_display, DefaultRootWindow(x_display), 1, 1, 1); gcval.function = GXclear; gc = XCreateGC(x_display, cursor_mask, GCFunction, &gcval); XFillRectangle(x_display, cursor_mask, gc, 0, 0, 1, 1); dummy_color.pixel = 0; dummy_color.red = 0; dummy_color.flags = 04; cursor = XCreatePixmapCursor(x_display, cursor_mask, cursor_mask, &dummy_color, &dummy_color, 0, 0); XFreePixmap(x_display, cursor_mask); XFreeGC(x_display, gc); return cursor; } unsigned long get_color(Display *display, char *color_name) { XColor color; XWindowAttributes winattr; Status status; XGetWindowAttributes(display, RootWindow(display, DefaultScreen(display)), &winattr); status = XParseColor(display, winattr.colormap, color_name, &color); if (status == 0) { fprintf(stderr, "wmix:warning: Could not get color \"%s\" for OSD, falling back to default\n", color_name); if (color_name != default_osd_color) status = XParseColor(display, winattr.colormap, default_osd_color, &color); if (status == 0) return WhitePixel(display, DefaultScreen(display)); } color.flags = DoRed | DoGreen | DoBlue; XAllocColor(display, winattr.colormap, &color); return color.pixel; } void ui_rrnotify() { new_osd(60); }