diff options
Diffstat (limited to 'cmd/ossxmix')
-rw-r--r-- | cmd/ossxmix/.config | 4 | ||||
-rw-r--r-- | cmd/ossxmix/gtkvu.c | 304 | ||||
-rw-r--r-- | cmd/ossxmix/gtkvu.h | 61 | ||||
-rw-r--r-- | cmd/ossxmix/ossxmix.c | 2337 | ||||
-rw-r--r-- | cmd/ossxmix/ossxmix.man | 42 | ||||
-rw-r--r-- | cmd/ossxmix/ossxmix.xpm | 312 |
6 files changed, 3060 insertions, 0 deletions
diff --git a/cmd/ossxmix/.config b/cmd/ossxmix/.config new file mode 100644 index 0000000..67bc926 --- /dev/null +++ b/cmd/ossxmix/.config @@ -0,0 +1,4 @@ +cflags=$GTKCFLAGS +ldflags=$GTKLDFLAGS +depends=GTK +forgetos=VxWorks diff --git a/cmd/ossxmix/gtkvu.c b/cmd/ossxmix/gtkvu.c new file mode 100644 index 0000000..e5f6e36 --- /dev/null +++ b/cmd/ossxmix/gtkvu.c @@ -0,0 +1,304 @@ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#ifdef __hpux +#define G_INLINE_FUNC +#endif + +#include <stdio.h> +#include <gtk/gtkmain.h> +#include <gtk/gtksignal.h> + +#include "gtkvu.h" + +extern int width_adjust; + +#define SCROLL_DELAY_LENGTH 300 +#define VU_DEFAULT_SIZE 100 +#define VU_MARGIN (widget->allocation.width/3) + +/* Forward declarations */ + +static void gtk_vu_class_init (GtkVUClass * klass); +static void gtk_vu_init (GtkVU * vu); +static void gtk_vu_destroy (GtkObject * object); +static void gtk_vu_realize (GtkWidget * widget); +static void gtk_vu_unrealize (GtkWidget * widget); +static void gtk_vu_size_request (GtkWidget * widget, + GtkRequisition * requisition); +static void gtk_vu_size_allocate (GtkWidget * widget, + GtkAllocation * allocation); +static gint gtk_vu_expose (GtkWidget * widget, GdkEventExpose * event); + +/* Local data */ + +static GtkWidgetClass *parent_class = NULL; + +GtkType +gtk_vu_get_type (void) +{ + static GtkType vu_type = 0; + + if (!vu_type) + { + GtkTypeInfo vu_info = { + "GtkVU", + sizeof (GtkVU), + sizeof (GtkVUClass), + (GtkClassInitFunc) gtk_vu_class_init, + (GtkObjectInitFunc) gtk_vu_init, + NULL, + NULL, + }; + + vu_type = gtk_type_unique (gtk_widget_get_type (), &vu_info); + } + + return vu_type; +} + +static void +gtk_vu_class_init (GtkVUClass * gvclass) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = (GtkObjectClass *) gvclass; + widget_class = (GtkWidgetClass *) gvclass; + + parent_class = GTK_WIDGET_CLASS (gtk_type_class (gtk_widget_get_type ())); + + object_class->destroy = gtk_vu_destroy; + + widget_class->realize = gtk_vu_realize; + widget_class->unrealize = gtk_vu_unrealize; + widget_class->expose_event = gtk_vu_expose; + widget_class->size_request = gtk_vu_size_request; + widget_class->size_allocate = gtk_vu_size_allocate; +} + +static void +gtk_vu_init (GtkVU * vu) +{ + vu->level = 0; +} + +GtkWidget * +gtk_vu_new (void) +{ + GtkVU *vu; + + vu = GTK_VU (gtk_type_new (gtk_vu_get_type ())); + + return GTK_WIDGET (vu); +} + +static void +gtk_vu_destroy (GtkObject * object) +{ + /* GtkVU *vu; */ + + g_return_if_fail (object != NULL); + g_return_if_fail (GTK_IS_VU (object)); + + /* vu = GTK_VU (object); */ + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (*GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +gtk_vu_realize (GtkWidget * widget) +{ + GtkVU *vu; + GdkWindowAttr attributes; + gint attributes_mask; + gboolean alloc_success[7]; + + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_VU (widget)); + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + vu = GTK_VU (widget); + + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + widget->window = + gdk_window_new (widget->parent->window, &attributes, attributes_mask); + + widget->style = gtk_style_attach (widget->style, widget->window); + + gdk_window_set_user_data (widget->window, widget); + + /* Dark green */ + + vu->colors[0].red = 0x0000; + vu->colors[0].green = 0x30FF; + vu->colors[0].blue = 0x0000; + + /* Green */ + + vu->colors[1].red = 0x0000; + vu->colors[1].green = 0xBFFF; + vu->colors[1].blue = 0x0000; + + /* Dark Orange */ + + vu->colors[2].red = 0x30FF; + vu->colors[2].green = 0x30FF; + vu->colors[2].blue = 0x0000; + + /* Orange */ + + vu->colors[3].red = 0xBFFF; + vu->colors[3].green = 0xBFFF; + vu->colors[3].blue = 0x0000; + + /* Dark Red */ + + vu->colors[4].red = 0x30FF; + vu->colors[4].green = 0x0000; + vu->colors[4].blue = 0x0000; + + /* Red */ + + vu->colors[5].red = 0xBFFF; + vu->colors[5].green = 0x0000; + vu->colors[5].blue = 0x0000; + + /* Black */ + + vu->colors[6].red = 0x0000; + vu->colors[6].green = 0x0000; + vu->colors[6].blue = 0x0000; + + gdk_colormap_alloc_colors (gtk_widget_get_colormap (widget), vu->colors, 7, + FALSE, TRUE, alloc_success); + vu->gc = gdk_gc_new (widget->window); + vu->pixmap = + gdk_pixmap_new (widget->window, widget->allocation.width, + widget->allocation.height, -1); + + /* gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE); */ +} + +static void +gtk_vu_unrealize (GtkWidget * widget) +{ + GtkVU *vu; + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_VU (widget)); + + GTK_WIDGET_UNSET_FLAGS (widget, GTK_REALIZED); + vu = GTK_VU (widget); + + gdk_colormap_free_colors (gtk_widget_get_colormap (widget), vu->colors, 7); + gdk_pixmap_unref (vu->pixmap); + gdk_gc_unref (vu->gc); + gdk_window_unref (widget->window); +} + +/*ARGSUSED*/ +static void +gtk_vu_size_request (GtkWidget * widget, GtkRequisition * requisition) +{ + if (width_adjust <= 0) + requisition->width = 20; + else + requisition->width = 28; + requisition->height = 85; +} + +static void +gtk_vu_size_allocate (GtkWidget * widget, GtkAllocation * allocation) +{ + GtkVU *vu; + + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_VU (widget)); + g_return_if_fail (allocation != NULL); + + widget->allocation = *allocation; + vu = GTK_VU (widget); + + if (GTK_WIDGET_REALIZED (widget)) + { + + gdk_window_move_resize (widget->window, + allocation->x, allocation->y, + allocation->width, allocation->height); + gdk_pixmap_unref (vu->pixmap); + vu->pixmap = + gdk_pixmap_new (widget->window, widget->allocation.width, + widget->allocation.height, + gdk_window_get_visual (widget->window)->depth); + } +} + +static gint +gtk_vu_expose (GtkWidget * widget, GdkEventExpose * event) +{ + GtkVU *vu; + guint i, y_size; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_VU (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + if (event->count > 0) + return FALSE; + + vu = GTK_VU (widget); + + gdk_gc_set_foreground (vu->gc, &vu->colors[6]); + gdk_draw_rectangle (vu->pixmap, vu->gc, TRUE, 0, 0, + widget->allocation.width, widget->allocation.height); + /* gdk_window_clear_area (vu->pixmap, + 0, 0, + widget->allocation.width, + widget->allocation.height); */ + + y_size = (widget->allocation.height - 50) / 8; + + for (i = 0; i < 8; i++) + { + gdk_gc_set_foreground (vu->gc, + &vu->colors[((7 - i) / 3) * 2 + + (((8 - vu->level) > i) ? 0 : 1)]); + gdk_draw_rectangle (vu->pixmap, vu->gc, TRUE, VU_MARGIN, + (i * (y_size + 5)) + 10, + widget->allocation.width - VU_MARGIN * 2, y_size); + } + + gdk_draw_pixmap (widget->window, vu->gc, vu->pixmap, 0, 0, 0, 0, + widget->allocation.width, widget->allocation.height); + return FALSE; +} + +void +gtk_vu_set_level (GtkVU * vu, guint new_level) +{ + if (new_level != vu->level) + { + vu->level = new_level; + gtk_widget_queue_draw (GTK_WIDGET (vu)); + } +} diff --git a/cmd/ossxmix/gtkvu.h b/cmd/ossxmix/gtkvu.h new file mode 100644 index 0000000..344d566 --- /dev/null +++ b/cmd/ossxmix/gtkvu.h @@ -0,0 +1,61 @@ +#ifndef __GTK_VU_H__ +#define __GTK_VU_H__ + +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#include <gdk/gdk.h> +#include <gtk/gtkadjustment.h> +#include <gtk/gtkwidget.h> + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +#define GTK_VU(obj) GTK_CHECK_CAST (obj, gtk_vu_get_type (), GtkVU) +#define GTK_VU_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_vu_get_type (), GtkVUClass) +#define GTK_IS_VU(obj) GTK_CHECK_TYPE (obj, gtk_vu_get_type ()) + + + typedef struct _GtkVU GtkVU; + typedef struct _GtkVUClass GtkVUClass; + + struct _GtkVU + { + GtkWidget widget; + + guint level; + GdkGC *gc; + GdkPixmap *pixmap; + GdkColor colors[7]; + + }; + + struct _GtkVUClass + { + GtkWidgetClass parent_class; + }; + + + GtkWidget *gtk_vu_new (void); + GtkType gtk_vu_get_type (void); + void gtk_vu_set_level (GtkVU * vu, guint new_level); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __GTK_VU_H__ */ diff --git a/cmd/ossxmix/ossxmix.c b/cmd/ossxmix/ossxmix.c new file mode 100644 index 0000000..5f92174 --- /dev/null +++ b/cmd/ossxmix/ossxmix.c @@ -0,0 +1,2337 @@ +/* + * Purpose: This is the ossxmix (GTK++ GUI) program shipped with OSS + * + * Description: + * The {!xlink ossxmix} program is the primary mixer and control panel utility + * available for OSS. It shows how the new mixer API of OSS can be + * used in GUI type of programs See the "{!link mixer}" section of the + * OSS Developer's manual for more info. + * + * This program is fully dynamic as required by the mixer interface. It doesn't + * contain anything that is specific to certain device. All the mixer structure + * information is loaded in the beginning of the program by using the + * {!nlink SNDCTL_MIX_EXTINFO} ioctl (and the related calls). + * + * Note that this program was written before the final mixer API + * was ready. For this reason handling of some special situations is missing + * or incompletely implemented. For example handling of the + * {!nlink EIDRM} is "emulated" simply by closing and re-execing the + * program. This is bit iritating but works. + * + * What might be interesting in this program is how to create the GUI layout + * based on the control tree obtained using the SNDCTL_MIX_EXTINFO routine. + * However unfortunately this part of the program is not particularily easy + * understand. + * + * {!notice Please read the mixer programming documentation very carefully + * before studying this program. + * + * The {!nlink ossmix.c} program is a command line version of this one. + * + * The {!nlink mixext.c} program is a very simple program that shows how + * "non-mixer" applications can do certain mixer changes. + * + * This program uses a "LED" bar widget contained in gtkvu.c. + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#ifdef __hpux +#define G_INLINE_FUNC +#endif +#include <gtk/gtk.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <soundcard.h> +#include <sys/ioctl.h> +#include <errno.h> +#include "gtkvu.h" +#include "ossxmix.xpm" + +#undef TEST_JOY +#undef DEBUG + +#ifndef GTK1_ONLY +#include <gtk/gtkversion.h> +#if GTK_CHECK_VERSION(2,10,0) && !defined(GDK_WINDOWING_DIRECTFB) +#define STATUSICON +#endif /* GTK_CHECK_VERSION(2,10,0) && !GDK_WINDOWING_DIRECTFB */ +#else +#include <gdk/gdkx.h> +#endif /* !GTK1_ONLY */ + +#ifdef TEST_JOY +#include "gtkjoy.h" +#endif + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +static int boomer_workaround = 0; + +#define MAX_DEVS 16 +static int set_counter[MAX_DEVS] = { 0 }; +static int prev_update_counter[MAX_DEVS] = { 0 }; +static oss_mixext_root * root[MAX_DEVS] = { NULL }; +static oss_mixext * extrec[MAX_DEVS] = { NULL }; +static int global_fd = -1; /* Global /dev/mixer fd for SNDCTL_SYSINFO/MIXERINFO/etc */ +static int local_fd[MAX_DEVS]; /* Mixer specific fd(s) for actual mixer access */ +static int dev = -1; +static int show_all = 1; +static int fully_started = 0; +static int load_all_devs = 1; +static int background = 0; +static int show_status_icon = 1; + +int width_adjust = 0; + +#define LEFT 1 +#define RIGHT 2 +#define MONO 3 +#define BOTH 4 + +static guint poll_tag_list[4] = { 0 }; +#define PEAK_DECAY 6 +#define PEAK_POLL_INTERVAL 50 +#define VALUE_POLL_INTERVAL 5000 +#define MIXER_POLL_INTERVAL 100 +#define MIXNUM_POLL_INTERVAL 5000 +static int mixer_num; + +#ifndef EIDRM +#define EIDRM EFAULT +#endif + +typedef enum uflag { + WHAT_LABEL, + WHAT_UPDATE, + WHAT_VMIX +} +uflag_t; + +typedef struct ctlrec +{ + struct ctlrec *next; + oss_mixext *mixext; + GtkObject *left, *right; + GtkWidget *gang, *frame; +#define FRAME_NAME_LENGTH 8 + char frame_name[FRAME_NAME_LENGTH+1]; + int last_left, last_right; + int full_scale; + uflag_t what_to_do; + int parm; +} +ctlrec_t; + +static ctlrec_t * control_list[MAX_DEVS] = { NULL }; +static ctlrec_t * peak_list[MAX_DEVS] = { NULL }; +static ctlrec_t * value_poll_list[MAX_DEVS] = { NULL }; +static ctlrec_t * check_list[MAX_DEVS] = { NULL }; + +static GtkWidget * window, * scrolledwin; + +static gint add_timeout (gpointer); +static void change_enum (GtkToggleButton *, gpointer); +static void change_on_off (GtkToggleButton *, gpointer); +static void check_tooltip (oss_mixext *, GtkWidget *); +static void cleanup (void); +static gint close_request (GtkWidget *, gpointer); +static void connect_enum (oss_mixext *, GtkObject *); +static void connect_onoff (oss_mixext *, GtkObject *); +static void connect_peak (oss_mixext *, GtkWidget *, GtkWidget *); +static void connect_scrollers (oss_mixext *, GtkObject *, + GtkObject *, GtkWidget *); +static void connect_value_poll (oss_mixext *, GtkWidget *); +static void create_update (GtkWidget *, GtkObject *, GtkObject *, GtkWidget *, + oss_mixext *, uflag_t, int); +static GtkRequisition create_widgets (void); +static char * cut_name (char *); +static void do_update (ctlrec_t *); +static int findenum (oss_mixext *, const char *); +static int find_default_mixer (void); +static void gang_change (GtkToggleButton *, gpointer); +static int get_fd (int); +static int get_value (oss_mixext *); +static GtkWidget * load_devinfo (int); +static GList * load_enum_values (char *, oss_mixext *); +static GtkWidget * load_multiple_devs (void); +static void manage_label (GtkWidget *, oss_mixext *); +#ifndef GTK1_ONLY +static gint manage_timeouts (GtkWidget *, GdkEventWindowState *, gpointer); +#endif /* !GTK1_ONLY */ +static void parse_dimarg (const char *, GtkRequisition *); +static gint poll_all (gpointer); +static gint poll_peaks (gpointer); +static gint poll_values (gpointer); +static gint poll_mixnum (gpointer); +static void reload_gui (void); +static gint remove_timeout (gpointer); +static void Scrolled (GtkAdjustment *, gpointer); +static int set_value (oss_mixext *, int); +static char * showenum (oss_mixext *, int); +static void store_name (int, int, char *, char **); +static void switch_page (GtkNotebook *, GtkNotebookPage *, guint, gpointer); +static void update_label (oss_mixext *, GtkWidget *, int); +#ifdef STATUSICON +static void activate_mainwindow (GtkStatusIcon *, guint, guint, gpointer); +static void popup_mainwindow (GtkWidget *, gpointer); +static void trayicon_popupmenu (GtkStatusIcon *, guint, guint, gpointer); + +static GtkStatusIcon *status_icon = NULL; +#endif /* STATUSICON */ + +static int +get_fd (int dev) +{ + int fd; + oss_mixerinfo mi; + + if (dev < 0 || dev >= MAX_DEVS) + { + fprintf (stderr, "Bad mixer device number %d\n", dev); + exit (EXIT_FAILURE); + } + + if (local_fd[dev] != -1) + return local_fd[dev]; + + mi.dev = dev; + if (ioctl (global_fd, SNDCTL_MIXERINFO, &mi) == -1) + { + perror ("SNDCTL_MIXERINFO"); + exit (EXIT_FAILURE); + } + + if ((fd = open (mi.devnode, O_RDWR, 0)) == -1) + { + perror (mi.devnode); + } + + return local_fd[dev] = fd; +} + +static void +check_tooltip (oss_mixext * rec, GtkWidget * wid) +{ + oss_mixer_enuminfo ei; + char *p; + + if (!(rec->flags & MIXF_DESCR)) /* No description available */ + return; + + ei.dev = rec->dev; + ei.ctrl = rec->ctrl; + + if (ioctl (get_fd(rec->dev), SNDCTL_MIX_DESCRIPTION, &ei) == -1) + return; + +/* + * Separate the first line which contains the tooltip from the subsequent lines + * which contain the optional help text. + */ + p=ei.strings; + + while (*p && *p != '\n') p++; /* Find a line feed */ + + if (*p=='\n') + *p++=0; + if (*p==0) + p=NULL; + +#if GTK_CHECK_VERSION(2,12,0) + gtk_widget_set_tooltip_text(wid, ei.strings); +#else + { + GtkTooltips *tip; + + tip = gtk_tooltips_new(); + gtk_tooltips_set_tip(tip, wid, ei.strings, p); + } +#endif +} + +static void +store_name (int dev, int n, char *name, char **extnames) +{ + char *src = name; + size_t i, l; + + l = strlen (name); + for (i = 0; i < l; i++) + { + if (name[i] >= 'A' && name[i] <= 'Z') + name[i] += 32; + if (name[i] == '.') src = name + i + 1; + } + + extnames[n] = g_strdup (src); +#ifdef DEBUG + fprintf (stderr, "Control = %s\n", name); +#endif +} + +static char * +cut_name (char * name) +{ + char *s = name; + while (*s) + if (*s++ == '_') + return s; + + if (name[0] == '@') + return &name[1]; + + return name; +} + +static char * +showenum (oss_mixext * rec, int val) +{ + static char tmp[100]; + oss_mixer_enuminfo ei; + + if (val > rec->maxvalue) + { + snprintf (tmp, sizeof(tmp), "%d(too large (%d)?)", val, rec->maxvalue); + return tmp; + } + + ei.dev = rec->dev; + ei.ctrl = rec->ctrl; + + if (ioctl (get_fd(rec->dev), SNDCTL_MIX_ENUMINFO, &ei) != -1) + { + char *p; + + if (val >= ei.nvalues) + { + snprintf (tmp, sizeof(tmp), "%d(too large2 (%d)?)", val, ei.nvalues); + return tmp; + } + + p = ei.strings + ei.strindex[val]; + strncpy (tmp, p, sizeof(tmp)); + return tmp; + } + + snprintf (tmp, sizeof(tmp), "%d", val); + return tmp; +} + +static GList * +load_enum_values (char *extname, oss_mixext * rec) +{ + int i; + GList *list = NULL; + oss_mixer_enuminfo ei; + + ei.dev = rec->dev; + ei.ctrl = rec->ctrl; + + if (ioctl (get_fd(rec->dev), SNDCTL_MIX_ENUMINFO, &ei) != -1) + { + int n = ei.nvalues; + char *p; + + if (n > rec->maxvalue) + n = rec->maxvalue; + + for (i = 0; i < rec->maxvalue; i++) + if (rec->enum_present[i / 8] & (1 << (i % 8))) + { + p = ei.strings + ei.strindex[i]; + list = g_list_append (list, g_strdup (p)); + } + + return list; + } + + if (*extname == '.') + extname++; + + for (i = 0; i < rec->maxvalue; i++) + if (rec->enum_present[i / 8] & (1 << (i % 8))) + { + list = g_list_append (list, g_strdup (showenum (rec, i))); + } + + return list; +} + +static int +findenum (oss_mixext * rec, const char * arg) +{ + int i, v; + oss_mixer_enuminfo ei; + + ei.dev = rec->dev; + ei.ctrl = rec->ctrl; + + if (ioctl (get_fd(rec->dev), SNDCTL_MIX_ENUMINFO, &ei) != -1) + { + int n = ei.nvalues; + char *p; + + if (n > rec->maxvalue) + n = rec->maxvalue; + + for (i = 0; i < rec->maxvalue; i++) + if (rec->enum_present[i / 8] & (1 << (i % 8))) + { + p = ei.strings + ei.strindex[i]; + if (strcmp (p, arg) == 0) + return i; + } + } + + if (sscanf (arg, "%d", &v) == 1) + return v; + + fprintf (stderr, "Invalid enumerated value '%s'\n", arg); + return 0; +} + +static int +get_value (oss_mixext * thisrec) +{ + oss_mixer_value val; + + val.dev = thisrec->dev; + val.ctrl = thisrec->ctrl; + val.timestamp = thisrec->timestamp; + + if (ioctl (get_fd(thisrec->dev), SNDCTL_MIX_READ, &val) == -1) + { + if (errno == EPIPE) + { +#if 0 + fprintf (stderr, + "ossxmix: Mixer device disconnected from the system\n"); +#endif + return 0; + } + + if (errno == EIDRM) + { + if (fully_started) + { +/* + * The mixer structure got changed for some reason. This program + * is not designed to handle this event properly so all we can do + * is to recreate the entire GUI. + * + * Well written applications should just dispose the changed GUI elements + * (by comparing the {!code timestamp} fields. Then the new fields can be + * created just like we did when starting the program. + */ + fprintf (stderr, + "ossxmix: Mixer structure changed - restarting.\n"); + reload_gui (); + return -1; + } + else + { + fprintf (stderr, + "ossxmix: Mixer structure changed - aborting.\n"); + exit (-1); + } + } + fprintf (stderr, "%s\n", thisrec->id); + perror ("SNDCTL_MIX_READ"); + return -1; + } + + return val.value; +} + +static int +set_value (oss_mixext * thisrec, int value) +{ + oss_mixer_value val; + + if (!(thisrec->flags & MIXF_WRITEABLE)) + return -1; + val.dev = thisrec->dev; + val.ctrl = thisrec->ctrl; + val.value = value; + val.timestamp = thisrec->timestamp; + set_counter[thisrec->dev]++; + + if (ioctl (get_fd(thisrec->dev), SNDCTL_MIX_WRITE, &val) == -1) + { + if (errno == EIDRM) + { + if (fully_started) + { + fprintf (stderr, + "ossxmix: Mixer structure changed - restarting.\n"); + reload_gui (); + return -1; + } + else + { + fprintf (stderr, + "ossxmix: Mixer structure changed - aborting.\n"); + exit (-1); + } + } + fprintf (stderr, "%s\n", thisrec->id); + perror ("SNDCTL_MIX_WRITE"); + return -1; + } + return val.value; +} + +static void +create_update (GtkWidget * frame, GtkObject * left, GtkObject * right, + GtkWidget * gang, oss_mixext * thisrec, uflag_t what, int parm) +{ + ctlrec_t *srec; + + srec = g_new (ctlrec_t, 1); + srec->mixext = thisrec; + srec->parm = parm; + srec->what_to_do = what; + srec->frame = frame; + srec->left = left; + srec->right = right; + srec->gang = gang; + srec->frame_name[0] = '\0'; + + srec->next = check_list[thisrec->dev]; + check_list[thisrec->dev] = srec; +} + +static void +manage_label (GtkWidget * frame, oss_mixext * thisrec) +{ + char new_label[FRAME_NAME_LENGTH+1], tmp[16]; + + if (thisrec->id[0] != '@') + return; + + *new_label = 0; + + strncpy (tmp, &thisrec->id[1], sizeof(tmp)); + tmp[15] = '\0'; + + if ((tmp[0] == 'd' && tmp[1] == 's' && tmp[2] == 'p') || + (tmp[0] == 'p' && tmp[1] == 'c' && tmp[2] == 'm')) + { + int dspnum; + oss_audioinfo ainfo; + + if (sscanf (&tmp[3], "%d", &dspnum) != 1) + return; + + ainfo.dev = dspnum; + if (ioctl (global_fd, SNDCTL_ENGINEINFO, &ainfo) == -1) + { + perror ("SNDCTL_ENGINEINFO"); + return; + } + create_update (frame, NULL, NULL, NULL, thisrec, WHAT_LABEL, dspnum); + if (*ainfo.label != 0) + { + strncpy (new_label, ainfo.label, FRAME_NAME_LENGTH); + new_label[FRAME_NAME_LENGTH] = 0; + } + } + + + if (*new_label != 0) + gtk_frame_set_label (GTK_FRAME (frame), new_label); + +} + +static void +Scrolled (GtkAdjustment * adjust, gpointer data) +{ + int val, origval, lval, rval; + int side, gang_on; + ctlrec_t *srec = (ctlrec_t *) data; + int shift = 8; + + val = srec->mixext->maxvalue - (int) adjust->value; + origval = (int) adjust->value; + + if (srec->mixext->type == MIXT_MONOSLIDER16 + || srec->mixext->type == MIXT_STEREOSLIDER16) + shift = 16; + + if (srec->full_scale) + side = BOTH; + else if (srec->right == NULL) + side = MONO; + else if (GTK_OBJECT (adjust) == srec->left) + side = LEFT; + else + side = RIGHT; + + if (srec->mixext->type == MIXT_3D) + { +#ifdef TEST_JOY +#else + lval = 100 - (int) GTK_ADJUSTMENT (srec->left)->value; + rval = 360 - (int) GTK_ADJUSTMENT (srec->right)->value; + val = (50 << 8) | (lval & 0xff) | (rval << 16); + set_value (srec->mixext, val); +#endif + return; + } + + if (side == BOTH) + { + set_value (srec->mixext, val); + return; + } + + if (side == MONO) + { + val = val | (val << shift); + set_value (srec->mixext, val); + return; + } + + gang_on = 0; + + if (srec->gang != NULL) + { + gang_on = GTK_TOGGLE_BUTTON (srec->gang)->active; + } + + if (gang_on) + { + gtk_adjustment_set_value (GTK_ADJUSTMENT (srec->left), origval); + gtk_adjustment_set_value (GTK_ADJUSTMENT (srec->right), origval); + + val = val | (val << shift); + set_value (srec->mixext, val); + + return; + } + + lval = srec->mixext->maxvalue - (int) GTK_ADJUSTMENT (srec->left)->value; + rval = srec->mixext->maxvalue - (int) GTK_ADJUSTMENT (srec->right)->value; + val = lval | (rval << shift); + set_value (srec->mixext, val); +} + +static void +gang_change (GtkToggleButton * but, gpointer data) +{ + ctlrec_t *srec = (ctlrec_t *) data; + int val, aval, lval, rval; + int mask = 0xff, shift = 8; + + if (!but->active) + return; + + lval = srec->mixext->maxvalue - (int) GTK_ADJUSTMENT (srec->left)->value; + rval = srec->mixext->maxvalue - (int) GTK_ADJUSTMENT (srec->right)->value; + if (lval == rval) + return; + + if (lval < rval) + lval = rval; + + if (srec->mixext->type == MIXT_STEREOSLIDER16) + { + shift = 16; + mask = 0xffff; + } + + val = lval | (lval << shift); + aval = srec->mixext->maxvalue - (val & mask); + + gtk_adjustment_set_value (GTK_ADJUSTMENT (srec->left), aval); + gtk_adjustment_set_value (GTK_ADJUSTMENT (srec->right), aval); + set_value (srec->mixext, val); +} + +/*ARGSUSED*/ +static void +change_enum (GtkToggleButton * but, gpointer data) +{ + ctlrec_t *srec = (ctlrec_t *) data; + int val; + const char *entry; + + entry = gtk_entry_get_text (GTK_ENTRY (srec->left)); + if (*entry == 0) /* Empty - Why? */ + return; + + val = findenum (srec->mixext, entry); + + set_value (srec->mixext, val); +} + + +static void +change_on_off (GtkToggleButton * but, gpointer data) +{ + ctlrec_t *srec = (ctlrec_t *) data; + int val; + + val = but->active; + + /* + * old OSSv4 builds had a bug where SNDCTL_MIX_WRITE would always return + * 1 when changing a rec button. + */ +#if 0 + val = set_value (srec->mixext, val); +#else + set_value (srec->mixext, val); + val = get_value (srec->mixext); +#endif + if (val != -1) but->active = val; +} + +static void +store_ctl (ctlrec_t * rec, int dev) +{ + rec->next = control_list[dev]; + control_list[dev] = rec; +} + +static void +connect_scrollers (oss_mixext * thisrec, GtkObject * left, GtkObject * right, + GtkWidget * gang) +{ + ctlrec_t *srec; + + srec = g_new (ctlrec_t, 1); + srec->mixext = thisrec; + srec->left = left; + srec->right = right; + srec->full_scale = (thisrec->type == MIXT_SLIDER); + srec->gang = gang; + gtk_signal_connect (GTK_OBJECT (left), "value_changed", + GTK_SIGNAL_FUNC (Scrolled), srec); + if (right != NULL) + gtk_signal_connect (GTK_OBJECT (right), "value_changed", + GTK_SIGNAL_FUNC (Scrolled), srec); + if (gang != NULL) + gtk_signal_connect (GTK_OBJECT (gang), "toggled", + GTK_SIGNAL_FUNC (gang_change), srec); + + store_ctl (srec, thisrec->dev); + +} + +static void +connect_peak (oss_mixext * thisrec, GtkWidget * left, GtkWidget * right) +{ + ctlrec_t *srec; + + srec = g_new (ctlrec_t, 1); + srec->mixext = thisrec; + srec->left = GTK_OBJECT (left); + if (right == NULL) + srec->right = NULL; + else + srec->right = GTK_OBJECT (right); + srec->gang = NULL; + srec->last_left = 0; + srec->last_right = 0; + + srec->next = peak_list[thisrec->dev]; + peak_list[thisrec->dev] = srec; +} + +static void +connect_value_poll (oss_mixext * thisrec, GtkWidget * wid) +{ + ctlrec_t *srec; + + srec = g_new (ctlrec_t, 1); + srec->mixext = thisrec; + srec->left = GTK_OBJECT (wid); + srec->right = NULL; + srec->gang = NULL; + srec->last_left = 0; + srec->last_right = 0; + + srec->next = value_poll_list[thisrec->dev]; + value_poll_list[thisrec->dev] = srec; +} + +static void +connect_enum (oss_mixext * thisrec, GtkObject * entry) +{ + ctlrec_t *srec; + + srec = g_new (ctlrec_t, 1); + srec->mixext = thisrec; + srec->left = entry; + srec->right = NULL; + srec->gang = NULL; + gtk_signal_connect (entry, "changed", GTK_SIGNAL_FUNC (change_enum), srec); + store_ctl (srec, thisrec->dev); + +} + +static void +connect_onoff (oss_mixext * thisrec, GtkObject * entry) +{ + ctlrec_t *srec; + + srec = g_new (ctlrec_t, 1); + srec->mixext = thisrec; + srec->left = entry; + srec->right = NULL; + srec->gang = NULL; + gtk_signal_connect (entry, "toggled", GTK_SIGNAL_FUNC (change_on_off), srec); + store_ctl (srec, thisrec->dev); + +} + +/* + * Create notebook and populate it with multiple mixer tabs. Returns notebook. + */ +static GtkWidget * +load_multiple_devs (void) +{ + int i, first_page = -1; + GtkWidget *notebook, *mixer_page, *label, *vbox, *hbox; + + if (ioctl (global_fd, SNDCTL_MIX_NRMIX, &mixer_num) == -1) + { + perror ("SNDCTL_MIX_NRMIX"); + exit (-1); + } + + if (mixer_num > MAX_DEVS) mixer_num = MAX_DEVS; + + /* This can happen when ossxmix is restarted by {get/set}_value */ + if (dev > mixer_num - 1) dev = find_default_mixer (); + + notebook = gtk_notebook_new (); + for (i = 0; i < mixer_num; i++) + { + if (get_fd(i) == -1) continue; + mixer_page = load_devinfo (i); + if (mixer_page == NULL) continue; + if (first_page == -1) first_page = i; + vbox = gtk_vbox_new (FALSE, 0); + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), mixer_page, FALSE, TRUE, 0); + gtk_box_pack_end (GTK_BOX (vbox), hbox, TRUE, TRUE, 0); + gtk_widget_show (hbox); + gtk_widget_show (vbox); + if (root[i] == NULL) + { + fprintf (stderr, "No device root node for mixer %d\n", i); + exit (-1); + } + label = gtk_label_new (root[i]->name); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); + } + + if (root[dev] != NULL) first_page = dev; + else if (first_page != -1) dev = first_page; + else + { + fprintf (stderr, "No mixers could be opened\n"); + exit (EXIT_FAILURE); + } + +#ifndef GTK1_ONLY + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), dev); +#else + gtk_notebook_set_page (GTK_NOTEBOOK (notebook), dev); +#endif /* !GTK1_ONLY */ + gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE); + gtk_signal_connect (GTK_OBJECT (notebook), "switch-page", + GTK_SIGNAL_FUNC (switch_page), (gpointer)poll_tag_list); + gtk_widget_show (notebook); + + return notebook; +} + +/* + * The load_devinfo() routine loads the mixer definitions and creates the + * GUI structure based on it. + * + * In short the algorithm is to create GTK vbox or hbox widgets for + * each group. A vbox is created for the root group. Then the orientation is + * changed in each level of sub-groups. However there are some exceptions to + * this rule (will be described in the documentation. + * + * The individual controls are just placed inside the hbox/vbx widgets of + * the parent groups. However the "legacy" mixer controls (before + * MIXT_MARKER) will be handled in slightly different way (please consult + * the documentation). + */ +static GtkWidget * +load_devinfo (int dev) +{ + char tmp[1024], *name = '\0'; + char ** extnames; + int i, n, val, left, right, mx, g, mask, shift; + int angle, vol; + int width; + int ngroups = 0; + int parent = 0; + int change_color; + oss_mixext *thisrec; + oss_mixerinfo mi; + GdkColor color; + GtkWidget *wid, *wid2, *gang, *rootwid = NULL, *pw = NULL, *frame, *box; + GtkWidget **widgets; + GtkObject *adjust, *adjust2; + gboolean change_orient = TRUE, ori, * orient; + gboolean expand, use_layout_b = FALSE; + + mi.dev = dev; + if (ioctl (global_fd, SNDCTL_MIXERINFO, &mi) == -1) + { + perror ("SNDCTL_MIXERINFO"); + exit (-1); + } + + if (!mi.enabled) return NULL; + /* e.g. disconnected USB device */ + + if (mi.caps & MIXER_CAP_LAYOUT_B) + use_layout_b = TRUE; + + if ((mi.caps & MIXER_CAP_NARROW) && (width_adjust >= 0)) + width_adjust = -1; + + n = mi.nrext; + if (n < 1) + { + fprintf (stderr, "Error: illogical number of extension info records\n"); + return NULL; + } + extrec[dev] = g_new0 (oss_mixext, n+1); + extnames = g_new (char *, n+1); + widgets = g_new0 (GtkWidget *, n); + orient = g_new0 (gboolean, n); + + for (i = 0; i < n; i++) + { + change_color = 0; + mask = 0xff; + shift = 8; + expand = TRUE; + + thisrec = &extrec[dev][i]; + thisrec->dev = dev; + thisrec->ctrl = i; + + if (ioctl (get_fd(dev), SNDCTL_MIX_EXTINFO, thisrec) == -1) + { + if (errno == EINVAL) + printf ("Incompatible OSS version\n"); + else + perror ("SNDCTL_MIX_EXTINFO"); + exit (-1); + } + + if (thisrec->id[0] == '-') /* Hidden one */ + thisrec->id[0] = '\0'; + + if (thisrec->type == MIXT_STEREOSLIDER16 + || thisrec->type == MIXT_MONOSLIDER16) + { + mask = 0xffff; + shift = 16; + } + + if ((thisrec->type != MIXT_DEVROOT) && (thisrec->type != MIXT_MARKER)) + { + parent = thisrec->parent; + name = cut_name (thisrec->id); + if ((thisrec->type == MIXT_GROUP) && !change_orient && (parent == 0)) + pw = rootwid; + else + pw = widgets[parent]; + if ((pw == NULL) && (show_all)) + fprintf (stderr, "Control %d/%s: Parent(%d)==NULL\n", i, + thisrec->extname, parent); + } + +#if OSS_VERSION >= 0x040004 + if (thisrec->rgbcolor != 0) + { + /* + * Pick the 8 bit RGB component colors and expand them to 16 bits + */ + color.red = (thisrec->rgbcolor & 0xff0000) >> 8; + color.green = (thisrec->rgbcolor & 0x00ff00); + color.blue = (thisrec->rgbcolor & 0x0000ff) << 8; + change_color=1; + + } +#endif + + switch (thisrec->type) + { + case MIXT_DEVROOT: + root[dev] = (oss_mixext_root *) & thisrec->data; + extnames[i] = g_strdup(""); + rootwid = gtk_vbox_new (FALSE, 2); + gtk_box_set_child_packing (GTK_BOX (rootwid), rootwid, TRUE, TRUE, + 100, GTK_PACK_START); + wid = gtk_hbox_new (FALSE, 1); + gtk_box_pack_start (GTK_BOX (rootwid), wid, TRUE, TRUE, 1); + gtk_widget_show_all (rootwid); + widgets[i] = wid; + break; + + case MIXT_GROUP: + if (!show_all) + break; +#if OSS_VERSION >= 0x040090 + if (!boomer_workaround) /* Boomer 4.0 doesn't provide update_counters */ + if (thisrec->update_counter == 0) + break; +#endif + + if (*extnames[parent] == '\0') + strcpy (tmp, name); + else + snprintf (tmp, sizeof(tmp), "%s.%s", extnames[parent], name); + store_name (dev, i, tmp, extnames); + if (thisrec->flags & MIXF_FLAT) /* Group contains only ENUM controls */ + expand = FALSE; + ori = !orient[parent]; + if (change_orient) + ori = !ori; + orient[i] = ori; + + switch (ori) + { + case 0: + wid = gtk_vbox_new (FALSE, 1); + break; + + default: + ngroups++; + if (!use_layout_b) + ori = !ori; + orient[i] = ori; + wid = gtk_hbox_new (FALSE, 1); + } + + frame = gtk_frame_new (extnames[i]); + manage_label (frame, thisrec); + gtk_box_pack_start (GTK_BOX (pw), frame, expand, TRUE, 1); + gtk_container_add (GTK_CONTAINER (frame), wid); + gtk_widget_set_name (wid, extnames[i]); + gtk_widget_show_all (frame); + widgets[i] = wid; + { + int tmp = -1; + + if ((sscanf (extnames[i], "vmix%d-out", &tmp) == 1) && + (tmp >= 0)) + { + create_update (NULL, NULL, NULL, wid, thisrec, WHAT_VMIX, n); + } + } + break; + + case MIXT_HEXVALUE: + case MIXT_VALUE: + if (!show_all) + break; + if (*thisrec->id == 0) + extnames[i] = extnames[parent]; + else + { + snprintf (tmp, sizeof(tmp), "%s.%s", extnames[parent], name); + store_name (dev, i, tmp, extnames); + } + val = get_value (thisrec); + + wid = gtk_label_new ("????"); + gtk_box_pack_start (GTK_BOX (pw), wid, FALSE, TRUE, 0); + if (thisrec->flags & MIXF_POLL) + connect_value_poll (thisrec, wid); + else + create_update (NULL, NULL, NULL, wid, thisrec, WHAT_UPDATE, i); + gtk_widget_set_name (wid, extnames[i]); + update_label (thisrec, wid, val); + check_tooltip(thisrec, wid); + gtk_widget_show (wid); + break; + + case MIXT_STEREODB: + case MIXT_MONODB: + if (!show_all) + break; + if (*thisrec->id == 0) + extnames[i] = extnames[parent]; + else + { + snprintf (tmp, sizeof(tmp), "%s.%s", extnames[parent], name); + store_name (dev, i, tmp, extnames); + } + wid = gtk_button_new_with_label (extnames[i]); + gtk_box_pack_start (GTK_BOX (pw), wid, FALSE, TRUE, 0); + gtk_widget_set_name (wid, extnames[i]); + check_tooltip(thisrec, wid); + gtk_widget_show (wid); + break; + + case MIXT_ONOFF: +#ifdef MIXT_MUTE + case MIXT_MUTE: /* TODO: Mute could have custom widget */ +#endif /* MIXT_MUTE */ + if (!show_all) + break; + val = get_value (thisrec) & 0x01; + if (*thisrec->id == 0) + extnames[i] = extnames[parent]; + else + { + snprintf (tmp, sizeof(tmp), "%s.%s", extnames[parent], name); + store_name (dev, i, tmp, extnames); + } + wid = gtk_check_button_new_with_label (extnames[i]); +#ifndef GTK1_ONLY + if (change_color) + gtk_widget_modify_bg (wid, GTK_STATE_NORMAL, &color); +#endif /* !GTK1_ONLY */ + connect_onoff (thisrec, GTK_OBJECT (wid)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), val); + create_update (NULL, NULL, NULL, wid, thisrec, WHAT_UPDATE, 0); + gtk_box_pack_start (GTK_BOX (pw), wid, FALSE, TRUE, 0); + gtk_widget_set_name (wid, extnames[i]); + check_tooltip(thisrec, wid); + gtk_widget_show (wid); + break; + + case MIXT_STEREOVU: + case MIXT_STEREOPEAK: + if (!show_all) + break; + val = get_value (thisrec); + mx = thisrec->maxvalue; + left = mx - (val & 0xff); + right = mx - ((val >> 8) & 0xff); + if (*thisrec->id == 0) + extnames[i] = extnames[parent]; + else + { + snprintf (tmp, sizeof(tmp), "%s.%s", extnames[parent], name); + store_name (dev, i, tmp, extnames); + } + wid = gtk_vu_new (); + wid2 = gtk_vu_new (); + check_tooltip(thisrec, wid); + + connect_peak (thisrec, wid, wid2); + gtk_widget_set_name (wid, extnames[i]); + gtk_widget_set_name (wid2, extnames[i]); + if (strcmp (extnames[parent], extnames[i]) != 0) + { + frame = gtk_frame_new (extnames[i]); + manage_label (frame, thisrec); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (pw), frame, FALSE, TRUE, 0); + box = gtk_hbox_new (FALSE, 1); + gtk_container_add (GTK_CONTAINER (frame), box); + gtk_box_pack_start (GTK_BOX (box), wid, TRUE, TRUE, 1); + gtk_box_pack_start (GTK_BOX (box), wid2, TRUE, TRUE, 1); + gtk_widget_show_all (frame); + } + else + { + box = gtk_hbox_new (FALSE, 1); + gtk_box_pack_start (GTK_BOX (pw), box, FALSE, TRUE, 1); + gtk_box_pack_start (GTK_BOX (box), wid, TRUE, TRUE, 1); + gtk_box_pack_start (GTK_BOX (box), wid2, TRUE, TRUE, 1); + gtk_widget_show_all (box); + } + break; + + case MIXT_MONOVU: + case MIXT_MONOPEAK: + if (!show_all) + break; + val = get_value (thisrec); + mx = thisrec->maxvalue; + left = mx - (val & 0xff); + if (*thisrec->id == 0) + extnames[i] = extnames[parent]; + else + { + snprintf (tmp, sizeof(tmp), "%s.%s", extnames[parent], name); + store_name (dev, i, tmp, extnames); + } + wid = gtk_vu_new (); + check_tooltip(thisrec, wid); + + connect_peak (thisrec, wid, NULL); + gtk_widget_set_name (wid, extnames[i]); + if (strcmp (extnames[parent], extnames[i]) != 0) + { + frame = gtk_frame_new (extnames[i]); + manage_label (frame, thisrec); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (pw), frame, FALSE, TRUE, 0); + box = gtk_hbox_new (FALSE, 1); + gtk_container_add (GTK_CONTAINER (frame), box); + gtk_box_pack_start (GTK_BOX (box), wid, TRUE, TRUE, 1); + gtk_widget_show_all (frame); + } + else + { + box = gtk_hbox_new (FALSE, 1); + gtk_box_pack_start (GTK_BOX (pw), box, FALSE, TRUE, 1); + gtk_box_pack_start (GTK_BOX (box), wid, TRUE, TRUE, 1); + gtk_widget_show_all (box); + } + break; + + case MIXT_STEREOSLIDER: + case MIXT_STEREOSLIDER16: + if (!show_all) + break; + width = -1; + + if (width_adjust < 0) + width = 12; + val = get_value (thisrec); + mx = thisrec->maxvalue; + left = mx - (val & mask); + right = mx - ((val >> shift) & mask); + if (*thisrec->id == 0) + extnames[i] = extnames[parent]; + else + { + snprintf (tmp, sizeof(tmp), "%s.%s", extnames[parent], name); + store_name (dev, i, tmp, extnames); + } + adjust = gtk_adjustment_new (left, 0, mx, 1, 5, 0); + adjust2 = gtk_adjustment_new (right, 0, mx, 1, 5, 0); + gang = gtk_check_button_new (); + g = (left == right); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gang), g); + + connect_scrollers (thisrec, adjust, adjust2, gang); + create_update (NULL, adjust, adjust2, gang, thisrec, WHAT_UPDATE, + 0); + + wid = gtk_vscale_new (GTK_ADJUSTMENT (adjust)); + check_tooltip(thisrec, wid); +#ifndef GTK1_ONLY + if (change_color) + gtk_widget_modify_bg (wid, GTK_STATE_NORMAL, &color); + gtk_widget_set_size_request (wid, width, 80); +#endif /* !GTK1_ONLY */ + gtk_scale_set_digits (GTK_SCALE (wid), 0); + gtk_scale_set_draw_value (GTK_SCALE (wid), FALSE); + gtk_widget_set_name (wid, extnames[i]); + + wid2 = gtk_vscale_new (GTK_ADJUSTMENT (adjust2)); +#ifndef GTK1_ONLY + if (change_color) + gtk_widget_modify_bg (wid2, GTK_STATE_NORMAL, &color); + gtk_widget_set_size_request (wid2, width, 80); +#endif /* !GTK1_ONLY */ + gtk_scale_set_digits (GTK_SCALE (wid2), 0); + gtk_scale_set_draw_value (GTK_SCALE (wid2), FALSE); + gtk_widget_set_name (wid2, extnames[i]); + + if (strcmp (extnames[parent], extnames[i]) != 0) + { + frame = gtk_frame_new (extnames[i]); + manage_label (frame, thisrec); + /* gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); */ + gtk_box_pack_start (GTK_BOX (pw), frame, FALSE, FALSE, 1); + box = gtk_hbox_new (FALSE, 1); + gtk_container_add (GTK_CONTAINER (frame), box); + gtk_box_pack_start (GTK_BOX (box), wid, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box), wid2, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box), gang, FALSE, TRUE, 0); + gtk_widget_show_all (frame); + } + else + { + box = gtk_hbox_new (FALSE, 1); +#if 1 + gtk_box_pack_start (GTK_BOX (pw), box, TRUE, TRUE, 1); +#else + gtk_box_pack_start (GTK_BOX (pw), box, FALSE, FALSE, 1); +#endif + gtk_box_pack_start (GTK_BOX (box), wid, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box), wid2, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box), gang, FALSE, TRUE, 0); + gtk_widget_show_all (box); + } + break; + + case MIXT_3D: +#ifdef TEST_JOY + if (!show_all) + break; + val = get_value (thisrec); + if (*thisrec->id == 0) + extnames[i] = extnames[parent]; + else + { + snprintf (tmp, sizeof(tmp), "%s.%s", extnames[parent], name); + store_name (dev, i, tmp, extnames); + } + wid = gtk_joy_new (); + check_tooltip(thisrec, wid); + create_update (NULL, NULL, NULL, wid, thisrec, WHAT_UPDATE, 0); + + if (strcmp (extnames[parent], extnames[i]) != 0) + { + frame = gtk_frame_new (extnames[i]); + manage_label (frame, thisrec); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (pw), frame, FALSE, TRUE, 0); + box = gtk_hbox_new (FALSE, 1); + gtk_container_add (GTK_CONTAINER (frame), box); + gtk_box_pack_start (GTK_BOX (box), wid, TRUE, TRUE, 1); + gtk_widget_show_all (frame); + } + else + { + box = gtk_hbox_new (FALSE, 1); + gtk_box_pack_start (GTK_BOX (pw), box, FALSE, TRUE, 1); + gtk_box_pack_start (GTK_BOX (box), wid, TRUE, TRUE, 1); + gtk_widget_show_all (box); + } + break; +#else + if (!show_all) + break; + val = get_value (thisrec); + mx = thisrec->maxvalue; + vol = 100 - (val & 0x00ff); + angle = 360 - ((val >> 16) & 0xffff); + if (*thisrec->id == 0) + extnames[i] = extnames[parent]; + else + { + snprintf (tmp, sizeof(tmp), "%s.%s", extnames[parent], name); + store_name (dev, i, tmp, extnames); + } + adjust = gtk_adjustment_new (vol, 0, 100, 1, 5, 0); + adjust2 = gtk_adjustment_new (angle, 0, 360, 1, 5, 0); + connect_scrollers (thisrec, adjust, adjust2, NULL); + create_update (NULL, adjust, adjust2, NULL, thisrec, WHAT_UPDATE, + 0); + wid = gtk_vscale_new (GTK_ADJUSTMENT (adjust)); + gtk_scale_set_digits (GTK_SCALE (wid), 0); + gtk_scale_set_draw_value (GTK_SCALE (wid), FALSE); + gtk_widget_set_name (wid, extnames[i]); + wid2 = gtk_vscale_new (GTK_ADJUSTMENT (adjust2)); +#ifndef GTK1_ONLY + if (change_color) + { + gtk_widget_modify_bg (wid, GTK_STATE_NORMAL, &color); + gtk_widget_modify_bg (wid2, GTK_STATE_NORMAL, &color); + } +#endif /* !GTK1_ONLY */ + gtk_scale_set_digits (GTK_SCALE (wid2), 0); + gtk_scale_set_draw_value (GTK_SCALE (wid2), FALSE); + gtk_widget_set_name (wid2, extnames[i]); + + frame = gtk_frame_new (extnames[i]); + manage_label (frame, thisrec); + /* gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); */ + gtk_box_pack_start (GTK_BOX (pw), frame, FALSE, FALSE, 1); + box = gtk_hbox_new (FALSE, 1); + gtk_container_add (GTK_CONTAINER (frame), box); + gtk_box_pack_start (GTK_BOX (box), wid, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box), wid2, TRUE, TRUE, 0); + gtk_widget_show_all (frame); + break; +#endif + + case MIXT_MONOSLIDER: + case MIXT_MONOSLIDER16: + case MIXT_SLIDER: + if (!show_all) + break; + val = get_value (thisrec); + mx = thisrec->maxvalue; + + if (thisrec->type == MIXT_MONOSLIDER) + val &= 0xff; + else if (thisrec->type == MIXT_MONOSLIDER16) + val &= 0xffff; + + val = mx - val; + if (*thisrec->id == 0) + extnames[i] = extnames[parent]; + else + { + snprintf (tmp, sizeof(tmp), "%s.%s", extnames[parent], name); + store_name (dev, i, tmp, extnames); + } + adjust = gtk_adjustment_new (val, 0, mx, 1, 5, 0); + connect_scrollers (thisrec, adjust, NULL, NULL); + create_update (NULL, adjust, NULL, NULL, thisrec, WHAT_UPDATE, 0); + wid = gtk_vscale_new (GTK_ADJUSTMENT (adjust)); + check_tooltip(thisrec, wid); +#ifndef GTK1_ONLY + if (change_color) + gtk_widget_modify_bg (wid, GTK_STATE_NORMAL, &color); + gtk_widget_set_size_request (wid, -1, 80); +#endif /* !GTK1_ONLY */ + gtk_scale_set_digits (GTK_SCALE (wid), 0); + gtk_scale_set_draw_value (GTK_SCALE (wid), FALSE); + gtk_widget_set_name (wid, extnames[i]); + + if (strcmp (extnames[parent], extnames[i]) != 0) + { + frame = gtk_frame_new (extnames[i]); + manage_label (frame, thisrec); + /* gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); */ + gtk_box_pack_start (GTK_BOX (pw), frame, FALSE, FALSE, 1); + gtk_container_add (GTK_CONTAINER (frame), wid); + gtk_widget_show_all (frame); + } + else + { + gtk_box_pack_start (GTK_BOX (pw), wid, FALSE, FALSE, 1); + gtk_widget_show (wid); + } + break; + + case MIXT_ENUM: + + if (!show_all) + break; + if (*thisrec->id == 0) + extnames[i] = extnames[parent]; + else + { + snprintf (tmp, sizeof(tmp), "%s.%s", extnames[parent], name); + store_name (dev, i, tmp, extnames); + } + val = get_value (thisrec) & 0xff; + + wid = gtk_combo_new (); + check_tooltip(thisrec, wid); +#ifndef GTK1_ONLY + if (change_color) + gtk_widget_modify_fg (wid, GTK_STATE_NORMAL, &color); +#endif /* !GTK1_ONLY */ + { + GList *opt = NULL; + + if (!(thisrec->flags & MIXF_WIDE)) + gtk_widget_set_usize (wid, 100 + 20 * width_adjust, -1); + opt = load_enum_values (extnames[i], thisrec); + gtk_combo_set_popdown_strings (GTK_COMBO (wid), opt); + g_list_free (opt); + + gtk_combo_set_use_arrows_always (GTK_COMBO (wid), 1); + gtk_entry_set_editable (GTK_ENTRY (GTK_COMBO (wid)->entry), + FALSE); + } + connect_enum (thisrec, GTK_OBJECT (GTK_COMBO (wid)->entry)); + create_update (NULL, NULL, NULL, wid, thisrec, WHAT_UPDATE, i); + gtk_widget_set_name (wid, extnames[i]); + frame = gtk_frame_new (extnames[i]); +#ifndef GTK1_ONLY + if (change_color) + gtk_widget_modify_bg (wid, GTK_STATE_NORMAL, &color); +#endif /* !GTK1_ONLY */ + manage_label (frame, thisrec); + gtk_box_pack_start (GTK_BOX (pw), frame, TRUE, FALSE, 0); + gtk_container_add (GTK_CONTAINER (frame), wid); + gtk_widget_show_all (frame); + break; + + case MIXT_MARKER: + show_all = 1; + change_orient = FALSE; + break; + + default: + fprintf (stderr, "Unknown type for control %s\n", thisrec->extname); + } + + } + + g_free (extnames); + g_free (widgets); + g_free (orient); + return rootwid; +} + +/* + * Creates the widget tree. Returns dimensions of scrolledwin. + */ +static GtkRequisition +create_widgets (void) +{ + GtkRequisition Dimensions; + char tmp[100]; + + scrolledwin = gtk_scrolled_window_new (NULL, NULL); + /* + * A GtkScrolledWindow placed inside a GtkWindow is not considered to have + * a demand for space on its parent if it's policy is GTK_POLICY_AUTOMATIC. + * So if the window is realized, if will be reduced to the + * GtkScrolledWindow's minimum size which is quite small. + * + * To get around this, we setup scrolledwin with GTK_POLICY_NEVER, get the + * size GTK would have used for the window in that case, and use it as a + * default size for the window after we've reset the policy to + * GTK_POLICY_AUTOMATIC. + */ + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin), + GTK_POLICY_NEVER, GTK_POLICY_NEVER); + gtk_container_add (GTK_CONTAINER (window), scrolledwin); + if (!load_all_devs) + { + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolledwin), + load_devinfo (dev)); + if (root[dev] == NULL) + { + fprintf (stderr, "No device root node\n"); + exit (-1); + } + snprintf (tmp, sizeof(tmp), "ossxmix - device %d / %s", + dev, root[dev]->name); + gtk_window_set_title (GTK_WINDOW (window), tmp); + gtk_widget_size_request (scrolledwin, &Dimensions); + } + else + { + GtkWidget * notebook, * tab; + GtkRequisition tDimensions; + + notebook = load_multiple_devs (); + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolledwin), + notebook); + gtk_window_set_title (GTK_WINDOW (window), "ossxmix"); + gtk_widget_size_request (scrolledwin, &Dimensions); + + /* + * Dimensions.height doesn't include the tab for some reason. + * I hate GTK. + */ + tab = gtk_notebook_get_tab_label (GTK_NOTEBOOK (notebook), + gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), + gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook)))); + gtk_widget_size_request (tab, &tDimensions); + Dimensions.height += tDimensions.height; + } + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_widget_show (scrolledwin); + return Dimensions; +} + +/* + * Reload the entire GUI + */ +static void +reload_gui (void) +{ +#define FREECTL(x) do { \ + for (i=0; i < MAX_DEVS; i++) \ + { \ + for (p = x[i]; p != NULL;) \ + { \ + nextp = p->next; \ + g_free (p); \ + p = nextp; \ + } \ + x[i] = NULL; \ + } \ + } while (0) + + ctlrec_t * p, * nextp; + int i; + + remove_timeout ((gpointer)poll_tag_list); + fully_started = 0; + FREECTL (control_list); FREECTL (peak_list); + FREECTL (check_list); FREECTL (value_poll_list); + for (i=0; i < MAX_DEVS; i++) + { + root[i] = NULL; + g_free (extrec[i]); + extrec[i] = NULL; + if (local_fd[i] != -1) + { + close (local_fd[i]); + local_fd[i] = -1; + } + } + + gtk_widget_destroy (scrolledwin); + create_widgets (); + fully_started = 1; + add_timeout ((gpointer)poll_tag_list); +#undef FREECTL +} + +/* + * The update_label() routine is used to update the values of certain + * read only controls. + */ + +static void +update_label (oss_mixext * mixext, GtkWidget * wid, int val) +{ + char tmp[100]; + + if (mixext->type == MIXT_HEXVALUE) + snprintf (tmp, sizeof(tmp), "[%s: 0x%x] ", + gtk_widget_get_name (wid), val); + else + snprintf (tmp, sizeof(tmp), "[%s: %d] ", + gtk_widget_get_name (wid), val); + + if (mixext->flags & MIXF_HZ) + { + if (val > 1000000) + { + snprintf (tmp, sizeof(tmp), "[%s: %d.%03d MHz] ", + gtk_widget_get_name (wid), val / 1000000, + (val / 1000) % 1000); + } + else if (val > 1000) + { + snprintf (tmp, sizeof(tmp), "[%s: %d.%03d kHz] ", + gtk_widget_get_name (wid), val / 1000, val % 1000); + } + else + snprintf (tmp, sizeof(tmp), "[%s: %d Hz] ", + gtk_widget_get_name (wid), val); + } + else if (mixext->flags & MIXF_OKFAIL) + { + if (val != 0) + snprintf (tmp, sizeof(tmp), "[%s: Ok] ", + gtk_widget_get_name (wid)); + else + snprintf (tmp, sizeof(tmp), "[%s: Fail] ", + gtk_widget_get_name (wid)); + } + gtk_label_set (GTK_LABEL (wid), tmp); +} + +/* + * The do_update() routine reads a value of certain mixer control + * and updates the on-screen value depending on the type of the control. + */ + +static void +do_update (ctlrec_t * srec) +{ + int val, mx, left, right, vol, angle; + int mask = 0xff, shift = 8; + + val = get_value (srec->mixext); + if (val == -1) return; + + if (srec->mixext->type == MIXT_MONOSLIDER16 + || srec->mixext->type == MIXT_STEREOSLIDER16) + { + mask = 0xffff; + shift = 16; + } + + switch (srec->mixext->type) + { + case MIXT_ONOFF: +#ifdef MIXT_MUTE + case MIXT_MUTE: +#endif /* MIXT_MUTE */ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (srec->gang), val); + break; + + case MIXT_ENUM: + gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (srec->gang)->entry), + showenum (srec->mixext, val)); + break; + + case MIXT_VALUE: + case MIXT_HEXVALUE: + update_label (srec->mixext, (srec->gang), val); + break; + + case MIXT_SLIDER: + mx = srec->mixext->maxvalue; + val = mx - val; + gtk_adjustment_set_value (GTK_ADJUSTMENT (srec->left), val); + break; + + case MIXT_MONOSLIDER: + case MIXT_MONOSLIDER16: + mx = srec->mixext->maxvalue; + val = mx - (val & mask); + gtk_adjustment_set_value (GTK_ADJUSTMENT (srec->left), val); + break; + + case MIXT_STEREOSLIDER: + case MIXT_STEREOSLIDER16: + mx = srec->mixext->maxvalue; + left = mx - (val & mask); + right = mx - ((val >> shift) & mask); + if (srec->gang != NULL) + if (left != right) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (srec->gang), 0); + gtk_adjustment_set_value (GTK_ADJUSTMENT (srec->left), left); + gtk_adjustment_set_value (GTK_ADJUSTMENT (srec->right), right); + break; + + case MIXT_3D: +#ifdef TEST_JOY + if (srec->gang != NULL) + gtk_joy_set_level (GTK_JOY (srec->gang), val); +#else + vol = 100 - (val & 0x00ff); + angle = 360 - ((val >> 16) & 0xffff); + gtk_adjustment_set_value (GTK_ADJUSTMENT (srec->left), vol); + gtk_adjustment_set_value (GTK_ADJUSTMENT (srec->right), angle); +#endif + break; + } +} + +/* + * The poll_all() routine get's called reqularily. It checks the + * modify counter for the mixer by calling {!nlink SNDCTL_MIXERINFO}. + * It checks if some other mixer program has made changes to the settings + * by comparing the modify counter against the "expected" value. + * + * If the mixer was changed then all the controls will be reloaded and updated. + */ + +/*ARGSUSED*/ +static gint +poll_all (gpointer data) +{ + ctlrec_t *srec; + oss_audioinfo ainfo; + char new_label[FRAME_NAME_LENGTH+1] = ""; + int status_changed = 0; + oss_mixerinfo inf; + + inf.dev = dev; + if (ioctl (global_fd, SNDCTL_MIXERINFO, &inf) == -1) + { + perror ("SNDCTL_MIXERINFO"); + exit (-1); + } + if (!inf.enabled) + { + reload_gui (); + return TRUE; + } + +/* + * Compare the modify counter. + */ + if ((inf.modify_counter - prev_update_counter[dev]) > set_counter[dev]) + status_changed = 1; + prev_update_counter[dev] = inf.modify_counter; + set_counter[dev] = 0; + + srec = check_list[dev]; + + while (srec != NULL) + { + switch (srec->what_to_do) + { + case WHAT_LABEL: +/* + * Names of certain mixer controls depend on the application that is using + * the associated audio device. Handling for this is here + */ + ainfo.dev = srec->parm; + if (ioctl (global_fd, SNDCTL_ENGINEINFO, &ainfo) == -1) + { + perror ("SNDCTL_ENGINEINFO"); + continue; + } + if (*ainfo.label != '\0') + { + strncpy (new_label, ainfo.label, FRAME_NAME_LENGTH); + new_label[FRAME_NAME_LENGTH] = '\0'; + } + else + { + snprintf (new_label, FRAME_NAME_LENGTH, "pcm%d", srec->parm); + } + if ((srec->frame != NULL) && + (strncmp (srec->frame_name, new_label, FRAME_NAME_LENGTH))) + { + strcpy (srec->frame_name, new_label); + gtk_frame_set_label (GTK_FRAME (srec->frame), new_label); + } + break; + case WHAT_VMIX: +/* + * The aforementioned mixer controls can be create dynamically, so ossxmix + * needs to poll for this. Handling for this is here + */ + if (inf.nrext != srec->parm) + { + srec->parm = inf.nrext; +/* + * Since we know the added controls are vmix controls, we should be able to do + * something more graceful here, like reloading only the current device, or + * even adding the controls directly. This will do for now. + */ + reload_gui (); + return TRUE; + } + break; + case WHAT_UPDATE: + if (status_changed) + do_update (srec); + break; + } + srec = srec->next; + } + return TRUE; +} + +/* + * The poll_peaks() routine gets called several times per second to update the + * VU/peak meter LED bar widgets. + */ + +/*ARGSUSED*/ +static gint +poll_peaks (gpointer data) +{ + ctlrec_t *srec; + int val, left, right; + + srec = peak_list[dev]; + + while (srec != NULL) + { + val = get_value (srec->mixext); + if (val == -1) return TRUE; + + left = val & 0xff; + right = (val >> 8) & 0xff; + + if (left > srec->last_left) + srec->last_left = left; + + if (right > srec->last_right) + srec->last_right = right; + + left = srec->last_left; + right = srec->last_right; + + /* gtk_adjustment_set_value(GTK_ADJUSTMENT(srec->left), left); + gtk_adjustment_set_value(GTK_ADJUSTMENT(srec->right), right); */ + gtk_vu_set_level (GTK_VU (srec->left), + (left * 8) / srec->mixext->maxvalue); + + if (srec->right != NULL) + gtk_vu_set_level (GTK_VU (srec->right), + (right * 8) / srec->mixext->maxvalue); + + + if (srec->last_left > 0) + srec->last_left -= PEAK_DECAY; + if (srec->last_right > 0) + srec->last_right -= PEAK_DECAY; + + srec = srec->next; + } + + return TRUE; +} + +/*ARGSUSED*/ +static gint +poll_values (gpointer data) +{ + ctlrec_t *srec; + int val; + + srec = value_poll_list[dev]; + + while (srec != NULL) + { + val = get_value (srec->mixext); + if (val == -1) return TRUE; + + update_label (srec->mixext, GTK_WIDGET (srec->left), val); + + srec = srec->next; + } + + return TRUE; +} + +/*ARGSUSED*/ +static gint +poll_mixnum (gpointer data) +{ + int c_mixer_num; + + if (ioctl (global_fd, SNDCTL_MIX_NRMIX, &c_mixer_num) == -1) + { + perror ("SNDCTL_MIX_NRMIX"); + exit (-1); + } + + if (c_mixer_num > MAX_DEVS) c_mixer_num = MAX_DEVS; + if (c_mixer_num != mixer_num) reload_gui (); + + return TRUE; +} + +static int +find_default_mixer (void) +{ + oss_mixerinfo mi; + int i, best = -1, bestpri = 0, mix_num; + + if (ioctl (global_fd, SNDCTL_MIX_NRMIX, &mix_num) == -1) + { + perror ("SNDCTL_MIX_NRMIX"); + if (errno == EINVAL) + fprintf (stderr, "Error: OSS version 4.0 or later is required\n"); + exit (-1); + } + + if (mix_num == 0) + { + fprintf (stderr, "No mixers are available\n"); + exit (-1); + } + + for (i = 0; i < mix_num; i++) + { + mi.dev = i; + + if (ioctl (global_fd, SNDCTL_MIXERINFO, &mi) == -1) + continue; /* Ignore errors */ + + if (mi.enabled) + { + if (best == -1) best = i; + + if (mi.priority > bestpri) + { + best = i; + bestpri = mi.priority; + } + } + } + + if (best == -1) + { + fprintf (stderr, "No mixers are available for use as a default mixer\n"); + exit (-1); + } + + return best; +} + +static void +parse_dimarg (const char * dimarg, GtkRequisition * Dimensions) +{ + long height = 0, width = 0; + char * p; + + errno = 0; + width = strtol (dimarg, &p, 10); + if (errno || (width <= 0)) return; + if (width > Dimensions->width) width = Dimensions->width; + height = width; + if (*p != '\0') + { + errno = 0; + height = strtol (p+1, NULL, 10); + if (errno || (height <= 0)) height = width; + } + + Dimensions->width = width; + if (height < Dimensions->height) Dimensions->height = height; + return; +} + +int +main (int argc, char **argv) +{ + extern char * optarg; + char * dimarg = NULL; + int i, v, c; + GtkRequisition Dimensions; +#ifndef GTK1_ONLY + GdkPixbuf *icon_pix; +#else + GdkPixmap *icon_pix; + GdkBitmap *icon_mask; +#endif /* !GTK1_ONLY */ + + const char *devmixer; + oss_sysinfo si; + + for (i=0; i< MAX_DEVS; i++) + local_fd[i] = -1; /* Not opened */ + + if ((devmixer=getenv("OSS_MIXERDEV"))==NULL) + devmixer = "/dev/mixer"; + +#if !defined(GTK1_ONLY) && defined(DEBUG) + g_mem_set_vtable (glib_mem_profiler_table); +#endif + /* Get Gtk to process the startup arguments */ + gtk_init (&argc, &argv); + + while ((c = getopt (argc, argv, "Sbd:g:hn:w:x")) != EOF) + switch (c) + { + case 'd': + dev = atoi (optarg); + load_all_devs = 0; + break; + + case 'w': + v = 0; + v = atoi (optarg); + if (v <= 0) + v = 1; + width_adjust += v; + break; + + case 'n': + v = 0; + v = atoi (optarg); + if (v <= 0) + v = 1; + width_adjust -= v; + break; + + case 'x': + show_all = 0; + break; + + case 'b': + background = 1; + break; + + case 'S': + show_status_icon = 0; + break; + + case 'g': + dimarg = optarg; + break; + + case 'h': + printf ("Usage: %s [options...]\n", argv[0]); + printf (" -h Prints help (this screen)\n"); + printf (" -d<dev#> Selects the mixer device\n"); + printf (" -x Hides the \"legacy\" mixer controls\n"); + printf (" -w[val] Make mixer bit wider on screen\n"); + printf (" -n[val] Make mixer bit narrower on screen\n"); + printf (" -b Start mixer in background\n"); + printf (" -g[w:h] Start mixer window with w:h size\n"); +#ifdef STATUSICON + printf (" -S Don't place an icon in system tray\n"); +#endif /* STATUSICON */ + exit (0); + break; + } + + if (width_adjust < -2) + width_adjust = -2; + if (width_adjust > 4) + width_adjust = 4; + + if ((global_fd = open (devmixer, O_RDWR, 0)) == -1) + { + perror (devmixer); + exit (-1); + } + + atexit (cleanup); + + if (ioctl(global_fd, SNDCTL_SYSINFO, &si) != -1) + if (si.versionnum == 0x040003) /* Boomer 4.0 */ + boomer_workaround = 1; + + if (dev == -1) + dev = find_default_mixer (); + + v = chdir ("/"); /* We don't really care if this fails */ + + /* Create the app's main window */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + Dimensions = create_widgets (); + if (dimarg != NULL) parse_dimarg (dimarg, &Dimensions); + gtk_window_set_default_size (GTK_WINDOW (window), + Dimensions.width, Dimensions.height); + + fully_started = 1; + + /* Connect a window's signal to a signal function */ + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (close_request), NULL); + +#ifndef GTK1_ONLY + icon_pix = gdk_pixbuf_new_from_xpm_data ((const char **)ossxmix); +#ifdef STATUSICON + if (show_status_icon) + { + char tmp[100]; + + status_icon = gtk_status_icon_new_from_pixbuf (icon_pix); + snprintf (tmp, sizeof(tmp), "ossxmix - device %d / %s", + dev, root[dev]->name); + gtk_status_icon_set_tooltip (status_icon, tmp); + g_signal_connect (G_OBJECT (status_icon), "popup-menu", + G_CALLBACK (trayicon_popupmenu), NULL); + g_signal_connect (G_OBJECT (status_icon), "activate", + G_CALLBACK (activate_mainwindow), NULL); + } +#endif /* STATUSICON */ + gtk_window_set_icon (GTK_WINDOW (window), icon_pix); + + g_signal_connect (G_OBJECT (window), "window-state-event", + G_CALLBACK (manage_timeouts), (gpointer)poll_tag_list); + if ((!background) || (!show_status_icon)) + { + gtk_widget_show (window); + if (background) gtk_window_iconify (GTK_WINDOW (window)); + } +#if GTK_CHECK_VERSION(2,2,0) + else gdk_notify_startup_complete (); +#endif +#else + add_timeout ((gpointer)poll_tag_list); + gtk_widget_show (window); + if (background) XIconifyWindow (GDK_WINDOW_XDISPLAY (window->window), + GDK_WINDOW_XWINDOW (window->window), + DefaultScreen (GDK_DISPLAY ())); + icon_pix = gdk_pixmap_create_from_xpm_d (window->window, &icon_mask, + &window->style->bg[GTK_STATE_NORMAL], + (gchar **)ossxmix); + gdk_window_set_icon (window->window, NULL, icon_pix, icon_mask); +#endif /* !GTK1_ONLY */ + + gtk_main (); + + return 0; +} + +/* + * Function to run when program is shutting down + */ +static void +cleanup (void) +{ + int i; + + close (global_fd); + + for (i=0;i<MAX_DEVS;i++) + if (local_fd[i] != -1) + close (local_fd[i]); + +#if !defined(GTK1_ONLY) && GTK_CHECK_VERSION(2,2,0) + gdk_notify_startup_complete (); +#endif /* !GTK1_ONLY */ + +#ifdef DEBUG + g_mem_profile (); +#endif +} + +/* + * Function to handle a close signal on the window + */ +/*ARGSUSED*/ +static gint +close_request (GtkWidget * theWindow, gpointer data) +{ +#ifdef STATUSICON + if (show_status_icon && (gtk_status_icon_is_embedded (status_icon) == TRUE)) + { + gtk_widget_hide (window); + return TRUE; + } +#endif + gtk_main_quit (); + return FALSE; +} + +/* + * Function to make sure only the currently shown mixer is polled + */ +/*ARGSUSED*/ +static void +switch_page (GtkNotebook * notebook, GtkNotebookPage * page, + guint page_num, gpointer data) +{ +#ifdef STATUSICON + char tmp[100]; + + if ((show_status_icon) && + (gtk_status_icon_is_embedded (status_icon) == TRUE)) + { + snprintf (tmp, sizeof(tmp), "ossxmix - device %d / %s", + page_num, root[page_num]->name); + gtk_status_icon_set_tooltip (status_icon, tmp); + } +#endif /* STATUSICON */ + + /* + * GTK1 calls switch_page when scrolledwin is destroyed in reload_gui. + * This is merely annoying, but this check prevents it nonetheless. + */ + if (fully_started == 0) return; + remove_timeout (data); + dev = page_num; + add_timeout (data); +} + +/* + * Function to start polling mixer 'dev' + */ +/*ARGSUSED*/ +static gint +add_timeout (gpointer data) +{ + guint *poll_tag_list = (guint *) data; + + if ((peak_list[dev] != NULL) && (poll_tag_list[0] == 0)) + poll_tag_list[0] = g_timeout_add (PEAK_POLL_INTERVAL, poll_peaks, NULL); + if ((value_poll_list[dev] != NULL) && (poll_tag_list[1] == 0)) + poll_tag_list[1] = g_timeout_add (VALUE_POLL_INTERVAL, poll_values, NULL); + if (poll_tag_list[2] == 0) + poll_tag_list[2] = g_timeout_add (MIXER_POLL_INTERVAL, poll_all, NULL); + if ((poll_tag_list[3] == 0) && (load_all_devs)) + poll_tag_list[3] = g_timeout_add (MIXNUM_POLL_INTERVAL, poll_mixnum, NULL); + return FALSE; +} + +/* + * Function to stop polling mixer 'dev' + */ +/*ARGSUSED*/ +static gint +remove_timeout (gpointer data) +{ + guint *poll_tag_list = (guint *) data; + + if (poll_tag_list[0] != 0) + { + g_source_remove (poll_tag_list[0]); + poll_tag_list[0] = 0; + } + if (poll_tag_list[1] != 0) + { + g_source_remove (poll_tag_list[1]); + poll_tag_list[1] = 0; + } + if (poll_tag_list[2] != 0) + { + g_source_remove (poll_tag_list[2]); + poll_tag_list[2] = 0; + } + if (poll_tag_list[3] != 0) + { + g_source_remove (poll_tag_list[3]); + poll_tag_list[3] = 0; + } + return FALSE; +} + +#ifndef GTK1_ONLY +/* + * Function to make sure polling isn't done when window is minimized or hidden + */ +/*ARGSUSED*/ +static gint +manage_timeouts (GtkWidget * w, GdkEventWindowState * e, gpointer data) +{ + if (e->new_window_state & + (GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_WITHDRAWN)) + { + remove_timeout (data); + return FALSE; + } + add_timeout (data); + return FALSE; +} +#endif /* !GTK1_ONLY */ + +#ifdef STATUSICON +/*ARGSUSED*/ +static void +activate_mainwindow (GtkStatusIcon * icon, guint button, guint etime, + gpointer data) +{ + if (GTK_WIDGET_VISIBLE (window)) gtk_widget_hide (window); + else popup_mainwindow (NULL, NULL); +} + +/*ARGSUSED*/ +static void +popup_mainwindow (GtkWidget * w, gpointer data) +{ + gtk_widget_show (window); + gtk_window_present (GTK_WINDOW (window)); +} + +/* + * Popup menu when clicking on status icon + */ +/*ARGSUSED*/ +static void +trayicon_popupmenu (GtkStatusIcon * icon, guint button, guint etime, + gpointer data) +{ + static GtkWidget *tray_menu = NULL; + + if (tray_menu == NULL) + { + GtkWidget *item; + tray_menu = gtk_menu_new (); + + item = gtk_menu_item_new_with_label ("Restore"); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (popup_mainwindow), NULL); + gtk_menu_append (tray_menu, item); + item = gtk_menu_item_new_with_label ("Quit"); + gtk_menu_append (tray_menu, item); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (gtk_main_quit), NULL); + } + + gtk_widget_show_all (tray_menu); + + gtk_menu_popup (GTK_MENU (tray_menu), NULL, NULL, + gtk_status_icon_position_menu, status_icon, + button, etime); +} +#endif /* STATUSICON */ + diff --git a/cmd/ossxmix/ossxmix.man b/cmd/ossxmix/ossxmix.man new file mode 100644 index 0000000..31b74e0 --- /dev/null +++ b/cmd/ossxmix/ossxmix.man @@ -0,0 +1,42 @@ +NAME +ossxmix - Open Sound System GTK based GUI mixer program. + +SYNOPSIS +ossxmix [-Sbhx] [-d <dev#>] [-w <value>] [-n <value>] + +DESCRIPTION +ossxmix is a GTK+ based mixer applet that is used to display the +mixer settings of physical and virtual audio devices. There can be +several physical mixers for a single audio device. Mixers found on +audio devices are controllers that set the volume, select the input, +perform certain functions on the speakers or set various device +characteristics. + +OPTIONS +-h Display usage instructions. +-d<dev#> Display only mixer device dev#. If this option is not given then + ossxmix will display all the mixers available on the system. +-x Hides the "legacy" mixer controls. +-w[value] Make the mixer slightly wider on the screen. This option affects + only the selection boxes and peak meter LED bars. In most cases + this option is not required. The value can be between 1 (default) + and 4. +-n[value] Make the mixer slightly narrower than normally. This may be + necessary if the mixer is wider than the screen. The value can be + 1 (default) or 2. +-g[w:h] Start ossxmix window with dimnesions w:h. +-b Start in background. +-S Do not try to place an icon in the system tray. + +NOTES +o ossxmix without the -d parameter shows all the currently enabled mixers. +o Standard gtk toolkit options like --display are available as well. + +SEE ALSO +ossdevlinks(1), ossmix(1), savemixer(1) + +FILES +/usr/bin/ossxmix + +AUTHOR +4Front Technologies diff --git a/cmd/ossxmix/ossxmix.xpm b/cmd/ossxmix/ossxmix.xpm new file mode 100644 index 0000000..3c27a6e --- /dev/null +++ b/cmd/ossxmix/ossxmix.xpm @@ -0,0 +1,312 @@ +/* XPM */ +static const char *ossxmix[] = { +/* width height num_colors chars_per_pixel */ +" 121 51 254 2", +/* colors */ +".. c #020202", +".# c #36e7fe", +".a c #0ac2fe", +".b c #0481cc", +".c c #fe6666", +".d c #025690", +".e c #02426d", +".f c #6c110d", +".g c #0e3a46", +".h c #e65856", +".i c #f6f6f6", +".j c #a2a2a2", +".k c #66120e", +".l c #06344c", +".m c #62120e", +".n c #cecece", +".o c #022e4e", +".p c #7a7a7a", +".q c #eaeaea", +".r c #8a8a8a", +".s c #022a44", +".t c #76566a", +".u c #cacaca", +".v c #9aeefe", +".w c #7e7e7e", +".x c #5e110d", +".y c #022335", +".z c #c4c4c4", +".A c #5d5d5d", +".B c #1e9aba", +".C c #81ecfe", +".D c #cb4e4b", +".E c #021c2d", +".F c #fe7e7e", +".G c #451210", +".H c #7ce2fd", +".I c #74d6fa", +".J c #b34645", +".K c #525652", +".L c #6eecfe", +".M c #021622", +".N c #be1e1e", +".O c #3b3b3b", +".P c #4ab2d6", +".Q c #12d2fe", +".R c #333333", +".S c #270705", +".T c #0e7292", +".U c #02121c", +".V c #9e6e86", +".W c #0eb6fe", +".X c #65eafe", +".Y c #0a4662", +".Z c #a31d1a", +".0 c #220606", +".1 c #3292be", +".2 c #e6e6e6", +".3 c #025d9d", +".4 c #2ed6fe", +".5 c #8c3d3e", +".6 c #2496b4", +".7 c #0e4c66", +".8 c #d29ea2", +".9 c #020e12", +"#. c #fe928e", +"## c #b0b0b0", +"#a c #252525", +"#b c #57ebfe", +"#c c #2a2a2a", +"#d c #1e1e1e", +"#e c #424242", +"#f c #63dbf9", +"#g c #5bc9f1", +"#h c #1e7e96", +"#i c #025286", +"#j c #7d1511", +"#k c #0363a3", +"#l c #a2768a", +"#m c #2a2e2a", +"#n c #5dcef7", +"#o c #8e8e8e", +"#p c #363a36", +"#q c #4fe6fe", +"#r c #2ea6ba", +"#s c #0f74a8", +"#t c #3db8ec", +"#u c #464646", +"#v c #1e0606", +"#w c #249bd8", +"#x c #2e322e", +"#y c #1f6c79", +"#z c #923636", +"#A c #39dafe", +"#B c #c66a76", +"#C c #027ed6", +"#D c #1a1a1a", +"#E c #bdbdbd", +"#F c #a23e3e", +"#G c #024e82", +"#H c #40aade", +"#I c #06aefe", +"#J c #0265ad", +"#K c #7e2e2a", +"#L c #e1e1e1", +"#M c #fe9696", +"#N c #48dafe", +"#O c #020a10", +"#P c #fe7677", +"#Q c #43f3fe", +"#R c #e66666", +"#S c #25a9e0", +"#T c #74b4ce", +"#U c #1ac6fc", +"#V c #521a1a", +"#W c #929292", +"#X c #c27682", +"#Y c #666666", +"#Z c #0272ba", +"#0 c #42e6fe", +"#1 c #1686a2", +"#2 c #991e1a", +"#3 c #728eae", +"#4 c #30d0fc", +"#5 c #0699f7", +"#6 c #562a32", +"#7 c #4a4a4a", +"#8 c #023e64", +"#9 c #7ff4fe", +"a. c #16bafc", +"a# c #0c6694", +"aa c #161616", +"ab c #fe9a9a", +"ac c #ca2e2e", +"ad c #6a6a6a", +"ae c #47eefe", +"af c #2cc4f3", +"ag c #026ab0", +"ah c #1a627c", +"ai c #1a0606", +"aj c #024a7c", +"ak c #9d9d9d", +"al c #3cdefe", +"am c #8c262c", +"an c #fa7a7e", +"ao c #223252", +"ap c #28b2e2", +"aq c #168dc4", +"ar c #5a1f1f", +"as c #faaeaa", +"at c #040608", +"au c #fe9e9e", +"av c #25b8e9", +"aw c #a02e2c", +"ax c #46bcf0", +"ay c #fe7a7a", +"az c #34aae4", +"aA c #d2d2d2", +"aB c #8e1c17", +"aC c #1ca1d5", +"aD c #ca625e", +"aE c #0e0e0e", +"aF c #fefefe", +"aG c #6e6e6e", +"aH c #d6bac6", +"aI c #6af4fe", +"aJ c #4ff3fe", +"aK c #1e221e", +"aL c #62eefe", +"aM c #4e524e", +"aN c #44eafe", +"aO c #0a6294", +"aP c #b6b6b6", +"aQ c #0e6ea1", +"aR c #1daddb", +"aS c #3bd1f2", +"aT c #1e96be", +"aU c #0282de", +"aV c #026ab9", +"aW c #56e2fe", +"aX c #a9a9a9", +"aY c #be3e3a", +"aZ c #0272c1", +"a0 c #023e6c", +"a1 c #f76565", +"a2 c #1e86ba", +"a3 c #5afafe", +"a4 c #121212", +"a5 c #e26e6e", +"a6 c #0a8ec6", +"a7 c #6e2222", +"a8 c #f75d5c", +"a9 c #360e0d", +"b. c #feadad", +"b# c #dd5a58", +"ba c #023a62", +"bb c #90f7fe", +"bc c #76e2fe", +"bd c #024274", +"be c #14a6e8", +"bf c #9eaabe", +"bg c #858585", +"bh c #f66e6c", +"bi c #b61a16", +"bj c #36e2fe", +"bk c #035b93", +"bl c #74f5fe", +"bm c #56120e", +"bn c #35c8f0", +"bo c #120202", +"bp c #831b17", +"bq c #80fbfe", +"br c #0b0a0b", +"bs c #023256", +"bt c #1382b9", +"bu c #fe7171", +"bv c #f0f0f0", +"bw c #237a8a", +"bx c #d8d8d8", +"by c #068ce3", +"bz c #4ae6fe", +"bA c #1498dd", +"bB c #362636", +"bC c #2e2e46", +"bD c #9e7e9a", +"bE c #5aacc7", +"bF c #24b2f8", +"bG c #2cbce7", +"bH c #2290cb", +"bI c #9bfcfe", +"bJ c #4aeafe", +"bK c #89f3fe", +"bL c #7e86a2", +"bM c #50ebfe", +"bN c #64f6fe", +"bO c #026ebe", +"bP c #024677", +"bQ c #b53533", +"bR c #123a5a", +"bS c #02365b", +"bT c #42e2fe", +"bU c #3ad6f8", +"bV c #0378c9", +"bW c #3de7fe", +"bX c #737373", +"bY c #8aeefe", +"bZ c #3ee2fe", +"b0 c #cd3736", +"b1 c #1876aa", +"b2 c #02253f", +"b3 c #f66a6b", +"b4 c #2e9ed6", +"b5 c #902e2a", +"b6 c #0e79b7", +"b7 c #626262", +/* pixels */ +"..................................................................................................................................................................................................................................................", +"..........................................ar.5.5.5.5#6.B#r#r#r#r#r#r#r#r#r#r#r#r#r#1..................................................................................at.7bwbwbwah#O..............................................................", +"......................................a9.J.F.F.F.FaybD#QaNaNaNaNaNaNaNaNaNaNaNaNaN.Q...................................................................................yafaeaN#Qbn#O..............................................................", +"....................................ar#Rayay#P#P#Pb3bEbWbTbTbZbZbZbZbZbZbZbZbZbZbW.a..................................................................................bSbU#0bTaNaR................................................................", +"................................ai.Jayay#P#P#P#P#P#XaSbWbTbZ#q#b#b#b#b#b#b#b#b#b#9.W..................................................................................bkbZbTbTae.T................................................................", +"..............................arb#ayay#P#P#P#P#Pbu#3bWbTbZ#qbYbc.H.H.Hbcbcbcbcbc#n#5.M................................................................................btbZbTbTae.Y................................................................", +"..........................#v#F#Payay#Pa1ay#P#P#Pa8bnbWbTbZbl#t#JaVaVaVaVbObObObOaZby.M................................................................................be#0bTbT.#.U................................................................", +"........................#Vb#ayayayb3bQ.hay#P#Pbu.V.#bTbTbT#q#4bGbGbGbGbGaCbOaVaVaZ#C.y.9.9.9.9.9.........U.9.9.9.9.9.U.U.U.U....at.U.9.9.9.9.9.9.9.9.9.9.9at.....Y#y#h#4#0bTbTal#y#y#y.Y..........................................................", +"....................bo#zbuayay#P.h#2#2#P#P#P#Pb3bEbWbTbTbTbTbT#0#0#0#0bWa.#Saq#Saf#AalbZbZbZ#AbU.6.Ub2.6albZbZbZbWbWbWbWbWbW.6.EajavbUaSaSaSaSaSbUaSaSaSaSap.g#OavaJaN#0bTbTbZbWbWbW#Qav..........................................................", +"...................Gb#ayayayb3b0#2#j.Day#P#P#P#BbnbWbTbTbTbTbTbTbTbZaN#UbG#0bZ#0#0#0#0bT#0bTbT#0a3#tapaN#0bTbTbJbTal#0bTbT#0aJaCbsaqaN#0#0#0bJ#AbU#A#0#0#0aNbTba#SbGaf#0bTbZ#0.LblblbqbE..........................................................", +"................#zbuayay#Pa8ac#2#jbpb3ay#P#PbubL.#bTbZbZbZbjbjbjbjbzblbebUbTbTbZ#bbnaCaf#0bZbZ#b#b.balbTbTbZaI.XbHbtbUbTbTbTaJbA.oav#0bTbZ#b#gbO#ibtbTbTbTbJ#Ab2bdbab6#0bTbZ#q#gazaz#tah..........................................................", +".............Gb#ayayayaya8ac.Zbp.xaYay#P#P#Pa1bnbWbTbZaLbY.C.C.C.CbYazbAbTbTbZbz.H.bbdb6bTbMaIbIbA.baNbTbZbTbYbAbP.dalbTbTbTbW.ba0#AbTbTbZ.XbH#ibs#S#0bTbZbMaR.ybabsaC#0bTbZaWbHbOaZbVb2..........................................................", +"..........#Kbhay#Payabb3ac.ZaB.f.x#Ray#P#P#P#l.#bTbTbZ#9#waU.b.b.b#CbOav#0bTbZ#9#taV#8bd#sa2#H#waV#S#0bTbZbM.IbVa0btaNbTbT#0.4aVa#bTbTbTbZbc#Z.ebs#A#0bTbTaebtbsbaa0af#0bTbZ#fb6agaV#J.M..........................................................", +"......a9b#ayay#Pauas.h.N.Zbp.f.xaw#P#P#P#PanbEbWbTbZbM.IbVbObOaVag#JbOalbTbZbTbba6.3babababd.dag#J#4#0bTbjbKbF#Jbdav#0bTbTaN#UbkaT#0bTbZbJ#g.dbS.daebTbTbTae#ibSba#ibUbTbTbW#f.d.3#k#G............................................................", +".....Sb#.Fay#P.Fb..hb0b0bQawb5b5b#ay#P#Pbu.8aSbZbTbZ#9azaV#J#J#J#kbkaqaNbTbZbMbcbVbkba#8baa0#i.3b6bZbTbZbTbIbybkbkbUbTbTbZae#I.3bG#0bTbZblaTbPbSa2aNbTbT#0bjbababSaQalbTbZ#0.Paj.dbkbs............................................................", +"....a7buay#P#P#P#P#Payayayayayayay#P#Pbu#.#TbWbTbZbzbKbV#kbkbkbk.d#ibn#0bTbZ.L#HaV#iba#8#8babPbPap#0bTbZ.X#NaU#GbtbZbTbZbTbMbyaOalbTbZbZ#9agba#8ap#0bTbZaebGbsbabSaq#0bTbZ#bb1bdaj#G.9............................................................", +".....Dayayayayayayayayayayay#P#P#P#P#P#PasafbWbTbZaI#naVbk#i#i#i#GaQalbTbZbjbKby#Jajba#8#8ba#8bdalbTbTbZbMa.aVbdap#0bTbZ#b#NbVbtaNbTbZbM#f.dba#G#4#0bTbZbNaqbs#8baaR#0bTbTbz#Gba.e.e..............................................................", +"..a9.c.ca1a1a1a1a1a1a1a1a1a1a1#P#P#P#P#.bfbjbTbTbZbKaT#k#ibdbdbda0#w#0bTbZ#b#nbO.3bPbSba#8#8bS#saNbTbTbTbT#4#SaSaNbTbTbZbqbF.3bG#0bTbZaL#tbS.E#sbWbTbZbZbq#G#O.9bS#4#0bTbT#4#sah.l.9..............................................................", +"..bmaBbpbpbpbpbpbpbpbpbpbp#jbQbubububub.#TbjbZal#0.HbV.3aj#8a0#8bPaSbWbZbj#9aCaV.db2.9bs#8#8a0aCaebTbZbZbZbZbZbZbZbZbZ#qbbbA#kbUbZbZbj.LaT.9..aCaNbZbZbMbc.y....ajbj#0bZbZ#0aJ#Qa6................................................................", +"...G.m.x.x.x.x.x.x.x.m.m.x.k.D#Mb.b.b.aHbnbNblblbIazaV.da0atat..bkbMaIblbl.CbV#k.e.....Ub2b2bS#JbnalbM#baLbNbNbNbNbNbl.vax#C#sbT#bblbq.Cag...Uav#0bJaLbI#t....#ObPaRbzbNaIbb#f#I#i................................................................", +"...S.f.m.m.m.m.m.m.m.m.m.m.kam.JaDa5bh.t.3a2#H#tax.b#J#G.s......bd#s.1#Hax#SbO.3b2..........b2ba.daOaQb1bta2bHbHbHbHbH.b#CaVbda#b6bHb4#wbP...y#iaO#sbtb4b6.....Uba#Gbtb4az#waU#5.y................................................................", +".....f.k.m.m.m.m.m.m.m.m.m.m.x.x.faBbiambababPbkaVbO.3bP.U....atbSbSba#i#kbO#k#iat...........s#8bababSbSbaa0bdbP#G#G.d#JbV#ibSbaa0#G.3bOb2...s#8bababPaga0.....E#8babaa0#G.3bVbV..................................................................", +"....bm.k.m.m.m.m.m.m.m.m.m.m.m.m.kbp#2am#8ba#8#i#kag.dbS........bS#8#8bPbkag.3bS............b2#8ba#8ba#8#8#8a0bdbP#G#i#k#Zbs#8#8#8aj.3#k.U..bs#8#8babP#J.y.....s#8#8ba.e#GbkbO#G..................................................................", +"....a9.k.k.m.k.m.k.m.m.m.m.m.m.m.m.k#jambR#8ba.e#i.3#G.y........bs#8ba#8aj.3#i.E.............E#8ba#8ba#8#8#8#8#8#8.ebP.d#k.E#8#8ba.e#i#G..atbS#8#8baa0#i.9....bS#8#8#8#8aj#ibkb2..................................................................", +"....#v.k.k.m.k.m.k.m.m.m.k.m.m.m.m.m.fbpao#8#8ba.e#ibP#O.........o#8babaa0#G.eat.............E#8ba#8ba#8ba#8ba#8baba#8#G.e.E#8ba#8#8ajbS..#Obabababa#8ba......a0#8ba#8ba.eajbP.U..................................................................", +".......0.0.0.0.0.0.0.0.0.0.0.G.k.m.m.m.fbC#8#8#8babdba..........b2#8#8ba#8bP.o...............E.e#8#8ba#8ba#8ba#8ba#8babPb2.E#8#8#8#8.e.E...9#8#8ba#8#8b2.....U.eba#8ba#8#8ba.U....................................................................", +".............................S.m.k.k.m.kbB#8#8#8#8#8.E...........y#8#8#8#8#8.U..............at.s#8#8#8#8#8#8#8#8#8#8ba.satb2#8#8ba#8#8at...M#8#8#8#8#8.M....atbsa0#8#8a0.oat......................................................................", +"...............................0.0#v#v.0br.U.M.U.M.Mat..........#O.M.U.M.U.M.....................M.U.U.U.U.U.U.U.U.M.9....#O.U.M.U.M.U....at.9.U.M.U.M........at.E.E.E.Eat........................................................................", +"..................................................................................................................................................................................................................................................", +"..........................aK#x#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#m#x#d", +"..........................#p.KaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaM.K.R", +"..........................#p.KaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaMaM.K.R", +"..........................#d#m#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#c#m#d", +"..................................................................................................................................................................................................................................................", +".......................................................................................................OaMaMaMaMaM#7......................a4#E#W....................................bX.i...............................O#a........................", +"......................................................................................................#EbxaAaF.zaAbv#e....................brbx.j....................................adaF..............................bx#o........................", +"......................................................................................................ak....aF#a..ak#Y......................##ak....................................#maF..............................aGaM........................", +"......................................................................................................#c....aF.R..#aaa......................##ak.....................................RaF....................................................at....", +"............................................................................................................aF.R......ataX.q##.....RaPaFaX..###E#EaF.r..#u##.O##.i.R....#e##bv###a...RaF....#D#Ebvak#c..#DaG#Lbv.A.A#abg.p....bX#Lbx#a...K#LbvaM..", +"............................................................................................................aF.R......##.p#DaXak...u.A#maX..###L#7bXaF..#daFaX#makbvaa..#EaK#d.j#E..#maF.....n.A#d#E##..aG.z#D.jaF.z..#L.r..bXaP#d.K.q.R.2#d#aak..", +"............................................................................................................aF.R....aM#L.....waAbX##........##ak...RaFb7..bv.R....aF#Dad.2.....AaF#u#caF...r#E....#eaF.R.q.O..at.iaa...ubgat.ibr..#daF.OaFaK......", +"............................................................................................................aF.R....aGaFaXaPbxaX#oak........aP.j...RaFad..bv.O..ataF#DbX#L.......i.K#caF..ak#E......aF.R.zaX..aK##.....ubgaEaFaPaP#Ebv#m.i.ibxbXbr", +"............................................................................................................aF.R....aG#L........#W##........aP.j...RaFad..bv.O..ataF#DbX.2.......i.K#caF..ak#E......aF.R#uaFaGaA.O.....ubgaEaFat.........RbX.naF.O", +"............................................................................................................aF#m....#7aFaM......bgbx........##ak...RaFad..bv.R..ataF#D.Abv.......n..#caF..bgaF#m..braF#dat##..br.......ubg..bv.j......aK.O.....i.O", +"........................................................................................................aa.AaF.w......bx.nad.pb7#aaF##b7bX#ebx.j...RaFakataF#o#DataFaGaEbvaX...paP...waF#e#DaFaM..ak#W...AaF.wbgbgaE#d.2##...waFbXadbg#e##...OaP..", +".........................................................................................................RaX#EaPaM....aK.z.u#YaE..bX.u.uaM.r#E###m...r###o#EaP#7..aX.j..#c.uakakaa..aX#Ebg..#u#Eak.j....#Ybx.u.n.i.n#e#E#Ea4...j.u#EaM#caAak##aE..", +".........................................................................................................................................................................................................i#m.....A.q..............................", +"........................................................................................................................................................................................................aF#a....#Ybx..............................", +".........................................................................................................................................................................................................i.R....aAad..............................", +".........................................................................................................................................................................................................O##bv.zb7................................", +"..........................................................................................................................................................................................................at.OaE.................................." +}; |