[PATCH (dockapps)] ascd: Add version 0.13.2 to repository.
Doug Torrance <[email protected]>
| Newsgroups | gmane.compw.window-managers.windowmaker.devel |
|---|---|
| Message-ID | <[email protected]> |
Obtained from: https://web.archive.org/web/20090620082249/http://worldserver.oleane.com/rsn/ascd-en.html --- ascd/Imakefile | 7 + ascd/README | 50 + ascd/ascd/Imakefile | 63 ++ ascd/ascd/README | 138 +++ ascd/ascd/ascd.c | 764 ++++++++++++++ ascd/ascd/ascd.h | 186 ++++ ascd/ascd/ascd.man | 267 +++++ ascd/ascd/cdcontrol.c | 362 +++++++ ascd/ascd/config.h | 6 + ascd/ascd/configure | 203 ++++ ascd/ascd/dirs.h | 3 + ascd/ascd/doc/CHANGES | 597 +++++++++++ ascd/ascd/doc/NEWS | 148 +++ ascd/ascd/ext.h | 164 +++ ascd/ascd/faktory.c | 1777 ++++++++++++++++++++++++++++++++ ascd/ascd/faktory_prot.h | 15 + ascd/ascd/handlers.c | 445 ++++++++ ascd/ascd/misc.c | 431 ++++++++ ascd/ascd/misc/config.h.default | 5 + ascd/ascd/misc/default | 8 + ascd/ascd/misc/dirs.h.default | 3 + ascd/ascd/mixer_ext.h | 13 + ascd/ascd/mixer_global.c | 15 + ascd/ascd/mixer_handlers.c | 134 +++ ascd/ascd/themes/default.tar | Bin 0 -> 40960 bytes ascd/ascd/themes/themes-manual.ps.gz | Bin 0 -> 82472 bytes ascd/ascd/themes/themes.tar | Bin 0 -> 92160 bytes ascd/ascd/version.h | 1 + ascd/ascd/wings_ext.h | 75 ++ ascd/ascd/wings_global.c | 79 ++ ascd/ascd/wings_main.c | 1070 +++++++++++++++++++ ascd/ascd/workman/include | 1 + ascd/ascd/workman/wm_cdda.h | 117 +++ ascd/ascd/workman/wm_cddb.h | 42 + ascd/ascd/workman/wm_cdinfo.h | 78 ++ ascd/ascd/workman/wm_cdrom.h | 64 ++ ascd/ascd/workman/wm_config.h | 357 +++++++ ascd/ascd/workman/wm_database.h | 44 + ascd/ascd/workman/wm_helpers.h | 105 ++ ascd/ascd/workman/wm_index.h | 36 + ascd/ascd/workman/wm_platform.h | 51 + ascd/ascd/workman/wm_scsi.h | 48 + ascd/ascd/workman/wm_struct.h | 181 ++++ ascd/ascd/workman/wm_version.h | 35 + ascd/ascd/workman/workman.h | 51 + ascd/ascd/workman/workman_defs.h | 30 + ascd/configure | 9 + ascd/default | 9 + ascd/libworkman/COPYING.LIB | 481 +++++++++ ascd/libworkman/Config | 48 + ascd/libworkman/Imakefile | 74 ++ ascd/libworkman/README | 6 + ascd/libworkman/buildindex.c | 223 ++++ ascd/libworkman/cddaslave.c | 565 ++++++++++ ascd/libworkman/cddb.c | 541 ++++++++++ ascd/libworkman/cdinfo.c | 887 ++++++++++++++++ ascd/libworkman/cdrom.c | 793 ++++++++++++++ ascd/libworkman/database.c | 1522 +++++++++++++++++++++++++++ ascd/libworkman/drv_sony.c | 173 ++++ ascd/libworkman/drv_toshiba.c | 154 +++ ascd/libworkman/include/wm_cdda.h | 117 +++ ascd/libworkman/include/wm_cddb.h | 42 + ascd/libworkman/include/wm_cdinfo.h | 78 ++ ascd/libworkman/include/wm_cdrom.h | 64 ++ ascd/libworkman/include/wm_config.h | 357 +++++++ ascd/libworkman/include/wm_database.h | 44 + ascd/libworkman/include/wm_helpers.h | 105 ++ ascd/libworkman/include/wm_index.h | 36 + ascd/libworkman/include/wm_platform.h | 51 + ascd/libworkman/include/wm_scsi.h | 48 + ascd/libworkman/include/wm_struct.h | 181 ++++ ascd/libworkman/include/wm_version.h | 35 + ascd/libworkman/include/workman.h | 51 + ascd/libworkman/include/workman_defs.h | 30 + ascd/libworkman/index.c | 385 +++++++ ascd/libworkman/plat_aix.c | 443 ++++++++ ascd/libworkman/plat_bsd386.c | 500 +++++++++ ascd/libworkman/plat_freebsd.c | 565 ++++++++++ ascd/libworkman/plat_hpux.c | 355 +++++++ ascd/libworkman/plat_irix.c | 456 ++++++++ ascd/libworkman/plat_linux.c | 866 ++++++++++++++++ ascd/libworkman/plat_linux_audio.c | 490 +++++++++ ascd/libworkman/plat_linux_cdda.c | 371 +++++++ ascd/libworkman/plat_news.c | 474 +++++++++ ascd/libworkman/plat_openbsd.c | 530 ++++++++++ ascd/libworkman/plat_osf1.c | 666 ++++++++++++ ascd/libworkman/plat_sun.c | 1188 +++++++++++++++++++++ ascd/libworkman/plat_sun_audio.c | 489 +++++++++ ascd/libworkman/plat_sun_cdda.c | 371 +++++++ ascd/libworkman/plat_svr4.c | 458 ++++++++ ascd/libworkman/plat_template.c | 269 +++++ ascd/libworkman/plat_ultrix.c | 666 ++++++++++++ ascd/libworkman/scsi.c | 531 ++++++++++ ascd/libworkman/wm_helpers.c | 229 ++++ ascd/version.h | 1 + 95 files changed, 25296 insertions(+) create mode 100644 ascd/Imakefile create mode 100644 ascd/README create mode 100644 ascd/ascd/Imakefile create mode 100644 ascd/ascd/README create mode 100644 ascd/ascd/ascd.c create mode 100644 ascd/ascd/ascd.h create mode 100644 ascd/ascd/ascd.man create mode 100644 ascd/ascd/cdcontrol.c create mode 100644 ascd/ascd/config.h create mode 100755 ascd/ascd/configure create mode 100644 ascd/ascd/dirs.h create mode 100644 ascd/ascd/doc/CHANGES create mode 100644 ascd/ascd/doc/NEWS create mode 100644 ascd/ascd/ext.h create mode 100644 ascd/ascd/faktory.c create mode 100644 ascd/ascd/faktory_prot.h create mode 100644 ascd/ascd/handlers.c create mode 100644 ascd/ascd/misc.c create mode 100644 ascd/ascd/misc/config.h.default create mode 100755 ascd/ascd/misc/default create mode 100644 ascd/ascd/misc/dirs.h.default create mode 100644 ascd/ascd/mixer_ext.h create mode 100644 ascd/ascd/mixer_global.c create mode 100644 ascd/ascd/mixer_handlers.c create mode 100644 ascd/ascd/themes/default.tar create mode 100644 ascd/ascd/themes/themes-manual.ps.gz create mode 100644 ascd/ascd/themes/themes.tar create mode 100644 ascd/ascd/version.h create mode 100644 ascd/ascd/wings_ext.h create mode 100644 ascd/ascd/wings_global.c create mode 100644 ascd/ascd/wings_main.c create mode 120000 ascd/ascd/workman/include create mode 100644 ascd/ascd/workman/wm_cdda.h create mode 100644 ascd/ascd/workman/wm_cddb.h create mode 100644 ascd/ascd/workman/wm_cdinfo.h create mode 100644 ascd/ascd/workman/wm_cdrom.h create mode 100644 ascd/ascd/workman/wm_config.h create mode 100644 ascd/ascd/workman/wm_database.h create mode 100644 ascd/ascd/workman/wm_helpers.h create mode 100644 ascd/ascd/workman/wm_index.h create mode 100644 ascd/ascd/workman/wm_platform.h create mode 100644 ascd/ascd/workman/wm_scsi.h create mode 100644 ascd/ascd/workman/wm_struct.h create mode 100644 ascd/ascd/workman/wm_version.h create mode 100644 ascd/ascd/workman/workman.h create mode 100644 ascd/ascd/workman/workman_defs.h create mode 100755 ascd/configure create mode 100755 ascd/default create mode 100644 ascd/libworkman/COPYING.LIB create mode 100644 ascd/libworkman/Config create mode 100644 ascd/libworkman/Imakefile create mode 100644 ascd/libworkman/README create mode 100644 ascd/libworkman/buildindex.c create mode 100644 ascd/libworkman/cddaslave.c create mode 100644 ascd/libworkman/cddb.c create mode 100644 ascd/libworkman/cdinfo.c create mode 100644 ascd/libworkman/cdrom.c create mode 100644 ascd/libworkman/database.c create mode 100644 ascd/libworkman/drv_sony.c create mode 100644 ascd/libworkman/drv_toshiba.c create mode 100644 ascd/libworkman/include/wm_cdda.h create mode 100644 ascd/libworkman/include/wm_cddb.h create mode 100644 ascd/libworkman/include/wm_cdinfo.h create mode 100644 ascd/libworkman/include/wm_cdrom.h create mode 100644 ascd/libworkman/include/wm_config.h create mode 100644 ascd/libworkman/include/wm_database.h create mode 100644 ascd/libworkman/include/wm_helpers.h create mode 100644 ascd/libworkman/include/wm_index.h create mode 100644 ascd/libworkman/include/wm_platform.h create mode 100644 ascd/libworkman/include/wm_scsi.h create mode 100644 ascd/libworkman/include/wm_struct.h create mode 100644 ascd/libworkman/include/wm_version.h create mode 100644 ascd/libworkman/include/workman.h create mode 100644 ascd/libworkman/include/workman_defs.h create mode 100644 ascd/libworkman/index.c create mode 100644 ascd/libworkman/plat_aix.c create mode 100644 ascd/libworkman/plat_bsd386.c create mode 100644 ascd/libworkman/plat_freebsd.c create mode 100644 ascd/libworkman/plat_hpux.c create mode 100644 ascd/libworkman/plat_irix.c create mode 100644 ascd/libworkman/plat_linux.c create mode 100644 ascd/libworkman/plat_linux_audio.c create mode 100644 ascd/libworkman/plat_linux_cdda.c create mode 100644 ascd/libworkman/plat_news.c create mode 100644 ascd/libworkman/plat_openbsd.c create mode 100644 ascd/libworkman/plat_osf1.c create mode 100644 ascd/libworkman/plat_sun.c create mode 100644 ascd/libworkman/plat_sun_audio.c create mode 100644 ascd/libworkman/plat_sun_cdda.c create mode 100644 ascd/libworkman/plat_svr4.c create mode 100644 ascd/libworkman/plat_template.c create mode 100644 ascd/libworkman/plat_ultrix.c create mode 100644 ascd/libworkman/scsi.c create mode 100644 ascd/libworkman/wm_helpers.c create mode 100644 ascd/version.h diff --git a/ascd/Imakefile b/ascd/Imakefile new file mode 100644 index 0000000..f3ae0fb --- /dev/null +++ b/ascd/Imakefile @@ -0,0 +1,7 @@ +#define IHaveSubdirs +#define PassCDebugFlags + +SUBDIRS = libworkman ascd + +MakeSubdirs($(SUBDIRS)) +DependSubdirs($(SUBDIRS)) diff --git a/ascd/README b/ascd/README new file mode 100644 index 0000000..f810fc8 --- /dev/null +++ b/ascd/README @@ -0,0 +1,50 @@ +This archive includes the original ascd 0.13.2 archive and the latest +LibWorkMan snapshot (990620). To compile the software: + + - run ./configure (*) + - make + - make install + - make install.man + +(*) configure will ask you a few question about ascd options. If you want +to use the default settings, run ./default instead of ./configure. + +Important note: +--------------- + +I changed two things in libworkman: + +1) I added #under OSS_SUPPORT in plat_linux.c in order to definitly +prevent libworkman to use the mixer. + +2) I added wm_cddb.h in installed files as it was missing in the +original tarball. + +Upgrading from ascd <= 0.12.X +----------------------------- + +Ascd 0.13 uses the last libworkman snapshot and is *not* compatible +with the previous ones. Do not forget to compile and install the +snapsnot included in this archive before trying to compile ascd 0.13. + +More infos: +----------- + +I strongly suggest to read the following texts: + + - in libworkman/ dir, read the README file and edit Config file. + - in ascd/ dir, read the README file. + +AScd: +----- + + * maintained by Denis Bourez <[email protected]> + * homepage: http://worldserver.oleane.com/rsn/ascd-en.html + +LibWorkMan: +----------- + + * maintained by Dirk Försterling <[email protected]> + * homepage: http://www.DeathsDoor.com/milliByte/WorkMan/ + +Denis Bourez, 99/9/5 diff --git a/ascd/ascd/Imakefile b/ascd/ascd/Imakefile new file mode 100644 index 0000000..b032d30 --- /dev/null +++ b/ascd/ascd/Imakefile @@ -0,0 +1,63 @@ +#include "config.h" +#include "dirs.h" + +NAME = ascd +BINDIR = CBINDIR +MANDIR = CMANDIR +THEMESDIR = CTHDIR +MOREFLAGS = -I/usr/local/include -I/usr/X11R6/include -I. +CCOPTIONS = $(MOREFLAGS) +SYS_LIBRARIES = $(XLIB) -lXpm + +LOCAL_LIBRARIES = -L../libworkman -lworkman -L/usr/local/lib + +SRCS_OPT1 = +SRCS_OPT2 = +SRCS_OPT3 = +OBJS_OPT1 = +OBJS_OPT2 = +OBJS_OPT3 = + +#ifdef WMK +SRCS_OPT1 = wings_main.c +OBJS_OPT1 = wings_main.o +#endif + +#ifdef MIXER +SRCS_OPT2 = mixer_handlers.c +OBJS_OPT2 = mixer_handlers.o +#endif + +SRCS = cdcontrol.c misc.c faktory.c handlers.c \ + $(SRCS_OPT1) \ + $(SRCS_OPT2) \ + $(SRCS_OPT3) \ + ascd.c + +OBJS = cdcontrol.o misc.o faktory.o handlers.o \ + $(OBJS_OPT1) \ + $(OBJS_OPT2) \ + $(OBJS_OPT3) \ + ascd.o + +#ifdef WMK +WM_LIB = -lm -lz -lpng -ljpeg -ltiff -lgif -lPropList -lwraster -lWINGs +#else +WM_LIB = +#endif + +AllTarget($(NAME)) +NormalProgramTarget($(NAME),$(OBJS),NullParameter,$(SYS_LIBRARIES) $(LOCAL_LIBRARIES) $(WM_LIB),NullParameter) +InstallProgram($(NAME),$(BINDIR)) +InstallManPage($(NAME),$(MANDIR)) +InstallMultiple(themes/themes-manual.ps.gz,$(THEMESDIR)) + +install:: + @echo "Installing themes..." + @cp themes/themes.tar $(THEMESDIR) + @cp themes/default.tar $(THEMESDIR) + @cd $(THEMESDIR) ; tar xf themes.tar + @cd $(THEMESDIR) ; tar xf default.tar + @cd $(THEMESDIR) ; rm themes.tar + @cd $(THEMESDIR) ; rm default.tar + @echo "Done." diff --git a/ascd/ascd/README b/ascd/ascd/README new file mode 100644 index 0000000..609ab50 --- /dev/null +++ b/ascd/ascd/README @@ -0,0 +1,138 @@ +======================================================================== + AScd 0.13 + CD player for Afterstep and Window Maker +======================================================================== + +MAINTAINER: + + Denis Bourez: + - email: [email protected] + - AScd home page: http://worldserver.oleane.com/rsn/ + +AScd ORIGINAL AUTHOR: + + Rob Malda: + - email: [email protected] + - WWW: http://CmdrTaco.net/ + +======================================================================== + +DESCRIPTION: + +AScd is a CD player that can be "docked" on the Afterstep Wharf or the +Window Maker dock. It uses libworkman to access CD drivers. + +======================================================================== + +HISTORY: + +AScd 0.1 -> 0.6 +--------------- + +The original code was taken by Rob Malda from asclock (Afterstep wharf +module, by Beat Christen <[email protected]>) and from workbone (CD +driver). + +AScd 0.7 -> 0.9 +--------------- + +I removed all the workbone code (Linux specific): ascd could then be compiled +on several UN*X flavors. I took a big part or the WorkMan (see at the end of +this text) src and merged it with the ascd 0.4 interface. The WorkMan code +was compiled as a library. + +AScd >= 0.10 +------------ + +WorkMan has splitted in 2 parts: the ui and the lib, named libworkman. So, +AScd uses this brand new lib, instead of the one which was included in the +AScd's archive. WorkMan's maintainer is Dirk Foersterling +([email protected]). + +AScd >= 0.12 +------------ + +AScd no longer includes the pixmaps in the source files. It is now possible +to change the pixmaps theme at run time. AScd now needs a directory where +to store the pixmaps. The default is /usr/local/share/AScd. + +AScd >= 0.13 +------------ + +The theme files are now fully customizable. All theme directories are +installed by default in /usr/local/share/AScd/Themes. + +======================================================================== + HOW TO COMPILE???? +======================================================================== + +You need to download, compile and install the WorkMan library. Please +use the 99/05/05 snapshot wish can be found on the AScd's home page or +on the primary ftp site for WorkMan: + +ftp://ftp.midwinter.com/WorkMan/snapshots/ + +======================================================================== + CONFIGURATION +======================================================================== + +Run ./configure + +This script will ask you a few questions about compilation-time +options, and generate a "config.h" file matching your choices. The +script will then run xmkmf to generate the Makefile. + +======================================================================== + COMPILATION/INSTALLATION +======================================================================== + +IMPORTANT NOTE: if you're upgrading from AScd 0.12, please remove +everything in the AScd default directory (/usr/local/share/AScd) as the +theme files are *not* compatible. + + make + + - If you want to try the program, just type + + ./ascd + + (try './ascd -help' to see the commandline options) + + If everything is ok, you can install the program and the + man page: + + make install + make install.man + + +* installation for AfterStep + -------------------------- + + Add a line in your ~/.steprc file: + + *Wharf - - Swallow "ascd" /usr/local/bin/ascd -device /dev/wcd0a & + + (customized to your system settings of course!) + +* installation for Window Maker + ----------------------------- + + Launch ascd with the "+w" option. + Dock ascd by dragging it to the dock. + +======================================================================== + CREDITS +======================================================================== + + Denis Bourez ([email protected]) http://worldserver.oleane.com/rsn/ + Rob Malda ([email protected]) http://CmdrTaco.net/ + + The 0.12 *digits.xpm were taken and modified from wmitime, an excellent + clock applet for Window Maker. Wmitime was written by Dave Clark + ([email protected]) More info at: http://www.neotokyo.org. I added the + lower case letters. + + The new 0.13 default font is fully 8bit compatible! It has been drawn + by Stefan Zeiger ([email protected]). +======================================================================== + Denis Bourez, 05/17/1999 diff --git a/ascd/ascd/ascd.c b/ascd/ascd/ascd.c new file mode 100644 index 0000000..394cf95 --- /dev/null +++ b/ascd/ascd/ascd.c @@ -0,0 +1,764 @@ +/* =========================================================================== + * AScd: the AfterStep and WindowMaker CD player + * ascd.c: main source + * =========================================================================== + * Copyright (c) 1999 Denis Bourez and Rob Malda. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Denis Bourez & Rob Malda + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY DENIS BOUREZ AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL DENIS BOUREZ, ROB MALDA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * =========================================================================== + */ + +#include <stdio.h> +#include <sys/param.h> +#include <sys/types.h> + +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include <X11/Xlib.h> +#include <X11/xpm.h> +#include <X11/extensions/shape.h> + +#include <errno.h> +#include <unistd.h> +#include <grp.h> +#include <pwd.h> + +#include "config.h" +#include "ascd.h" +#include "faktory_prot.h" +#include <workman/workman.h> + +XpmIcon alphaXPM; +XpmIcon ralphaXPM; +XpmIcon backXPM; +XpmIcon iconXPM; + +/* External functions */ + +extern time(); +extern open(); +extern cd_control(int); +extern cd_control_version(); + +/* the various switches: */ + +int debug = FALSE; /* verbose mode */ +unsigned int autoplay = 0; /* if set, play a disk when it is inserted -> see -a c.l. option */ +unsigned int autoprobe = TRUE; /* probe the drive, but do not play (autoplay) */ +unsigned int intro_mode = 0; /* play only the beginning of the tracks */ +unsigned int autorepeat = FALSE; /* zzzzzzzzzzzzz */ +unsigned int blind_mode = FALSE; /* no counter updates = no drive LED flashes */ +unsigned int loop_mode = 0; /* loop from loop_1 to loop_2 */ +unsigned int loop_start_track = 0; /* track of the beggining of the loop */ +unsigned int loop_end_track =0; /* track of the end of the loop */ +unsigned int loop_1 = 0; /* pos. 1 */ +unsigned int loop_2 = 0; /* pos. 2 */ +unsigned int show_db = FALSE; /* do we have to scroll song names? */ +unsigned int show_artist = FALSE; /* if show_db, do you append artist name? */ +unsigned int show_db_pos = 0; /* internal, used for the song name scrolling */ +unsigned int show_icon_db_pos = 0; /* internal, used for the song name scrolling */ +unsigned int force_upper = FALSE; /* all messages must be in uppercase */ +int ignore_avoid = FALSE; /* if set, we play *all* tracks, ignoring 'avoid' tags in database */ +unsigned int fast_track = 0; /* the fast track select method */ +unsigned int xflaunch = FALSE; /* do we launch xfascd when right click on the display? */ +unsigned int time_mode = 0; /* display mode for the counter. see function RedrawWindow() */ +unsigned int cue_time = 10; /* nbr of seconds for cue -> see -c command line option */ + +/* internals */ + +int lasttime = -1; +extern char *cd_device; /* the hardware device pointing to the CD ROM player */ +unsigned int datatrack = 0; /* is the current track a data track ? */ +unsigned int direct_access = 0; /* pos. to reach with the ruler */ +unsigned int direct_track = 0; /* if we want to go directly to another track */ +unsigned int wanna_play = FALSE; +unsigned int do_autorepeat = FALSE; +int wanted_track = 0; +unsigned int old_track = 0; +unsigned int anomalie = 0; /* cd_control return value */ + +/* let's talk about X... */ + +Display *Disp; +Window Root; +Window Iconwin; +Window Win; +char *Geometry = 0; +char device[128]=DEFAULTDEVICE; +char xv[128]; +int withdrawn=FALSE; +GC WinGC; +int CarrierOn = FALSE; +int screen; + +/* everything dealing with the hardware volume: */ + +int volume = MAX_VOL ; /* CD volume */ +int muted_volume = 0; /* CD volume in muted mode */ +int unmuted_volume = MAX_VOL; /* CD volume to restore when leaving muted mode */ +unsigned int muted = FALSE; /* is the CD muted? */ +unsigned int fade_out = FALSE; /* do you have to start a fade in/out? */ +unsigned int fade_step = 5; /* the fading speed */ +unsigned int fade_ok = 0; /* can't remember. sorry!!!! */ +extern int min_volume,max_volume; /* from LibWorkMan */ +int cur_balance = 10; /* ? */ + +char led_text[9]; /* the 'help' messages */ +unsigned int text_timeout = 1; /* messages timemout in seconds */ +long text_start = 0; /* timeout */ + +/* misc, have to add explanations here...! */ + +int selectors_timeout = 0; +int redraw = TRUE; +int has_clicked = FALSE; +int info_modified = 0; +int big_spaces = 0; +int slow_down = 0; + +/* ==================================================================== + Faktory: theme subsystem + ==================================================================== */ + +char theme[FAK_CMAX]; +char selected_theme[FAK_CMAX]; +unsigned int theme_select = 0; +unsigned int theme_select_nbr = 0; + +unsigned int panel = 1; /* current panel */ +unsigned int panels = 0; /* how many panels? */ +unsigned int but_max = 0; /* how many buttons? */ +unsigned int but_msg = 0; /* which one is the message zone? */ +unsigned int but_counter = 0; /* which one is the counter? */ +unsigned int but_tracknbr = 0; /* which one is the track number? */ +unsigned int but_db = 0; /* which one is the database zone? */ +unsigned int icon_msg = 0; +unsigned int icon_counter = 0; +unsigned int icon_tracknbr = 0; +unsigned int but_current = 0; /* last selected button */ + +unsigned int panel_stop = 0; /* autoswitch to this panel on stop/eject */ +unsigned int panel_play = 0; /* autoswitch to this panel on play */ + +char th_name[FAK_CMAX]; +char th_author[FAK_CMAX]; +char th_release[FAK_CMAX]; +char th_email[FAK_CMAX]; +char th_url[FAK_CMAX]; +char th_comment[FAK_CMAX]; +char th_alpha1[FAK_CMAX]; +char th_alpha2[FAK_CMAX]; +char th_background[FAK_CMAX]; +char th_icon_window[FAK_CMAX]; + +int th_no_minus = FALSE; +int th_no_icon_window = FALSE; + +struct fak_button thdata[FAK_BMAX]; + +/*****************************************************************************/ + +/* ==================================================================== + The optional modules globals: + ==================================================================== */ + +#ifdef WMK +#include "wings_global.c" +#endif + +#ifdef MIXER +#include "mixer_global.c" +#endif + +/*****************************************************************************/ + +void mouse_events(XEvent Event) +{ + int i; + int j; + int do_it = TRUE; + + for (i=1 ; i <= but_max ; i++) { + if ((thdata[i].panel == panel) || (thdata[i].panel == 0)) { + if (strlen(thdata[i].xpm_file) > 0) { + if ((Event.xbutton.y >= thdata[i].y) && (Event.xbutton.y <= thdata[i].y + thdata[i].xpm.attributes.height)) { + if ((Event.xbutton.x >= thdata[i].x) && (Event.xbutton.x <= thdata[i].x + thdata[i].xpm.attributes.width)) { + if (th_no_icon_window) { + /* Special Icon Mode: we must check in which window the user clicked */ + do_it = TRUE; + if (Event.xbutton.window == Iconwin) { + if (!thdata[i].icon) do_it = FALSE; + } else { + if (thdata[i].icon) do_it = FALSE; + } + } + /* no commands defined? We skip this one! */ + if (((thdata[i].left != 0) || (thdata[i].mid != 0) || (thdata[i].right != 0)) && (do_it)) { + if (debug) fprintf(stderr, "** User selected button %d with mouse button %d\n", i, Event.xbutton.button); + j = 0; + switch(Event.xbutton.button) { + case 1: j = thdata[i].left; break; + case 2: j = thdata[i].mid; break; + default: j = thdata[i].right; break; + } + but_current = i; + if (debug) fprintf(stderr, "-> command = %d\n", j); + if ((j >= 1) && (j <= 49)) fak_event_handle(j, Event); + else if ((j >= 50) && (j <= 99)) cd_event_handle(j, Event); +#ifdef MIXER + else if ((j >= 100) && (j <= 199)) mixer_event_handle(j, Event); +#endif + break; + } + } + } + } + } + } + +} + +/* ------------------------------------------------------------------------ + GUI control + ------------------------------------------------------------------------ */ + +Pixel get_color(char *ColorName) +{ + XColor Color; + XWindowAttributes Attributes; + + XGetWindowAttributes(Disp,Root,&Attributes); + Color.pixel = 0; + + if (!XParseColor (Disp, Attributes.colormap, ColorName, &Color)) + fprintf(stderr,"ascd: can't parse %s\n", ColorName); + else if(!XAllocColor (Disp, Attributes.colormap, &Color)) + fprintf(stderr,"ascd: can't allocate %s\n", ColorName); + + return Color.pixel; +} + +void create_window(int argc, char **argv) +{ + int i; + unsigned int borderwidth ; + char *display_name = NULL; + char *wname = "AScd"; + XGCValues gcv; + unsigned long gcm; + XTextProperty name; + Pixel back_pix, fore_pix; + int x_fd; + int d_depth; + int ScreenWidth, ScreenHeight; + XSizeHints SizeHints; + XWMHints WmHints; + XClassHint classHint; + + /* Open display */ + if (!(Disp = XOpenDisplay(display_name))) { + fprintf(stderr,"ascd: can't open display %s\n", XDisplayName(display_name)); + exit (1); + } + + screen = DefaultScreen(Disp); + +#ifdef WMK + scr = WMCreateScreen(Disp, DefaultScreen(Disp)); + create_big_window(scr); + create_db_window(scr); + create_about_window(scr); +#endif + + Root = RootWindow(Disp, screen); + d_depth = DefaultDepth(Disp, screen); + x_fd = XConnectionNumber(Disp); + ScreenHeight = DisplayHeight(Disp,screen); + ScreenWidth = DisplayWidth(Disp,screen); + + /* it's time to load the visual theme! */ + + if (fak_load_theme(theme, FALSE)) { + if (debug) fprintf(stderr, "-> back from fak_load_theme!\n"); + } else { + fprintf(stderr, "ascd: fatal error\n\n"); + fprintf(stderr, "The '%s' theme definition file can't be read.\n\n", theme); + exit(1); + } + + SizeHints.flags= USSize|USPosition; + SizeHints.x = 0; + SizeHints.y = 0; + back_pix = get_color("black"); + fore_pix = get_color("white"); + + XWMGeometry(Disp, screen, Geometry, NULL, (borderwidth = 1), &SizeHints, + &SizeHints.x,&SizeHints.y,&SizeHints.width, + &SizeHints.height, &i); + + SizeHints.width = backXPM.attributes.width; + SizeHints.height= backXPM.attributes.height; + + Win = XCreateSimpleWindow(Disp, Root, + SizeHints.x, + SizeHints.y, + SizeHints.width, + SizeHints.height, + borderwidth, + fore_pix, + back_pix); + + if (!th_no_icon_window) { + Iconwin = XCreateSimpleWindow(Disp,Win, + SizeHints.x,SizeHints.y, + SizeHints.width, + SizeHints.height, + borderwidth, + fore_pix, + back_pix); + } else { + Iconwin = XCreateSimpleWindow(Disp, Win, + SizeHints.x, + SizeHints.y, + iconXPM.attributes.width, + iconXPM.attributes.height, + borderwidth, + fore_pix, + back_pix); + } + + XSetWMNormalHints(Disp, Win, &SizeHints); + + classHint.res_name = "ascd" ; + classHint.res_class = "AScd"; + + XSetClassHint (Disp, Win, &classHint); + + XSelectInput(Disp, Win, (ExposureMask | ButtonPressMask | StructureNotifyMask)); + + XSelectInput(Disp, Iconwin, (ExposureMask | ButtonPressMask | + StructureNotifyMask)); + + if (XStringListToTextProperty(&wname, 1, &name) ==0) { + fprintf(stderr, "ascd: can't allocate window name\n"); + exit(-1); + } + + XSetWMName(Disp, Win, &name); + + /* Create WinGC */ + gcm = GCForeground|GCBackground|GCGraphicsExposures; + gcv.foreground = fore_pix; + gcv.background = back_pix; + gcv.graphics_exposures = False; + WinGC = XCreateGC(Disp, Root, gcm, &gcv); + + WmHints.initial_state = withdrawn ? WithdrawnState : NormalState ; + WmHints.icon_window = Iconwin; + WmHints.window_group = Win; + WmHints.flags = StateHint | IconWindowHint | IconPositionHint | WindowGroupHint; + WmHints.icon_x = SizeHints.x; + WmHints.icon_y = SizeHints.y; + XSetWMHints(Disp, Win, &WmHints); + XSetCommand(Disp, Win, argv, argc); + + XMapWindow(Disp, Win); + + + if (debug) fprintf(stderr, "-> calling fak_maskset() from create_window()...\n"); + fak_maskset(); + if (debug) fprintf(stderr, "-> calling fak_redraw() from create_window()...\n"); + fak_redraw(); + if (debug) fprintf(stderr, "-> Leaving create_window()\n"); +} + +void newtext(char *txt) { + strcpy(led_text, txt); + text_start = 0; +} + +void show_icon_db_f() { + /* scroll the song title */ + char txt[256]; + char txt2[256]; + char dsp[9]; + int track; + int red; + + if (!th_no_icon_window) return; + if (icon_msg == 0) return; + + if (theme_select == 0) { + + /* there is a message to display, we'll wait for the message to + time-out before handling the track title... */ + + if (strlen(led_text) > 0) return; + + /* if in fast track selector, we show the title of the track to reach */ + + if (fast_track > 0) { + track = fast_track - 1; + red = FALSE; + } else { + track = cur_track - 1; + red = TRUE; + } + + if (track > cur_ntracks - 1) return; + + if (cd->trk[track].songname != NULL) { + if (show_artist) { + if (cd->artist != NULL) { + sprintf(txt2, "%s: %s", cd->artist, cd->trk[track].songname); + } else { + strcpy(txt2, cd->trk[track].songname); + } + } else { + strcpy(txt2, cd->trk[track].songname); + } + fak_icon_text("", MSG_PANEL, 0, red); + if (strlen(txt2) > thdata[icon_msg].w) { + strcpy(txt, " "); + strcat(txt, txt2); + if (show_icon_db_pos > strlen(txt)) show_icon_db_pos = 0; + tes_sncpy(dsp, txt + show_icon_db_pos, thdata[icon_msg].w); + fak_icon_text(dsp, MSG_PANEL, 0, red); + show_icon_db_pos ++; + } else { + fak_icon_text(txt2, MSG_PANEL, 0, red); + } + } else { + strcpy(txt, ""); + } + } +} + +void show_db_f() { + /* scroll the song title */ + char txt[256]; + char txt2[256]; + char dsp[9]; + int track; + int red; + int where; + int longueur; + int i; + + if (but_db > 0) { + where = DB_PANEL; + longueur = thdata[but_db].w; + } else { + where = MSG_PANEL; + longueur = thdata[but_msg].w; + } + + if ((theme_select == 0) || (where == DB_PANEL)) { + + /* + there is a message to display, we'll wait for the message + to time-out before handling the track title... (but only if + there is no separate areas) + */ + + if ((strlen(led_text) > 0) && (where == MSG_PANEL)) return; + + /* if in fast track selector, we show the title of the track to reach */ + + if (fast_track > 0) { + track = fast_track - 1; + red = FALSE; + } else { + track = cur_track - 1; + red = TRUE; + } + + /* warning: avoid weird datas: */ + + if (track > cur_ntracks - 1) return; + if (track < 0) return; + + if (debug > 1) fprintf(stderr, "** Show DB TRACK = %d\n", track); + + if (cd->trk[track].songname != NULL) { + if (show_artist) { + if (cd->artist != NULL) { + sprintf(txt2, "%s: %s", cd->artist, cd->trk[track].songname); + } else { + strcpy(txt2, cd->trk[track].songname); + } + } else { + strcpy(txt2, cd->trk[track].songname); + } + fak_text("", where, 0, red); + if (strlen(txt2) > longueur) { + strcpy(txt, ""); + for (i = 0 ; i < longueur ; i++) strcat(txt, " "); + strcat(txt, txt2); + if (show_db_pos > strlen(txt)) show_db_pos = 0; + tes_sncpy(dsp, txt + show_db_pos, longueur); + fak_text(dsp, DB_PANEL, 0, red); + show_db_pos ++; + } else { + fak_text(txt2, DB_PANEL, 0, red); + } + } else { + strcpy(txt, ""); + } + } + + show_icon_db_f(); +} + +/* ------------------------------------------------------------------------ + Loooooooooooooooooping......... + ------------------------------------------------------------------------ */ + +void main_loop() +{ + unsigned int no_disk = 0; + long int dodo = RDTIME; + XEvent Event; + + while(1) { + + if (debug > 1) fprintf(stderr, "** [Main Loop] mode = %02d track = %02d \n", cur_cdmode, cur_track); + + if (cur_cdmode == WM_CDM_EJECTED) no_disk = 1; + + slow_down++; + + if (slow_down > 10) { + +#ifdef WMK + /* in 0.13, we no longer close WINGs windows if ejected + if ((cur_cdmode == WM_CDM_EJECTED) && (en_vue)) { + WMCloseWindow(win); + en_vue = FALSE; + } + */ +#endif + + if (no_disk == 1) { + cur_ntracks = 0; /* 0.13pr6: what a hack!!!! */ + + if (autoplay || autoprobe) { + dodo = RDTIME2; + wm_cd_status(); + } + + if (cur_cdmode != WM_CDM_EJECTED) no_disk = 0; + if ( (cur_cdmode == WM_CDM_STOPPED) && (autoplay) ) { + newtext("Autoplay"); + if (cd->volume > 0) { + volume=cd->volume; + cd_volume(volume, 10, max_volume); + } + cd_control(PLAY); + wm_cd_status(); + dodo = RDTIME; + fak_maskset(); + fak_redraw(); + } + } + + /* The Loop Mode : */ + if ( (cur_track == loop_end_track ) && (cur_pos_rel >= loop_2) && (loop_mode) ) { + cd_control(LOOP); + fak_redraw(); + } + + /* The Intro Scan Mode : */ + if ( (cur_pos_rel > cue_time) && (intro_mode) ) { + cd_control(INTRONEXT); + fak_redraw(); + } + } + + if ((slow_down == 1) || (slow_down == 6)) { + if ((show_db) && (cur_cdmode == WM_CDM_PLAYING)) show_db_f(); + } + + /* Check events */ + + while (XPending(Disp)) + { + + XNextEvent(Disp, &Event); + +#ifdef WMK + if (!WMHandleEvent(&Event)) { +#endif + switch(Event.type) { + + /* ---------------------- Redraw Window --------------------- */ + + case Expose: + if(Event.xexpose.count == 0) { + lasttime=01; + redraw = TRUE; + fak_redraw(); + } else { + if (debug > 1) fprintf(stderr, "** XEVent - expose, not handled, count = %d\n", Event.xexpose.count); + } + break; + + /* ----------------------- Mouse Click ---------------------- */ + + case ButtonPress: + + wm_cd_status(); + mouse_events(Event); + break; + + /* ------------------------ Destroy Window ------------------- */ + + case DestroyNotify: + XFreeGC(Disp, WinGC); + XDestroyWindow(Disp, Win); + XDestroyWindow(Disp, Iconwin); + XCloseDisplay(Disp); + exit(0); + break; + default: + if (debug > 1) fprintf(stderr, "** XEvent - unknown event type = %d\n", Event.type); + break; + } + +#ifdef WMK + } +#endif + XFlush(Disp); + } /* check event */ + + usleep(dodo); + + /* ----------------- now we have to redraw the screen: ---------------- */ + + if ((slow_down > 10) || (has_clicked)) { + fak_redraw(); + slow_down = 0; + has_clicked = FALSE; + } + } +} + +/* ------------------------------------------------------------------------ + So... let's go! + ------------------------------------------------------------------------ */ + +int main(int argc, char *argv[]) +{ + extern char *rcfile, *dbfiles; + + /*printf("AScd %s\n", VERSION);*/ + + /* CD device: */ + +#ifndef NO_D_DEVICE + cd_device = malloc(strlen(DEFAULTDEVICE) + 1); + strcpy(cd_device, DEFAULTDEVICE); +#endif + + strcpy(led_text, ""); + strcpy(theme, "default"); + strcpy(xv, "xv"); + + /* the WorkMan database. It's still not used in ascd, but we need this + to start the WorkMan code + */ + + rcfile = getenv("WORKMANRC"); + if (rcfile) rcfile = (char *)wm_strdup(rcfile); + dbfiles = getenv("WORKMANDB"); + if (dbfiles) dbfiles = (char *)wm_strdup(dbfiles); + split_workmandb(); + + load_rc_file(FALSE); + command_line_parse(argc, argv); + +#ifdef WMK + if (debug) fprintf(stderr, "** [WINGs] init app...\n"); + WMInitializeApplication("AScd", &argc, argv); +#endif + +#ifdef MIXER + mixer_ok = 1; + if (debug) fprintf(stderr, "** [mixer] checking mixer device...\n"); + if (! check_mixer()) { + fprintf(stderr, "ascd: can't initialize mixer device. Mixing support disabled.\n"); + mixer_ok = 0; + } +#endif + + if (debug) fprintf(stderr, "** creating main window...\n"); + create_window(argc, argv); + if (debug) fprintf(stderr, "** checking CD status...\n"); + wm_cd_status(); + + if (cur_cdmode != WM_CDM_EJECTED) { + if (debug) fprintf(stderr, "** reading CD initial volume...\n"); + volume = wm_cd_read_initial_volume(max_volume); + } else { + if (debug) fprintf(stderr, "** CD is ejected: volume ignored.\n"); + } + + if (debug) fprintf(stderr, "** checking autoplay...\n"); + if ((autoplay) && (cur_cdmode == WM_CDM_STOPPED)) { + if (cur_track < 1) { + cur_track = 1; + wm_cd_status(); + } + if (debug) fprintf(stderr, "-> autoplay: play command\n"); + cd_control(PLAY); + if (debug) fprintf(stderr, "-> autoplay: checking status\n"); + wm_cd_status(); + if (debug) fprintf(stderr, "-> autoplay: redrawing\n"); + fak_redraw(); + if (debug) fprintf(stderr, "-> autoplay: done\n"); + } else { + if (debug) fprintf(stderr, "-> no autoplay\n"); + } + + if (debug) fprintf(stderr, "** checking current CD volume in database\n"); + + if (((cur_cdmode == WM_CDM_PLAYING) || (cur_cdmode == WM_CDM_PAUSED)) && (cd->volume > 0)) { + volume=cd->volume; + cd_volume(volume, 10, max_volume); + } + + fak_maskset(); + + if (debug) fprintf(stderr, "** Init passed. Entering main loop.\n"); + redraw = TRUE; + main_loop(); + return 0; +} diff --git a/ascd/ascd/ascd.h b/ascd/ascd/ascd.h new file mode 100644 index 0000000..27d5920 --- /dev/null +++ b/ascd/ascd/ascd.h @@ -0,0 +1,186 @@ +#ifndef FALSE +# define FALSE 0 +#endif +#ifndef TRUE +# define TRUE 1 +#endif + +/* These #defines control the main loop timeouts */ +#define RDTIME 50000L +#define RDTIME2 75000L + +#define MAX_VOL 255 + + +/*#define ACCTABLE "éÉèÈêÊàÀâÂùÙûÛîÎôÔçÇ**"*/ +#define ACCTABLE "éeèeêeàaâaùuûuîiôoçc**" +#define UPACCTABLE "éEèEêEàAâAùUûUîIôOçC**" + + +#define COUNTER_PANEL 0 +#define MSG_PANEL 1 +#define TRACK_PANEL 2 +#define DB_PANEL 3 + +/* ---------------------- CD control ---------------------- */ + +#define PLAY 0 +#define PAUSE 1 +#define STOP 2 +#define UPTRACK 3 +#define DNTRACK 4 +#define CUE 5 +#define REV 6 +#define FIRST 7 +#define LAST 8 +#define LOOP 9 +#define DIRECTACCESS 10 +#define INTROSCAN 11 +#define INTRONEXT 12 +#define LOCACCESS 13 +#define DIRECTTRACK 14 +#define GLOBALACCESS 15 + +/* CLOSETRAY added 990417 */ +#define CLOSETRAY 16 + +/* new modes added in GMan experimentation. They're + also used in AScd >= 0.11 */ +#define STOPONLY 20 +#define EJECT 21 + +/* ------------------ FAKTORY defines: ------------------- */ + +#define FAK_BMAX 200 /* max buttons */ +#define FAK_CMAX 80 /* max lenght of infos strings */ + +/* screen elements types: */ + +#define FAK_PIXMAP 1 +#define FAK_COUNTER 2 +#define FAK_TRACKNBR 3 +#define FAK_MSG 4 +#define FAK_DB 5 +#define FAK_CD_BAR 10 +#define FAK_VCD_BAR 11 +#define FAK_ICD_BAR 12 +#define FAK_VOL_BAR 13 +#define FAK_VVOL_BAR 14 +#define FAK_IVOL_BAR 15 +#define FAK_MIXER_BAR 16 +#define FAK_VMIXER_BAR 17 +#define FAK_IMIXER_BAR 18 + +/* 0132 new pixmap sliders: */ + +#define FAK_CD_PIX 19 +#define FAK_VOL_PIX 20 + +#define FAK_VVOL_PIX 21 +#define FAK_VCD_PIX 22 + +/* general commands: the reserved range is 0 to 49 */ + +#define FAK_PANEL_SWITCH 1 +#define FAK_QUIT 2 +#define FAK_PANEL1 3 +#define FAK_PANEL2 4 +#define FAK_PANEL3 5 +#define FAK_PANEL4 6 +#define FAK_PANEL5 7 +#define FAK_WINGS 8 +#define FAK_COUNTER_MODE 9 +#define FAK_TSELECT 10 +#define FAK_TNEXT 11 +#define FAK_TPREVIOUS 12 +#define FAK_FTSELECT 13 +#define FAK_FTNEXT 14 +#define FAK_FTPREVIOUS 15 +#define FAK_SAVE 16 +#define FAK_LOAD 17 +#define FAK_QREF 20 + +/* general modes toggles: (not yet supported!!!) */ + +#define FAK_TOG_AUTOPLAY 30 +#define FAK_TOG_AUTOREPEAT 31 +#define FAK_TOG_SHOWDB 32 +#define FAK_TOG_SHOWARTIST 33 +#define FAK_TOG_UPPER 34 +#define FAK_TOG_ISKIPS 35 + +/* CD player commands: the reserved range is 50 to 99 */ + +#define FAK_CD_PLAY 50 +#define FAK_CD_PAUSE 51 +#define FAK_CD_STOP 52 +#define FAK_CD_EJECT 53 +#define FAK_CD_STOPEJECT 54 +#define FAK_CD_EJECTQUIT 55 + +#define FAK_CD_REW 60 +#define FAK_CD_FIRST 61 +#define FAK_CD_PREVIOUS 62 +#define FAK_CD_FWD 65 +#define FAK_CD_LAST 66 +#define FAK_CD_NEXT 67 +#define FAK_CD_DIRECT 68 + +#define FAK_CD_LSTART 70 +#define FAK_CD_LEND 71 +#define FAK_CD_LOOP 72 +#define FAK_CD_GOLSTART 73 +#define FAK_CD_GOLEND 74 +#define FAK_CD_LTRACK 75 +#define FAK_CD_LTOTRACK 76 +#define FAK_CD_LFROMTRACK 77 +#define FAK_CD_LCLEAR 78 + +#define FAK_CD_INTRO 80 +#define FAK_CD_FADE 81 + +/* these two ones are not yet supported: */ +#define FAK_CD_RANDOM 82 +#define FAK_CD_RMODE 83 + +#define FAK_CD_MUTE 90 +#define FAK_CD_VOLUME 91 + +/* Mixer commands: the reserved range is 100 to 199 */ + +#define FAK_MIXER_SET 100 +#define FAK_MIXER_50 101 +#define FAK_MIXER_75 102 +#define FAK_MIXER_100 103 +#define FAK_MIXER_0 104 +#define FAK_MIXER_LOAD 110 +#define FAK_MIXER_SAVE 111 + +/* -------------------------------------------------------------------- */ + +typedef struct _XpmIcon { + Pixmap pixmap; + Pixmap mask; + XpmAttributes attributes; +} XpmIcon; + +struct fak_button +{ + unsigned int type; + unsigned int panel; + char xpm_file[FAK_CMAX]; + char altxpm_file[FAK_CMAX]; + XpmIcon xpm; + XpmIcon altxpm; + unsigned int left; + unsigned int right; + unsigned int mid; + unsigned int x; + unsigned int y; + unsigned int w; + unsigned int h; + unsigned int arg; + int icon; + unsigned int ox; + unsigned int oy; +}; diff --git a/ascd/ascd/ascd.man b/ascd/ascd/ascd.man new file mode 100644 index 0000000..3aa208f --- /dev/null +++ b/ascd/ascd/ascd.man @@ -0,0 +1,267 @@ +.TH ascd 1 "17 may 1999" ascd +.UC + +.SH NAME + +\fBascd\fP \- An AfterStep and WindowMaker CD player + +.SH SYNOPSIS + +.B ascd +[ +.BI \-help +] +.br +.B ascd +[ +.BI \-version +] +.br +.B ascd +[ +.BI \-themes +] +.br +.br +.B ascd +[ +.BI \-geometry " geometry" +] +[ +.BI \+/-withdrawn +] +[ +.BI \-device " device" +] +[ +.BI \-mixer " device" +] +[ +.BI \+/-a +] +[ +.BI \+/-p +] +[ +.BI \+/-t +] +[ +.BI \+/-B +] +[ +.BI \-cue " time" +] +[ +.BI \-ti " time" +] +[ +.BI \-volume " volume" +] +[ +.BI \-mvolume " volume" +] +[ +.BI \-maxvolume " volume" +] +[ +.BI \-minvolume " volume" +] +[ +.BI \-fadestep " value" +] +[ +.BI \-theme " theme directory" +] +[ +.BI \+/-ia +] +[ +.BI \+/-up +] +[ +.BI \-showtitle +] +[ +.BI \-showartist +] +[ +.BI \-debug +] +[ +.BI \-save +] + +.SH DESCRIPTION + +\fBascd\fP is CD player written to play audio CD's integrated in +afterstep's Wharf module or WindowMaker's dock. +\fBascd\fP handles mounted volumes, and opens and closes the tray. + +.SH CONFIGURATION OPTIONS + +.B -help, -h + print a list of commandline options and exit. + +.B -version + + print the version number and exit. + + +.B -themes + + print the list of installed themes and exit. + +.B -geometry <geom> + specifies the geometry of \fBascd\fP. The Geometry + works like the standard X-Window geometry option. + +.B +/-withdrawn, +/-w + Turn on and off withdrawn mode. Use this option if + you use ascd with WindowMaker. + +.B -device <device> + specifies the CDROM device which should be used by + ascd. If "-DNO_DEFAULT_DEVICE" was set at + compilation time this option will have no effect. + +.B -mixer <device> + specifies the mixer device. Works only if mixer + support was enabled at compilation. + +.B -cue <seconds> + specifies the time -in seconds- for \fBintro scan\fP + and \fBcue\fP modes. + +.B -ti <seconds> + specifies the timeout for the help texts. The + default is 1 second. + +.B +/-autoplay, +/-a + Turn on or off \fBautoplay mode\fP. When this is + turned on, ascd always wait for a CD, and play it + automatically when inserted. + +.B +/-autoprobe, +/-p + Turn on or off \fBautoprobe mode\fP. This mode + checks for inserted CD. Default is on. + +.B +/-autorepeat, +/-t + autorepeat mode: when ascd reaches the end of the + last track, the CD is not stopped and first track + is played. + +.B +/-B + This option is no longer supported. + +.B -maxvolume <value> + Set the CD driver max volume. + +.B -minvolume <value> + Set the CD driver min volume. + +.B -volume <value> + Set the CD driver volume, between 0 and maxvolume. + Note that the -volume option in independant from + the mixer. It controls the drive, not the sound + card. + +.B -mvolume <value> + Set the CD driver volume for the mute mode. + +.B -fadestep <value> + Set the fade in/out speed. Default is 5. Greater + values accelerate the fade. + +.B -theme <theme dir> + Specify the visual theme to use. + +.B +/-ia + When this option is enabled, AScd ignore the skip + orders found in WorkMan database and read all the + tracks of the CD. + +.B +/-up + When enabled, all the message (and the track title + display) are forced in uppercase. + +.B -showtitle + Show the track title if found in WorkMan database. + If the message panel is not large enough, AScd + scrolls the title. + +.B -showartist + Add the artist name to the track title (-showtitle + option needed) + +.B -debug + AScd verbose mode + +.B -save + Save all the settings and exit. Usefull to 'make' a + new configuration file: set all the stuff you want + to set and put -save as last commandline option. + Example: + + ascd +w +a -device /dev/cd0c -save + + Then, you just call ascd without arguments, it will + use the settings you just saved. + +.SH FILES + +\fBascd\fP stores its settings in ~/.ascdrc. + +The theme files are usually stored in /usr/local/share/AScd. + +.SH INVOCATION +\fBascd\fP can be called from one of many ways. The most common invocation +is the command line: +.nf + + % ascd -device /dev/wcd0a +w +a & + +.fi +With Afterstep, another way to call \fBascd\fP is from the window +manager: +.nf + + *Wharf - - Swallow "ascd" ascd& + +.fi +This line, when placed in the \fI.steprc\fP file in the users home directory +will cause \fBascd\fP to use the shape extensions so that it will be a +button on the \fBWharf (1)\fP button bar under the \fBafterstep (1)\fP +window manager. + +.SH COPYRIGHTS + +Copyright 1999, Rob Malda and Denis Bourez. No guarantees or +warranties or anything are provided or implied in any way whatsoever. +Use this program at your own risk. Permission to use this program for +any purpose is given, as long as the copyright is kept intact. + +.SH AUTHORS + +Denis Bourez <[email protected]> + application since 0.7 +.br +Rob Malda <[email protected]> + application until 0.6 +.br +Dirk Foersterling <[email protected]> + WorkMan maintainer +.br +Steven Grimm <[email protected]> + WorkMan maintainer until 1.3 +.br +Erik O'Shaughnessy <[email protected]> + AIX platform device driver +.br +Michael Kurz <[email protected]> + changes and man page until 0.6 + +.SH MORE INFORMATION + +<URL:http://worldserver.oleane.com/rsn/> +.br +<URL:http://CmdrTaco.net/> diff --git a/ascd/ascd/cdcontrol.c b/ascd/ascd/cdcontrol.c new file mode 100644 index 0000000..b8daeff --- /dev/null +++ b/ascd/ascd/cdcontrol.c @@ -0,0 +1,362 @@ +/* =========================================================================== + * AScd: cdcontrol.c + * This is cd_control() function: accept orders from the ui player and send + * the appropriate commands to libworkman. + * + * Please do *not* modify this function as it is used without changes by + * several programs! + * =========================================================================== + * Copyright (c) 1999 Denis Bourez and Rob Malda. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Denis Bourez & Rob Malda + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY DENIS BOUREZ AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL DENIS BOUREZ, ROB MALDA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * =========================================================================== + */ + +#define CD_C_VERSION "1.4" +#include "ext.h" + +void +cd_control(int order) +{ + static int pos_changed = FALSE; + int currenttrack; + + anomalie = 0; + wanna_play = FALSE; + +#ifdef XFASCD + update_xpm = TRUE; +#endif + + if (cur_cdmode != WM_CDM_EJECTED) { + if (cd->trk[cur_track - 1].data) datatrack = 1; + else datatrack = 0; + } + + if ((cur_ntracks == 1) && (cd->trk[cur_track - 1].data)) { + + /* only one track, and it's a data track. We ignore + all commands to avoid some funny things. Only the EJECT + commands are allowed */ + + if ((order != STOP) && (order != EJECT)) { + anomalie = 1; + return; + } + } + + switch(order) { + + case PLAY: + loop_mode = 0; + intro_mode = 0; + if (! pos_changed) { + wm_cd_status(); + } else { + pos_changed = FALSE; + } + + if (cur_track < 1) cur_track = 1; + + /* don't play data tracks: */ + + if (! cd->trk[cur_track - 1].data) { + if (cur_cdmode != WM_CDM_EJECTED) { + if (cur_cdmode != WM_CDM_PAUSED) { + if (do_autorepeat) { + cur_track = 1; + do_autorepeat = FALSE; + } + + /* skip data and avoid tracks */ + while (((cd->trk[cur_track - 1].data) || ((cd->trk[cur_track - 1].avoid == 1) && (!ignore_avoid)) ) && (cur_track <= cur_ntracks)) cur_track++; + + if (cur_track > cur_ntracks) cur_track--; + if (!(cd->trk[cur_track - 1].data)) { + wanna_play = TRUE; + wm_cd_play(cur_track, 0, cur_ntracks + 1); + } + } + } + } + break; + + case PAUSE: + loop_mode = 0; + intro_mode = 0; + + if (pos_changed) { + cur_track = wanted_track; + wanna_play = TRUE; + wm_cd_play(cur_track, 0, cur_ntracks + 1); + cur_cdmode = WM_CDM_PLAYING; + } else { + wm_cd_pause(); + } + + if (cur_cdmode == WM_CDM_PLAYING) { + pos_changed = FALSE; + } + break; + + case STOP: /* Civilized handler: STOP and EJECT combined */ + loop_mode = 0; + intro_mode = 0; + if ((cur_cdmode == WM_CDM_PLAYING) || (cur_cdmode == WM_CDM_PAUSED)) { + cur_cdmode = WM_CDM_PAUSED; + wm_cd_stop(); + cur_cdmode = WM_CDM_STOPPED; + } else { + if (cur_cdmode != WM_CDM_EJECTED) { + currenttrack = 0; + wm_cd_eject(); + } + } + pos_changed = FALSE; + break; + + case STOPONLY: /* STOP, no more. Doesn't eject if CD is already stopped */ + loop_mode = 0; + intro_mode = 0; + if ((cur_cdmode == WM_CDM_PLAYING) || (cur_cdmode == WM_CDM_PAUSED)) { + cur_cdmode = WM_CDM_PAUSED; + wm_cd_stop(); + cur_cdmode = WM_CDM_STOPPED; + } + pos_changed = FALSE; + break; + + case EJECT: /* Troll handler: don't try to guess the current mode, just eject */ + if (cur_cdmode != WM_CDM_EJECTED) { + loop_mode = 0; + intro_mode = 0; + currenttrack = 0; + wm_cd_eject(); + } + break; + + case CLOSETRAY: /* let's try this */ + if (cur_cdmode == WM_CDM_EJECTED) { + /* commented, as current libworkman crashes when it receive this order */ + /*wm_cd_closetray();*/ + } + break; + + case UPTRACK: /* next track */ + loop_mode = 0; + intro_mode = 0; + if (pos_changed) cur_track = wanted_track; + + if (cur_track + 1 > cur_ntracks ) { + cur_track = 1; + } else { + cur_track ++; + } + if (cur_cdmode == WM_CDM_PAUSED) { + pos_changed = TRUE; + wanted_track = cur_track; + } + if (cur_cdmode == WM_CDM_PLAYING) { + wanna_play = TRUE; + wm_cd_play(cur_track, 0, cur_ntracks + 1); + } + break; + + case DNTRACK: /* previous track */ + loop_mode = 0; + intro_mode = 0; + if (pos_changed) cur_track = wanted_track; + + if (cur_cdmode == WM_CDM_PAUSED) { + if (cur_pos_rel < 2 || pos_changed) cur_track--; + cur_pos_rel = 0; + pos_changed = TRUE; + } else if (cur_cdmode == WM_CDM_PLAYING) { + if (cur_pos_rel < 2) cur_track--; + cur_pos_rel = 0; + } else { + cur_track--; + } + if (cur_track < 1) cur_track = cur_ntracks; + if (cur_cdmode == WM_CDM_PAUSED) { + pos_changed = TRUE; + wanted_track = cur_track; + } + if (cur_cdmode == WM_CDM_PLAYING) { + wanna_play = TRUE; + wm_cd_play(cur_track, 0, cur_ntracks + 1); + } + break; + + case DIRECTTRACK: /* direct access to a random track */ + loop_mode = 0; + intro_mode = 0; + + if (direct_track < 1) direct_track = 1; + + if (cur_cdmode == WM_CDM_PAUSED) pos_changed = TRUE; + cur_track = direct_track; + if (cur_track >= cur_ntracks + 1) cur_track = 1; + + /* if it's a data track, skip it */ + while ((cd->trk[cur_track - 1].data) && (cur_track <= cur_ntracks)) cur_track++; + + if (cur_cdmode == WM_CDM_PLAYING) { + wanna_play = TRUE; + wm_cd_play(cur_track, 0, cur_ntracks + 1); + } + break; + + case CUE: + loop_mode = 0; + intro_mode = 0; + if (cur_cdmode == WM_CDM_PLAYING) { + wanna_play = TRUE; + wm_cd_play(cur_track, cur_pos_rel + cue_time, cur_ntracks + 1); + } + break; + + case REV: + loop_mode = 0; + intro_mode = 0; + if ( (cur_cdmode == WM_CDM_PLAYING) && ( (cur_pos_rel - cue_time) >= 0 ) ) { + wanna_play = TRUE; + wm_cd_play(cur_track, cur_pos_rel - cue_time, cur_ntracks + 1); + } + break; + + case FIRST: + loop_mode = 0; + intro_mode = 0; + cur_track = 1; + if (cur_cdmode == WM_CDM_PLAYING) { + wanna_play = TRUE; + wm_cd_play(cur_track, 0, cur_ntracks + 1); + } else if (cur_cdmode == WM_CDM_PAUSED) { + pos_changed = TRUE; + wanted_track = cur_track; + } + break; + + case LAST: + loop_mode = 0; + intro_mode = 0; + cur_track = cur_ntracks; + if (cur_cdmode == WM_CDM_PLAYING) { + wanna_play = TRUE; + wm_cd_play(cur_track, 0, cur_ntracks + 1); + } else if (cur_cdmode == WM_CDM_PAUSED) { + pos_changed = TRUE; + wanted_track = cur_track; + } + break; + + case LOOP: + if ( (loop_2 > loop_1) || + (loop_end_track > loop_start_track) ) { + + if (loop_start_track == 0) loop_start_track = 1; + intro_mode = 0; + if ( ( loop_2 > loop_1 ) + || ( loop_start_track != loop_end_track ) ) + currenttrack = loop_start_track; + else { + loop_end_track = cur_ntracks; + currenttrack = loop_start_track = 1; + loop_1 = 0; + loop_2 = + thiscd.trk[ loop_end_track - 1 ].length - 1; + } + wm_cd_play(loop_start_track, loop_1, cur_ntracks + 1); + wanna_play = TRUE; + loop_mode = 1; + } else { + anomalie = 1; + } + break; + + case DIRECTACCESS: /* direct access to a random position of the current track */ + if (direct_access < 0) direct_access = 0; + loop_mode = 0; + intro_mode = 0; + wm_cd_status(); + wm_cd_play(cur_track, direct_access, cur_ntracks + 1); + wanna_play = TRUE; + break; + + case GLOBALACCESS: + if (direct_access < 0) direct_access = 0; + loop_mode = 0; + intro_mode = 0; + wm_cd_status(); + wm_cd_play(1, direct_access, cur_ntracks + 1); + wanna_play = TRUE; + break; + + case INTROSCAN: + if (! intro_mode) { + intro_mode = 1; + wm_cd_play(cur_track, 0, cur_ntracks + 1); + } else { + intro_mode = 0; + } + break; + + case INTRONEXT: + wm_cd_pause(); + currenttrack = cur_track; + currenttrack++; + if (cur_track +1 > cur_ntracks ) currenttrack = 1; + else currenttrack = cur_track + 1; + cur_pos_rel = 0; + wm_cd_play(currenttrack, 0, cur_ntracks + 1); + break; + + case LOCACCESS: + if ( (loop_1 > 0) && (loop_start_track > 0) ) { + intro_mode = 0; + loop_mode = 0; + currenttrack = loop_start_track; + wm_cd_stop(); + wm_cd_play(loop_start_track, loop_1, cur_ntracks + 1); + wanna_play = TRUE; + } else { + anomalie = 1; + } + break; + } +} + +char *cd_control_version(void) +{ + char *s = NULL; + + wm_strmcat(&s, CD_C_VERSION); + return s; +} diff --git a/ascd/ascd/config.h b/ascd/ascd/config.h new file mode 100644 index 0000000..67f2f44 --- /dev/null +++ b/ascd/ascd/config.h @@ -0,0 +1,6 @@ +#define PACKAGE "ascd" +#define DEFAULT_COLOR "#2FAFAF" +#define DEFAULT_BGCOLOR "#000000" +#define DEFAULTDEVICE "/dev/cdrom" +#define THDIR "/usr/local/share/AScd" +#define VERSION "0.13.2" diff --git a/ascd/ascd/configure b/ascd/ascd/configure new file mode 100755 index 0000000..fe032bc --- /dev/null +++ b/ascd/ascd/configure @@ -0,0 +1,203 @@ +#!/bin/sh + +LS=/bin/ls + +rm -f config.h +rm -f dirs.h + +echo "/* auto-generated config.h for AScd. */" > config.h +echo "/* do not edit. Run ./configure! */" >> config.h +echo "" >> config.h + +echo "/* internal defines, do not change! */" >> config.h +echo "#define PACKAGE \"ascd\"" >> config.h +cat version.h >> config.h +echo "" >> config.h +echo "/* user choices */" >> config.h +echo "#define DEFAULT_COLOR \"#2FAFAF\"" >> config.h +echo "#define DEFAULT_BGCOLOR \"#000000\"" >> config.h + +clear + +################################################################# + +echo "Welcome to the ascd configuration script. Please answer the" +echo "following questions before compilation." +echo +echo "--------------------" +echo "1. Default CD device" +echo "--------------------" +echo +echo "Please select a default CD device. This can be overriden at run" +echo "time with the -d option. Default is /dev/cdrom. You can also type" +echo "'NO' if you don't want to specify a default device (in case you're" +echo "using Volume Manager for example." +echo +printf "Default device or NO (or return to accept the default): " +read CDD +echo + +case $CDD in + "") echo "#define DEFAULTDEVICE \"/dev/cdrom\"" >> config.h + echo "** the default device will be /dev/cdrom" + ;; + NO) echo "#define DEFAULTDEVICE \"/dev/cdrom\"" >> config.h + echo "#define NO_D_DEVICE" >> config.h + echo "** no device option enabled" + ;; + *) echo "#define DEFAULTDEVICE \"$CDD\"" >> config.h + echo "** the default device will be $CDD" + ;; +esac + +################################################################# + +echo +echo "------------------" +echo "2. Binary location" +echo "------------------" +echo +echo "Where shall I install ascd program? Press return to accept the" +echo "default value (/usr/local/bin)." +echo +printf "Bin directory? " +read BINDIR +echo + +case $BINDIR in + "") echo "#define CBINDIR /usr/local/bin" > dirs.h + echo "** Ascd will be /usr/local/bin/ascd" + ;; + *) echo "#define CBINDIR $BINDIR" >> dirs.h + echo "** Ascd will be $BINDIR/ascd" + ;; +esac + +################################################################# + +echo +echo "---------------" +echo "3. Man location" +echo "---------------" +echo +echo "Where shall I install the man page? Press return to accept the" +echo "default value (/usr/local/man/man1)." +echo +printf "Man directory? " +read MANDIR +echo + +case $MANDIR in + "") echo "#define CMANDIR /usr/local/man/man1" >> dirs.h + echo "** Man page will be installed in /usr/local/man/man1." + ;; + *) echo "#define CMANDIR $MANDIR" >> dirs.h + echo "** Man page will be installed in $MANDIR." + ;; +esac + +################################################################# + +echo +echo "------------------" +echo "4. themes location" +echo "------------------" +echo +echo "Where shall I install the ascd themes files? Press return" +echo "to accept default value (/usr/local/share/AScd)." +echo +printf "Themes pixmaps directory? " +read THDIR +echo + +case $THDIR in + "") echo "#define THDIR \"/usr/local/share/AScd\"" >> config.h + echo "#define CTHDIR /usr/local/share/AScd" >> dirs.h + echo "** Theme dir: /usr/local/share/AScd." + ;; + *) echo "#define THDIR \"$THDIR\"" >> config.h + echo "#define CTHDIR $THDIR" >> dirs.h + echo "** Theme dir: $THDIR." + ;; +esac + + +################################################################# + +echo +echo "-------------------------" +echo "5. optional WINGs support" +echo "-------------------------" +echo +echo "If you're using Window Maker window manager, AScd can be compiled" +echo "with a configuration window using WINGs toolkit. Please note that" +echo "it's work in progress and you may have to change a few things in" +echo "ascd/Imakefile." +echo +echo "1) compile with WINGs support" +echo "2) no WINGs support (DEFAULT)" +echo +printf "Type 1 or 2: " +read WINGS +echo + +case $WINGS in + 1) echo "#define WMK" >> config.h + echo "** Compiling with WINGs support." + ;; + *) echo "** No WINGs support." + ;; +esac + +################################################################# + +echo +echo "-------------------------" +echo "6. optional mixer support" +echo "-------------------------" +echo +echo "This optional module adds mixing capabilities to AScd's themes." +echo "It is not supported on a lot of platforms, and you may have to" +echo "make some changes in the sources." +echo +echo "1) compile with mixer support" +echo "2) no mixer support (DEFAULT)" +echo +printf "Type 1 or 2: " +read WINGS +echo + +case $WINGS in + 1) echo "#define MIXER" >> config.h + echo "** Compiling with mixer support." + ;; + *) echo "** No mixer support." + ;; +esac + + +################################################################# + +echo +echo "-----------------------------" +echo "Automatic Makefile generation" +echo "-----------------------------" +echo +echo "Configuration is done. This script is now running xmkmf to" +echo "generate the Makefiles." +echo + +xmkmf -a + +echo +echo "---------" +echo "Finished!" +echo "---------" +echo +echo "You are now ready to compile AScd. type:" +echo +echo "make ... to compile the software" +echo "make install ... to install it" +echo "make install.man ... to install the man page" +echo +echo diff --git a/ascd/ascd/dirs.h b/ascd/ascd/dirs.h new file mode 100644 index 0000000..6f1c2f9 --- /dev/null +++ b/ascd/ascd/dirs.h @@ -0,0 +1,3 @@ +#define CBINDIR /usr/local/bin +#define CMANDIR /usr/local/man/man1 +#define CTHDIR /usr/local/share/AScd diff --git a/ascd/ascd/doc/CHANGES b/ascd/ascd/doc/CHANGES new file mode 100644 index 0000000..e114e93 --- /dev/null +++ b/ascd/ascd/doc/CHANGES @@ -0,0 +1,597 @@ +===================================================================== + AScd Change Log +===================================================================== + +------------- 09/05/99: + +- updated to latest libworkman snapshot (1.4.2, 99/06/20) +- Themes doc updated + +------------- 07/05/99: + +- added panel_stop and panel_play options to Theme file format. + +------------- 06/22/99: + +- [fix] the track number is no longer drawn on every panel. +- [fix] the mute function called the fade-in one! + +------------- 06/16/99: + +- updated to latest libworkman snapshot (1.4.2, 99/05/29) + +------------- 06/03/99: + +- [fix] small redraw bugs that could cause "flashy" screen updates +- [fix] fade in/out mode was broken in previous 0.13.X. + +------------- 05/25/99: + +- new pixmap sliders. Currently only direct_access and volume are + implemented, and only horizontal ones. + +------------- 05/24/99: + +- [fix] th_icon_window was not correctly set in fak_init_theme() + +------------- 05/19/99: + +- [fix] %s instead of %d in -debug theme summary +- added a -debug level + +------------- 05/19/99: 0.13.1 + +- code cleanup +- a few changes in Imakefile +- more messages in -debug mode +- a couple of fixes + +------------- 05/17/99: 0.13 *** beta stage *** + +- [fix] horizontal volume bar bug + +------------- 05/16/99: + +- added "quick ref" support with quick_ref command and a button + in the WINGs main window + +------------- 05/16/99: + +- added support for vertical sliders (CD direct access, volume and + mixer) via vprogress_bar, vvolume_bar and vmixer_bar. +- added support for inverted vertical sliders via iprogress_bar, + ivolume_bar and imixer_bar. +- mixer support (FreeBSD and Linux only. Please send me a mail if + you compile mixer support on other platforms.) +- added -mixer commandline option to specify the mixer device +- themes manual updated +- man page updated +- reorganized the WINGs src files + +------------- 05/13/99: + +- [fix] Track name was not shown for track #1 + +------------- 05/11/99: + +- better database support: CD default volume and CD autoplay are + handled (CD autoplay if for WorkMan, AScd ignores this parameter) +- better WINGs handling. The WINGs window is no longer closed by + the program when CD is ejected. +- [fix] WINGs windows crashed when CD was stopped +- [fix] big bug in database code that oftent crashed the + program at startup time (cd_curtrack = -1) +- [fix] bug in the song title scrolling (hardcoded length) + +------------- 05/11/99: 0.13pr6 + +- small updates in the man page (so outdated...) +- the theme manual (in PostScript) is now in the archive and + installed in /usr/local/share/AScd +- added alt pixmap support for eject command +- added database panel which can now be different from message one +- changed the themes loading mechanism: themes are now installed in + /usr/local/share/AScd/Themes. There's also a Default/ dir that can + contain pixmaps. If a pixmap is not found in the theme dir, AScd + also search in the Default dir. By default, this dir contains + digits.xpm and digits2.xpm. +- the theme selectors now use dirent instead of the '.dir' file +- added alt pixmap support for mute command + +------------- 05/10/99: 0.13pr5 + +- [fix] Less "floating point errors" should happen (I hope) +- [fix] the "°" in the counter was not drawn at the right place +- added icon keyword for a premilinary separate window/icon window + handling. See sample themes! +- added no_icon_window and icon_window prologues keywords to use + with big window themes (that cannot fit in the icon window) +- added volume slider handle with "volume" command +- [fix] bug in direct access bar handling + +------------- 05/10/99: 0.13pr4 + +- [fix] string truncate when its length > message panel width +- added comment keyword in Theme files + +------------- 05/09/99: 0.13pr3 + +New features and fixes since 0.12.1: + +- |fix] ./configure didn't work when using an alt pixmap path +- [fix] a STOP order is sent to drive at the CD end (!autorepeat) +- changed the global redraw time offsets +- support for 8bit fonts +- added a line if WINGs "about" window +- added autoprobe mode, activated by default (+/-p cl option) +- [fix] crash at CD end on Linux systems + +0.13 fixes and additions: + +- added a second debug level +- the redraw routine has been redesigned. Screen updates should be + less 'flashy' now...! + +------------- 05/08/99: 0.13pr2 + +New features and fixes since 0.12.1: + +- a few arrangements in the WINGs windows layout +- added a timeout for the theme and fast track selectors +- the fast track selector now scrolls the song titles +- support for loop clear and loop CD from start to current track +- mut be linked to 990505 LibWorkMan snapshot +- [fix] cd_control updated to 1.4 (added security in direct track) +- [fix] there was a bug in the cd_volume() calls in mute feature +- [fix] the messages now have priority on track title scrolling + +0.13 fixes and additions: + +- the buttons containing no command are no longuer parsed in + case of mouse events +- support for introscan +- support for fade in/out +- support for theme selector +- support for fast track selector +- support for mute +- WINGs module works again, but the visual theme selector crashes :( + +------------- 05/07/99: 0.13pr1 **** BACK TO ALPHA STAGE **** + +It's the biggest change since I begun working on ascd. Everything +that appears on the screen is now 100% configurable. Put the buttons +you want, where you want! + +- major rewrite of AScd. The source code has been splitted in + several smaller C files. +- [bug] the WINGs support shall *not* compile. I didn't have time + to update this code with the new features. +- added -debug cl option + +All of the CD player commands are not yet available. Check the next +pre-releases. + +------------- 05/06/99: + +- [fix] redraw problem at CD's end when autorepeat is on. +- [fix] redraw problem when selecting "PLAY" with a stopped CD. + +===================================================================== + +------------- 05/02/99: 0.12.1 + +- [fix WINGs] the themes popup didn't display the selected theme +- multiple-mask shape mode, to allow transparent background with + non rectangular buttons +- added a theme selector in the main window, activated by right + clicking on the message line +- [fix] when a theme is not found, ascd try to use the default one + instead of crashing. + +------------- 04/19/99: 0.12.0 RELEASE + +- added runtime visual themes management, with -theme cl option +- added a popup menu in configuration window to change the visual + theme *without* restarting the program +- [fix] there was a bug in the mouse routine that could eject CD + if the mouse click was outside the buttons +- [fix] a couple of typos in the WINGs windows. + +===================================================================== + +------------- 04/17/99: 0.11.2 + +- the volume is no longer set to the maximum at startup. AScd now + uses libworkman to read the initial volume. +- [fix] clicking on "play" when CD was ejected crashed the program. +- [fix] changed the WMCreateButton calls (didn't compile with rh 5.2) +- added an #undef OSS_SUPPORT in libworkman's plat_linux.c +- again, small changes in libworkman's Config + +------------- 04/15/99: "Le Chien" + +- added "+/-ia" CL option to ignore avoid tags in WorkMan's database +- added a button in WINGs window to control this parameter +- cdcontrol updated to 1.3 release, with a few cleanups +- added "+/-up" CL option to force uppercase messages +- added a button in WINGs window to control this parameter +- removed the "counter" popup, replaced by radio buttons + +------------- 04/14/99: 0.11.1 + +- added lowercase letters in (r)digits.xpm. I had to make a few + changes in the background pixmap to display the new letters. I + also added a conversion that remove accents. +- added 2 buttons in WINGs window to switch on/off song name and + artist scrolling in main window. +- added a field to edit the cd device in WINGs window +- [fix] with a songname <= 8 cars, the artist was not shown +- [fix?] autorepeat + +------------- 04/13/99: 0.11.0 RELEASE + +- song title (and CD artist) display with scrolling +- added -showtitle and -showartist command line options +- in ejected mode, the "eject" button is replaced by the "quit" one +- [fix] redraw problems with panel switching when CD is ejected +- [cdcontrol] fix with track n# after eject/load +- AScd now supports WorkMan's "avoid track" parameter. It has a better + handling than XfAScd: try it with Prince's "New Power Soul", when you + have to skip tracks 11 to 48... +- the database edition fixed (didn't save!!!) + +===================================================================== + +------------- 04/12/99: + +- added the "fast track selector" by clicking on the track number + when playing. Click play button to play selected track. +- cdcontrol updated to version 1.2, fixing a bug in autoplay. +- [fix] event handling should be smoother (especialy mouse clicks) + +------------- 04/10/99: "Human Being" + +Well... Once again, the screen layout was redesigned. And the mixer +is gone again! + +------------- 03/30/99: + +- added -xf command line option +- first attempt to add database editing. Ugly! + +------------- 03/19/99: (known as 0.10.1 RELEASE) + +- added misc/default script to generate a default config.h file + without asking any question. + +===================================================================== + +------------- 03/17/99: 0.10.0 RELEASE + +- switched to current LibWorkMan, which is no longuer included in + the archive. +- added a lot of settings in the WINGs window +- added a configure script +- updated cdcontrol to 1.0pre1 +- changed the pixmaps selection +- code cleanup +- [fix] foreground color saving was buggy! +- [fix] the muted volume was not saved +- [fix] crashed at startup with no cd (WINGs option only) + +===================================================================== + +------------- 03/16/99: 0.9.4 + +- added the optionnal WINGs window with WorkMan database display, + volume setting and autoplay/repeat switches. Still very + experimental! +- the timer handling was redesigned and should be more efficient. +- added fade in/out functions (right button on play) +- better volume control, the min/max/current volume can be loaded and + saved (~/.ascdrc) +- "-save" commandline option + +------------- 03/01/99: + +- "blind mode": in this mode, once a CD is started, AScd never + updates the counter and never check the drive status. +- changed the idle timeout to 500 ms (as in future 1.0.) + +------------- 02/26/99: 0.9.3 + +- [fix] autoplay fixes ("-1" track number at startup) (1.0 tree fix) +- [fix] pressing "eject" when the CD was already ejected crashed the + program (1.0 tree fix) + +------------- 02/10/99: 0.9.2 "p control" + +* All: + +- changed a few things in the Imakefiles. The "Config" file is now + replaced by config.h, wish should be a little more simplier +- [fix] a few typos in docs, man page and program. + +* Ascd: + +- added the -mdevice command line option to specify the mixer device. +- the mixer_device is now saved/loaded in ~/.ascdrc +- [fix] if LEDs colors where defined in ~/.ascd, the commandline + -bg and -fg didn't work -> "not enough color cells" +- [fix] when paused, selecting first or last track didn't work +- in mixer mode, the "track nbr" now displays the value for the + current mixer slider +- added an alternate pixmap set, "fusion", which can be set in the + config.h file. +- man page updated + +------------- 01/28/99: 0.9.1 "Late at night" + +* Ascd: + +- [fix] 0.9 didn't compile if NO_D_DEVICE was defined. Thanks to + Gus Hartmann who found the bug. +- [fix] 0.9 entered a crazy loop at CD end when autorepeat + was off. + +------------- 01/27/99: 0.9 RELEASE + +* Ascd: + +- code cleanup. + +------------- 01/26/99: PR5 "If U need a fix..." + +* Ascd: + +- [fix] ascd crashed on launch with no cd in tray (since 01/22) +- [fix] up/down buttons didn't work when paused (since 01/22) +- [fix] track nbr was not properly redrawn when using up/down + buttons when stopped (since 01/21) +- [fix] window was not properly redrawn when stopped (since 0.8-R) +- [fix] it was not possible to access the last track when stopped +- [fix] autorepeat works again (and fastly than in 0.8) +- the time display mode is now saved in ~/.ascdrc + +* Lib: + +- [BIG FIX] ascd now ejects CDs under FreeBSD. And there should be + no more device locking (it was necessary to quit ascd to eject + the CD and/or to mount a cdrom). Think this works with NetBSD too. + (bug found in 0.4+, 0.6+, 0.7 & 0.8) + +------------- 01/25/99: PR4 "If U need a mix..." + +* Ascd: + +- 100% new mixer section, with access to all the 17 mixing "sliders" + of OSS. +- the back and foreground colors may now be different for the CD + player and the mixer. +- all the mixer settings can be saved in a ~/.ascdrc file. This file + can be loaded at startup. +- Total rewrite of the command line parse function. Added several + new commands. See ascd -help for further details. + +* Lib: + +- [fix] I forgot to add the plat_openbsd.c in the Imakefile! +- added an option in the Config file to compile libworkman as a + shared lib. Tested with FreeBSD 2.2. + +------------- 01/22/99: PR3 "The Bug Factory" + +* Ascd: + +- Almost total rewrite of cd_control() function. The code should be + more reliable (I hope!). Some clean-ups two. +- no more uses old ascd variables (currenttrack, maxtracks, playing, + paused). Only the WorkMan ones (cur_track, cur_ntracks, cur_cdmode) + are used. +- on launch, if autoplay is disabled, the track nbr panel displays + how tracks are on the CD instead of "01". +- when the time panel displays the global remaining time of the CD, + the track nbr panel displays the number of remaining tracks. +- new alpha pixmaps. +- adjusted timing for checking inserted CD in autoplay mode. +- Middle click on the display switch the mute mode. Muted/Unmuted + volumes can be set with 'm' and 'v' command line parameters. +- [fix] in autoplay mode, launching ascd with an inserted audio CD in + drive didn't play this one automatically. + +------------- 01/21/99: PR2 + +* Ascd: + +- The mixer code was removed once again, due to the big XPM code + changes. +- XPM code redesigned: each button has its own XPM file. + +------------- 01/14/99: + +* Lib: + +- added O_RDONLY | O_NONBLOCK flagsto the cd_device opening function + in plat_linux.c and plat_freebsd.c. Thanks to Jens Axboe + ([email protected]) + +------------- 12/20/98: PR1 + +* Lib: + +- using WorkMan 1.3b (BETA) code instead of WorkMan 1.3a +- patched plat_freebsd.c to compile under FreeBSD 3.0. Thanks to + Stephane Marzloff ([email protected]) +- plat_openbsd.c added (WorkMan) + +===================================================================== + +------------- 12/18/98: 0.8 RELEASE + +* Lib: + +- added plat_aix.c in lib dir. Thanks to Erik O'Shaughnessy + ([email protected]) who wrote this driver and sent it to me. + +* Ascd: + +- Pixmaps are back to 48x48 pixel size +- Small fixes in buttons pixmaps ; new digits pixmaps +- right click on STOP button kills app. +- mixer section is back again! +- man page updated. + +------------- 12/07/98: + +* Lib: + +- srcs moved into subdirs, as PR1->PR4. +- Imakefiles are now working on Solaris. Makefile.solaris removed. +- WorkMan code is compiled as a library. + +* Ascd: + +- ascd now works with Solaris 7 i386 (gcc). + +------------- 11/29/98: + +- lib should now compile on Solaris 7 with gcc, using a specific + Makefile. +- added NO_D_DEVICE option for Solaris Volume Manager. + +------------- 12/01/97: + +* Lib: + +- [fix] it was impossible to play an inserted CD if autoplay mode was + disabled. + +------------- 09/25/97 -> 11/25/97: + +* Lib: + +- 'Config' file where all compilation options can be changed. + can be changed. +- now recognize WorkMan splited tracks. Try it with Prince's + Lovesexy's album !! + +* Ascd: + +- ascd now includes asmixer! +- added a "mixer screen" based on asmixer 0.5: clic on + time display with right button to switch to it. +- added -v, -1, -2, -3 command line parameters for the + mixer screen (same as asmixer). Removed the old -v option. +- setvol() and setvol_command() from asmixer src merged. +- added -t command line parameter: autorepeat +- stopped CD's display CD Length +- added "-b" command line option to specify the LEDs + background color. +- changed "-l" option to "-f" ([F]oreground color) +- when CD is ejected, track nbr shows "--". +- all the XPMs are now included in ascd.c, to allow to + define the bg color. I also fixed a few things in the + "old and quick made" ascd+ buttons. +- when global time is displayed, clicking on the + progress bar give access to the whole disk. +- added a "#define VERSION" in ascd.c +- added the new options (since 0.7) in the man page + +===================================================================== + +09/04/97: 0.7 release + +(denis) based on 0.4+, but with the WindowMaker specific code taken +from ascd 0.6 + +- added: "-v" command line option to specify the volume to set + when right-clicking on the time display. +- added: the time display now show a "-" before the remaining time(s) + and a "o" if it displays the CD ellapsed/remaining time. +- added: folding tags in the ascd.c for the jed editor +- added: "-l" command line option to change the LEDs colour +- added: clicking on the "DOWN" button restart the track ; another + click plays the previous track, like on a diskman (from Rob's + ascd 0.6) +- fixed: redisplay of the XPMs was buggy with 0.4+ (but worked with + afterstep... this fix is primary for WM) +- fixed: when autoplay is off, ascd doesn't scan the CD drive until + you click on a button. No more thousands of "cannot read audio disk" + in the FreeBSD logs :) (but there's still the problem with the "-a" + option) + +===================================================================== + +v0.6 +- Most important: WindowMaker Dock support with the -w flag! + +===================================================================== + +03/25/97: 0.4+ release + +(denis) this is a bug-fixes release. + +- fix: when first launch and if there was no CD in drive, a click on + the play button crashed the program. +- fixed a "floating point exception" when the track progress bar is + clicked on the very top. +- changed the "0.4" to "0.4+" for the "-h" option. + +===================================================================== + +03/23-24/97: 0.4+ release + +(denis) Based on Rob Malda's ascd 0.4. All the Workbone Code has been +removed. I use the Workman source code: ascd can be compiled on +several systems with several kinds of CD players, but tests have been +made only on FreeBSD 2.2.X (i386) and Linux 2.X (i386) with IDE +drives. Should work with SCSI drives too. (note: I removed the Workman +database code) + +- Imakefile has been updated for the Workman code. +- added: several new features (loops, introscan, progress bar...) + (see the README file) +- changed the XPM files for the new buttons and the progress bar. +- added: 4th digit for songs longer than 10 minutes. +- added: -a command line option. Activates the autoplay mode: ascd + automatically plays the CD when it is inserted. +- added: -c command line option which sets the value, in seconds, + for the intro scan mode and the cue/review function. +- changed the indentation in the source code with the auto- + indentation in jed. Sorry ;-) +- when a DATA CDROM is inserted, the display now show "--:--". +- a few fixes in ascd.c +- the default device is now /dev/cdrom. + +===================================================================== + +v0.4 - 1.15.96 +- Thanks olly again (Does this guy sleep?) We have a few + very nice bug fixes, and a nice new feature. +- ascd now stops correctly at the end of a CD. +- 'Next Track' used to go to far +- When ascd is paused, the LED now blinks! +- v0.4's secret code word is LLAMA BAIT + +===================================================================== + +v0.3 - 1.13.96 +- Thanks to [email protected], if a CD is playing when + ascd launches, it detects this and starts following it. +- Thanks to the 9,000 of you who e-mailed me fixes (thanks + guys, its nice to know people use this crap!) a Data CD + will not crash ascd. +- About 15 people sent in CHEEZ DOODLE's to me. Thanks. + The code word for v0.3 is ORANGE NIPPLE. + +===================================================================== + +v0.2 +- Fixed Certain Neat Error Messages +- I Think it should now get the last track from CD's. +- It shouldn't *lock* the CD Player from other devices + once you press the 'stop' button. This is especially + useful with ascdc. +- Now includes the correct README file. Thanks to the + Hundreds of you who caught that. It's nice to know + someone reads these files. diff --git a/ascd/ascd/doc/NEWS b/ascd/ascd/doc/NEWS new file mode 100644 index 0000000..8ae59a9 --- /dev/null +++ b/ascd/ascd/doc/NEWS @@ -0,0 +1,148 @@ +New features in ascd 0.13-beta: +=============================== + +(it is a big big big release, as I had to redesign or rewrite nearly +everything in the soft except the CD control functions. So, I'm +considering this code as beta stage one.) + +* Visual Themes: + + Visual themes are now 100% configurable. A theme consist in a description + file and various pixmaps. You can place any screen element anywhere in the + window and the icon. Available screen elements: + + - pixmaps + - counter + - track number + - message area + - database display area + - direct access horizontal, vertical and inverted vertical bars + - hardware volume horizontal, vertical and inverted vertical bars + - mixer control horizontal, vertical and inverted vertical bars + + For each element, you specify the *command* to use for the three mouse + buttons. AScd provides commands to control the general options, the CD + player and the mixer. + + AScd is no longer restricted to a "dockable" size. More: In -w mode, it's + now possible to have different screen layouts for the main window and the + icon one + +* Mixer support is back (FreeBSD and Linux are the only supported platforms) +* New documentation (PostScript) about making new themes +* LibWorkman updated to 990505, with early CDDB support (but it is *not* + yet supported by AScd) +* database support has been enhanced +* Quick Reference gif files support with an external viewer (xv by default) +* the font used in messages and database is now 8bit compatible, thanks to + Stefan Zeiger who made an excellent 8bit new font +* added -debug support, which could help when making/debugging new themes +* Speed improvment for a few features (fast track selector for instance) +* Better messages hierarchy +* CD control updated to 1.4 with a few fixes +* ascd.c has been splitted in several modules + +New features in ascd 0.12: +========================== + +* visual theme support!!! With the WINGs configuration + window, it is possible to switch between themes without + restarting ascd. + +New features in ascd 0.11.X: +============================ + +* another brand new look, with a lot of enhancements +* the mixer was removed once again +* better volume handling +* size set to 56x56 as it's ok with AfterStep's Wharf +* CD control updated to 1.2 with several fixes +* database editing (ALPHA!) in optional WINGs module +* AScd can show and scroll CD artist and track title +* a few commandline options to control this feature +* Added a commandline option to launch Xfascd +* The "avoid track" WorkMan's parameter is handled +* fast track access when playing by left/right clicking + on the track number +* a lot of enhancements in the configuration window +* fixes, as usual + +New features in ascd 0.10: +========================== + +* Using LibWorkMan 99/03/07 +* New configure script +* CD control updated to 1.0pre1 +* Enhanced WINGs module +* fixes + +0.9.4: +====== + +* optionnal config window using WINGs (WindowMaker) toolkit. +* fade in/out +* blind mode +* volume settings are now saved in ~/.ascdrc +* -save commandline option + +New features in ascd 0.9.1,2 and 3: +=================================== + +* 0.9.3 is a bug-fixes only +* fixes, fixes and fixes! +* alternate pixmaps set which be set in the config file +* mixer device can be changed with a command line option and + is stored in ~/.ascdrc +* in mixer mode, the track nbr displays the current mixer + setting. +* full mixer section added to man page. + +New features in ascd 0.9: +========================= + +* Lib code updated to WorkMan 1.3b with: + - plat_linux.c patched for glib systems + - plat_openbsd.c added +* Added an option in Config to compile libworkman as a + shared library (this works with ascd 0.8 too) +* plat_freebsd.c patched for FreeBSD 3.0-STABLE and hacked: + Eject now works and there're no more device locking! +* REWRITE of the pixmap routines: there are now one pixmap + per button, and several pixmaps are new. +* REWRITE of the CD control routine, which is now in a + separate src file. +* REWRITE from scratch of the mixer. +* REWRITE of the command line parsing function. Added a few + new options. See the man page or type ascd -help. +* ascd can save and load its settings in ~/.ascdrc file. It + is also possible to save the 17 mixer settings in this file. The + .ascdrc settings can be overidden with the commandline options. +* added drive volume control with a "mute" switch (values + can be changed with commandline options). Note that mute + function controls the CD Drive Volume, and is independant + from the mixer module. +* The LED colors can be changed for the mixer (Config file, + commandline & ~/.ascdrc) +* Autoplay and autorepeat modes redesigned. They should ok now. + (still a few little problems with autoplay detecting) +* a lot of fixes, including redraw problems found in 0.7 & 0.8. + +New features in ascd 0.8: +========================= + +* 'Config' file to specify compilation time default values +* Hardware code (WorkMan) updated and compiled as a lib +* Compilation on Solaris 7 i386 with gcc +* Compilation on AIX (Thanks to Erik O'Shaughnessy) +* Optional Mixer module (similar to asmixer) +* New pixmaps, size changed from 56x56 to 48x48 +* Text messages displaying mode changes +* LEDs colors can be changed from the commandline +* several fixes +* updated man page +* french translation for docs +* direct access to every part of the CD with a single click! +* right clic on "stop" button end the application + +========================================================================== +See CHANGES for details. Denis. diff --git a/ascd/ascd/ext.h b/ascd/ascd/ext.h new file mode 100644 index 0000000..cdf84a6 --- /dev/null +++ b/ascd/ascd/ext.h @@ -0,0 +1,164 @@ +#include <stdio.h> +#include <sys/param.h> +#include <sys/types.h> + +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include <X11/Xlib.h> +#include <X11/xpm.h> +#include <X11/extensions/shape.h> + +#include <errno.h> +#include <unistd.h> +#include <grp.h> +#include <pwd.h> +#include <dirent.h> + +#include "config.h" + +#include "ascd.h" +#include <workman/workman.h> + +#ifdef WMK +#include <WINGs.h> +#endif + +extern open(); +extern time(); + +/* added this for this lib: */ +extern unsigned int info_modified; + +extern int wanted_track; +extern unsigned int do_autorepeat; + +extern int anomalie; + +/* xfascd vars */ + +extern unsigned int wanna_play; + +#ifdef XFASCD +extern unsigned int update_xpm; +#endif +extern unsigned int blind_mode; +extern unsigned int loop_mode, intro_mode; +extern unsigned int datatrack, cue_time; +extern unsigned int loop_start_track, loop_end_track; +extern unsigned int loop_1; +extern unsigned int loop_2; +extern unsigned int direct_access, direct_track; +extern unsigned int ignore_avoid; + +extern int lasttime; + +extern Display *Disp; +extern Window Root; +extern Window Iconwin; +extern Window Win; +extern char *Geometry; +extern char device[]; +extern char xv[]; +extern int withdrawn; +extern GC WinGC; +extern int CarrierOn; + +extern char led_text[]; + +extern unsigned int text_timeout; +extern long text_start; +extern int redraw; +extern int mixer_changed; + +extern unsigned int autorepeat; +extern unsigned int autoplay; +extern unsigned int autoprobe; +extern unsigned int time_mode; +extern unsigned int global_mode; + +extern unsigned int volume; +extern unsigned int muted_volume; +extern unsigned int muted; +extern unsigned int unmuted_volume; + +extern unsigned int fade_ok; + +extern unsigned int old_track; + +extern int selectors_timeout; + +/* The WorkMan stuff: */ + +extern char *cd_device; + +/* from/for WorkMan database code: */ + +extern int mark_a; +extern int mark_b; +extern int cur_stopmode; +extern int cur_playnew; + +extern char theme[]; +extern char selected_theme[]; +extern unsigned int theme_select; +extern unsigned int theme_select_nbr; +extern int no_mask; +extern unsigned int show_db; +extern unsigned int show_artist; +extern unsigned int show_db_pos; +extern unsigned int show_icon_db_pos; +extern unsigned int force_upper; +extern unsigned int fast_track; +extern unsigned int xflaunch; +extern unsigned int fade_out; +extern unsigned int fade_step; + +extern int min_volume,max_volume; /* from LibWorkMan */ + +extern int debug; + +extern unsigned int panel; /* current panel */ +extern unsigned int panels; /* how many panels? */ +extern unsigned int but_max; /* how many buttons? */ +extern unsigned int but_msg; /* which one is the message zone? */ +extern unsigned int but_counter; /* which one is the counter? */ +extern unsigned int but_tracknbr; /* which one is the track number? */ +extern unsigned int but_db; +extern unsigned int icon_msg; +extern unsigned int icon_counter; +extern unsigned int icon_tracknbr; +extern unsigned int but_current; + +extern unsigned int panel_stop; +extern unsigned int panel_play; + +extern char th_name[]; +extern char th_author[]; +extern char th_release[]; +extern char th_email[]; +extern char th_url[]; +extern char th_comment[]; +extern char th_alpha1[]; +extern char th_alpha2[]; +extern char th_background[]; +extern char th_icon_window[]; + +extern int th_no_minus; +extern int th_no_icon_window; + +extern struct fak_button thdata[]; + +extern XpmIcon alphaXPM; +extern XpmIcon ralphaXPM; +extern XpmIcon backXPM; +extern XpmIcon iconXPM; + +#ifdef WMK +#include "wings_ext.h" +#endif + +#ifdef MIXER +#include "mixer_ext.h" +#endif diff --git a/ascd/ascd/faktory.c b/ascd/ascd/faktory.c new file mode 100644 index 0000000..79973e3 --- /dev/null +++ b/ascd/ascd/faktory.c @@ -0,0 +1,1777 @@ +/* =========================================================================== + * AScd: faktory.c + * The theme handling functions + * =========================================================================== + * Copyright (c) 1999 Denis Bourez and Rob Malda. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Denis Bourez & Rob Malda + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY DENIS BOUREZ AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL DENIS BOUREZ, ROB MALDA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * =========================================================================== + */ + +#include "ext.h" + +#define MAX_LINE_NAME 16 + +void tes_sncpy(char *out, char *in, int n) +{ + register int i; + for (i=0; i<=n-1; i++) out[i] = in[i]; + out[i] = '\0'; +} + +char *tes_xgets(char *chaine, int nb, FILE *fichier) +{ + char *r; + r = fgets(chaine, nb, fichier); + if (feof(fichier) == 0) chaine[strlen(chaine) - 1] = 0; + return r; +} + +int fak_parse_line(char *ligne, char *key, char *arguments) +{ + unsigned int pos = 0; + + if ((strlen(ligne) > 0) && (ligne[0] != '#')) { + while ((ligne[pos] != ' ') && (ligne[pos] != 9)) pos++; + tes_sncpy(key, ligne, pos); + while (((ligne[pos] == ' ') || (ligne[pos] == 9)) && (pos < strlen(ligne))) pos++; + if (pos < strlen(ligne)) strcpy(arguments, ligne + pos); + else strcpy(arguments, ""); + if (debug > 2) fprintf(stderr,"++ input: [%s]\n key: [%s]\n args: [%s]\n", ligne, key, arguments); + return TRUE; + } else { + strcpy(key, ""); + strcpy(arguments, ""); + if (debug > 2) fprintf(stderr,"++ input: [%s]\n key: [%s]\n args: [%s]\n", ligne, key, arguments); + return FALSE; + } +} + +int fak_use_alt(int i) +{ + int use_alt = 0; + switch (thdata[i].left) { + case FAK_CD_PLAY: + if (cur_cdmode == WM_CDM_PLAYING) use_alt = 1; + break; + case FAK_CD_PAUSE: + if (cur_cdmode == WM_CDM_PAUSED) use_alt = 1; + break; + case FAK_CD_STOP: + if (cur_cdmode == WM_CDM_STOPPED) use_alt = 1; + break; + case FAK_CD_EJECT: + if (cur_cdmode == WM_CDM_EJECTED) use_alt = 1; + break; + case FAK_CD_STOPEJECT: + if (cur_cdmode == WM_CDM_STOPPED) use_alt = 1; + break; + case FAK_CD_EJECTQUIT: + if ((cur_cdmode == WM_CDM_EJECTED) || (cur_cdmode == WM_CDM_STOPPED)) use_alt = 1; + break; + case FAK_CD_LOOP: + if (loop_mode) use_alt = 1; + break; + case FAK_CD_MUTE: + if (muted) use_alt = 1; + break; + case FAK_CD_VOLUME: + if (muted) use_alt = 1; + break; + case FAK_CD_INTRO: + if (intro_mode) use_alt = 1; + break; + case FAK_TOG_AUTOREPEAT: + if (autorepeat) use_alt = 1; + break; + case FAK_TOG_AUTOPLAY: + if (autoplay) use_alt = 1; + break; + case FAK_TOG_UPPER: + if (force_upper) use_alt = 1; + break; + case FAK_TOG_SHOWDB: + if (show_db) use_alt = 1; + break; + case FAK_TOG_SHOWARTIST: + if (show_artist) use_alt = 1; + break; + case FAK_TOG_ISKIPS: + if (ignore_avoid) use_alt = 1; + break; + case FAK_TSELECT: + case FAK_TNEXT: + case FAK_TPREVIOUS: + if (theme_select) use_alt = 1; + break; + default: + use_alt = 0; + break; + } + return use_alt; +} + +void fak_validate_pixmap(char *string, char *name) +{ + sprintf(string, "%s/Themes/%s/%s", THDIR, theme, name); + if (access(string, R_OK) != 0) { + sprintf(string, "%s/Default/%s", THDIR, name); + if (access(string, R_OK) != 0) { + fprintf(stderr, "\nascd: fatal error while loading theme '%s'\n", theme); + fprintf(stderr, " Pixmap file '%s' was not found.\n\n", name); + exit(1); + } + } +} + +void fak_init_theme(int upgrade) +{ + /* re-init all the buttons structure and globals */ + + int i; + + if (debug) fprintf(stderr, "** Init theme variables.\n Buttons...\n"); + for (i = 1; i <= but_max ; i++) { + + thdata[i].type = 0; + thdata[i].panel = 0; + thdata[i].left = 0; + thdata[i].mid = 0; + thdata[i].right = 0; + thdata[i].x = 0; + thdata[i].y = 0; + thdata[i].w = 0; + thdata[i].h = 0; + thdata[i].arg = 0; + thdata[i].icon = FALSE; + thdata[i].ox = 0; + thdata[i].oy = 0; + + if (strcmp(thdata[i].xpm_file, "") != 0) { + strcpy(thdata[i].xpm_file, ""); + /*if (upgrade) XpmFree(&thdata[i].xpm);*/ + } + if (strcmp(thdata[i].altxpm_file, "") != 0) { + strcpy(thdata[i].altxpm_file, ""); + /*if (upgrade) XpmFree(&thdata[i].altxpm);*/ + } + } + + if (debug) fprintf(stderr, " Strings & globals...\n"); + + strcpy(th_name, ""); + strcpy(th_author, ""); + strcpy(th_release, ""); + strcpy(th_email, ""); + strcpy(th_url, ""); + strcpy(th_comment, ""); + strcpy(th_alpha1, ""); + strcpy(th_alpha2, ""); + strcpy(th_background, ""); + strcpy(th_icon_window, ""); + + th_no_minus = FALSE; + + but_max = 0; + but_counter = 0; + but_msg = 0; + but_tracknbr = 0; + but_db = 0; + but_current = 0; + + icon_counter = 0; + icon_msg = 0; + icon_tracknbr = 0; + + panels = 0; + panel = 1; + + panel_stop = 0; + panel_play = 0; + + /* + if (upgrade) { + XpmFree(&alphaXPM); + XpmFree(&ralphaXPM); + XpmFree(&backXPM); + } + */ + if (debug) fprintf(stderr, "-> done.\n"); + +} + +int fak_load_theme(char *th, int upgrade) +{ + char txt[256]; + char key[256]; + char arguments[256]; + int button = FALSE; + unsigned int i; + XWindowAttributes Attributes; + int Ret; + FILE *in; +#ifdef MIXER + unsigned int j; +struct { + char name[MAX_LINE_NAME]; + int dev; +} mixernames[SOUND_MIXER_NRDEVICES] = { + { "volume", SOUND_MIXER_VOLUME }, + { "bass", SOUND_MIXER_BASS }, + { "treble", SOUND_MIXER_TREBLE }, + { "midi", SOUND_MIXER_SYNTH }, + { "pcm", SOUND_MIXER_PCM }, + { "speaker", SOUND_MIXER_SPEAKER }, + { "line", SOUND_MIXER_LINE }, + { "mic", SOUND_MIXER_MIC }, + { "cd", SOUND_MIXER_CD }, + { "imix", SOUND_MIXER_IMIX }, + { "altpcm", SOUND_MIXER_ALTPCM }, + { "reclev", SOUND_MIXER_RECLEV }, + { "igain", SOUND_MIXER_IGAIN }, + { "ogain", SOUND_MIXER_OGAIN }, + { "line1", SOUND_MIXER_LINE1 }, + { "line2", SOUND_MIXER_LINE2 }, + { "line3", SOUND_MIXER_LINE3 } +}; +#endif + + /* check if theme directory if ok: */ + + sprintf(txt, "%s/Themes/%s", THDIR, th); + + if (debug) fprintf(stderr, "** Theme to load: %s\n", txt); + + if (access(txt, X_OK | R_OK) != 0) { + sprintf(txt, "%s/Themes/default", THDIR); + if (access(txt, X_OK | R_OK) != 0) { + fprintf(stderr, "ascd: fatal error\n\n"); + fprintf(stderr, "The '%s' and 'default' themes folders are not \navailable in %s.\n\n", th, THDIR); + fprintf(stderr, "Please check the permissions and/or redo ascd installation.\n\n"); + exit(1); + } else { + strcpy(th, "default"); + } + } + + sprintf(txt, "%s/Themes/%s/Theme", THDIR, th); + + /* parse and load the file */ + + if (in = fopen(txt, "r")) { + + /* we first clear everything... */ + + fak_init_theme(upgrade); + + /* that's ok, let's go! */ + + if (debug) fprintf(stderr, "-> Parsing Theme file...\n"); + while (tes_xgets(txt, 255, in)) { + if (fak_parse_line(txt, key, arguments)) { + if (key[0] == '{') { + button = TRUE; + if (but_max < FAK_BMAX - 1) { + but_max ++; + strcpy(thdata[but_max].xpm_file, ""); + strcpy(thdata[but_max].altxpm_file, ""); + thdata[but_max].type = 0; + } else { + /* no more free buttons. We'll ignore the rest */ + button = FALSE; + } + } else if (key[0] == '}') { + button = FALSE; + + /* update the pointers to msg, counter and tracknbr: */ + + switch(thdata[but_max].type) { + case FAK_COUNTER: + if (thdata[but_max].icon) icon_counter = but_max; + else but_counter = but_max; + break; + case FAK_MSG: + if (thdata[but_max].icon) icon_msg = but_max; + else but_msg = but_max; + break; + case FAK_DB: + but_db = but_max; + break; + case FAK_TRACKNBR: + if (thdata[but_max].icon) icon_tracknbr = but_max; + else but_tracknbr = but_max; + break; + default: + break; + } + + if (debug > 1) { + fprintf(stderr, " Button %02d type: %02d commands: %02d %02d %02d arg: %02d\n", but_max, thdata[but_max].type, thdata[but_max].left, thdata[but_max].mid, thdata[but_max].right, thdata[but_max].arg); + fprintf(stderr, " Geometry: %02dx%02d on panel %02d\n", thdata[but_max].x, thdata[but_max].y, thdata[but_max].panel); + fprintf(stderr, " Pixmaps: [%s] and [%s]\n", thdata[but_max].xpm_file, thdata[but_max].altxpm_file); + } + } + + if (!button) { + if (strcmp(key, "name") == 0) strcpy(th_name, arguments); + else if (strcmp(key, "release") == 0) strcpy(th_release, arguments); + else if (strcmp(key, "author") == 0) strcpy(th_author, arguments); + else if (strcmp(key, "email") == 0) strcpy(th_email, arguments); + else if (strcmp(key, "url") == 0) strcpy(th_url, arguments); + else if (strcmp(key, "comment") == 0) strcpy(th_comment, arguments); + else if (strcmp(key, "alpha1") == 0) strcpy(th_alpha1, arguments); + else if (strcmp(key, "alpha2") == 0) strcpy(th_alpha2, arguments); + else if (strcmp(key, "background") == 0) strcpy(th_background, arguments); + else if (strcmp(key, "icon_background") == 0) strcpy(th_icon_window, arguments); + else if (strcmp(key, "panels") == 0) panels = atoi(arguments); + else if (strcmp(key, "panel_stop") == 0) panel_stop = atoi(arguments); + else if (strcmp(key, "panel_play") == 0) panel_play = atoi(arguments); + else if (strcmp(key, "no_minus") == 0) th_no_minus = TRUE; + } else { + + /* button definition: */ + + if (strcmp(key, "icon") == 0) thdata[but_max].icon = TRUE; + + if (strcmp(key, "type") == 0) { + if (strcmp(arguments, "pixmap") == 0) thdata[but_max].type = FAK_PIXMAP; + else if (strcmp(arguments, "counter") == 0) thdata[but_max].type = FAK_COUNTER; + else if (strcmp(arguments, "tracknumber") == 0) thdata[but_max].type = FAK_TRACKNBR; + else if (strcmp(arguments, "message") == 0) thdata[but_max].type = FAK_MSG; + else if (strcmp(arguments, "database") == 0) thdata[but_max].type = FAK_DB; + else if (strcmp(arguments, "progress_bar") == 0) thdata[but_max].type = FAK_CD_BAR; + else if (strcmp(arguments, "vprogress_bar") == 0) thdata[but_max].type = FAK_VCD_BAR; + else if (strcmp(arguments, "iprogress_bar") == 0) thdata[but_max].type = FAK_ICD_BAR; + else if (strcmp(arguments, "pixmap_bar") == 0) thdata[but_max].type = FAK_CD_PIX; + else if (strcmp(arguments, "vpixmap_bar") == 0) thdata[but_max].type = FAK_VCD_PIX; + else if (strcmp(arguments, "volume_bar") == 0) thdata[but_max].type = FAK_VOL_BAR; + else if (strcmp(arguments, "volume") == 0) thdata[but_max].type = FAK_VOL_BAR; + else if (strcmp(arguments, "pixmap_volume") == 0) thdata[but_max].type = FAK_VOL_PIX; + else if (strcmp(arguments, "vpixmap_volume") == 0) thdata[but_max].type = FAK_VVOL_PIX; + else if (strcmp(arguments, "vvolume") == 0) thdata[but_max].type = FAK_VVOL_BAR; + else if (strcmp(arguments, "vvolume_bar") == 0) thdata[but_max].type = FAK_VVOL_BAR; + else if (strcmp(arguments, "ivolume") == 0) thdata[but_max].type = FAK_IVOL_BAR; + else if (strcmp(arguments, "ivolume_bar") == 0) thdata[but_max].type = FAK_IVOL_BAR; + else if (strcmp(arguments, "mixer_bar") == 0) thdata[but_max].type = FAK_MIXER_BAR; + else if (strcmp(arguments, "mixer") == 0) thdata[but_max].type = FAK_MIXER_BAR; + else if (strcmp(arguments, "vmixer_bar") == 0) thdata[but_max].type = FAK_VMIXER_BAR; + else if (strcmp(arguments, "vmixer") == 0) thdata[but_max].type = FAK_VMIXER_BAR; + else if (strcmp(arguments, "imixer_bar") == 0) thdata[but_max].type = FAK_IMIXER_BAR; + else if (strcmp(arguments, "imixer") == 0) thdata[but_max].type = FAK_IMIXER_BAR; + } + + i = 0; + if ((strcmp(key, "left") == 0) || + (strcmp(key, "middle") ==0) || + (strcmp(key, "mid") ==0) || + (strcmp(key, "right") == 0)) { + + /* --------------- available commands: ---------------- */ + + /* basic orders */ + + if (strcmp(arguments, "panel_switch") == 0) i = FAK_PANEL_SWITCH; + else if (strcmp(arguments, "panel 1") == 0) i = FAK_PANEL1; + else if (strcmp(arguments, "panel 2") == 0) i = FAK_PANEL2; + else if (strcmp(arguments, "panel 3") == 0) i = FAK_PANEL3; + else if (strcmp(arguments, "panel 4") == 0) i = FAK_PANEL4; + else if (strcmp(arguments, "panel 5") == 0) i = FAK_PANEL5; + else if (strcmp(arguments, "counter_mode") == 0) i = FAK_COUNTER_MODE; + else if (strcmp(arguments, "quit") == 0) i = FAK_QUIT; + else if (strcmp(arguments, "wings_window") == 0) i = FAK_WINGS; + else if (strcmp(arguments, "theme_select") == 0) i = FAK_TSELECT; + else if (strcmp(arguments, "theme_next") == 0) i = FAK_TNEXT; + else if (strcmp(arguments, "theme_previous") == 0) i = FAK_TPREVIOUS; + else if (strcmp(arguments, "ftrack_select") == 0) i = FAK_FTSELECT; + else if (strcmp(arguments, "ftrack_next") == 0) i = FAK_FTNEXT; + else if (strcmp(arguments, "ftrack_previous") == 0) i = FAK_FTPREVIOUS; + else if (strcmp(arguments, "autoplay") == 0) i = FAK_TOG_AUTOPLAY; + else if (strcmp(arguments, "autorepeat") == 0) i = FAK_TOG_AUTOREPEAT; + else if (strcmp(arguments, "show_db") == 0) i = FAK_TOG_SHOWDB; + else if (strcmp(arguments, "show_artist") == 0) i = FAK_TOG_SHOWARTIST; + else if (strcmp(arguments, "uppercase") == 0) i = FAK_TOG_UPPER; + else if (strcmp(arguments, "ignore_skips") == 0) i = FAK_TOG_ISKIPS; + else if (strcmp(arguments, "save") == 0) i = FAK_SAVE; + else if (strcmp(arguments, "load") == 0) i = FAK_LOAD; + else if (strcmp(arguments, "quick_ref") == 0) i = FAK_QREF; + + /* CD player */ + + else if (strcmp(arguments, "play") == 0) i = FAK_CD_PLAY; + else if (strcmp(arguments, "pause") == 0) i = FAK_CD_PAUSE; + else if (strcmp(arguments, "stop") == 0) i = FAK_CD_STOP; + else if (strcmp(arguments, "eject") == 0) i = FAK_CD_EJECT; + else if (strcmp(arguments, "stop_eject") == 0) i = FAK_CD_STOPEJECT; + else if (strcmp(arguments, "eject_quit") == 0) i = FAK_CD_EJECTQUIT; + else if (strcmp(arguments, "rew") == 0) i = FAK_CD_REW; + else if (strcmp(arguments, "first_track") == 0) i = FAK_CD_FIRST; + else if (strcmp(arguments, "previous_track") == 0) i = FAK_CD_PREVIOUS; + else if (strcmp(arguments, "fwd") == 0) i = FAK_CD_FWD; + else if (strcmp(arguments, "last_track") == 0) i = FAK_CD_LAST; + else if (strcmp(arguments, "next_track") == 0) i = FAK_CD_NEXT; + else if (strcmp(arguments, "direct_access") == 0) i = FAK_CD_DIRECT; + else if (strcmp(arguments, "loop") == 0) i = FAK_CD_LOOP; + else if (strcmp(arguments, "loop_start") == 0) i = FAK_CD_LSTART; + else if (strcmp(arguments, "loop_end") == 0) i = FAK_CD_LEND; + else if (strcmp(arguments, "loop_go_start") == 0) i = FAK_CD_GOLSTART; + else if (strcmp(arguments, "loop_go_end") == 0) i = FAK_CD_GOLEND; + else if (strcmp(arguments, "loop_track") == 0) i = FAK_CD_LTRACK; + else if (strcmp(arguments, "loop_to_track") == 0) i = FAK_CD_LTOTRACK; + else if (strcmp(arguments, "loop_from_track") == 0) i = FAK_CD_LFROMTRACK; + else if (strcmp(arguments, "loop_clear") == 0) i = FAK_CD_LCLEAR; + else if (strcmp(arguments, "intro_scan") == 0) i = FAK_CD_INTRO; + else if (strcmp(arguments, "fade") == 0) i = FAK_CD_FADE; + else if (strcmp(arguments, "random_jump") == 0) i = FAK_CD_RANDOM; + else if (strcmp(arguments, "random_mode") == 0) i = FAK_CD_RMODE; + else if (strcmp(arguments, "mute") == 0) i = FAK_CD_MUTE; + else if (strcmp(arguments, "volume") == 0) i = FAK_CD_VOLUME; + + /* mixer */ + + else if (strcmp(arguments, "set") == 0) i = FAK_MIXER_SET; + else if (strcmp(arguments, "zero") == 0) i = FAK_MIXER_0; + else if (strcmp(arguments, "0") == 0) i = FAK_MIXER_0; + else if (strcmp(arguments, "50%") == 0) i = FAK_MIXER_50; + else if (strcmp(arguments, "50") == 0) i = FAK_MIXER_50; + else if (strcmp(arguments, "75%") == 0) i = FAK_MIXER_75; + else if (strcmp(arguments, "75") == 0) i = FAK_MIXER_75; + else if (strcmp(arguments, "100%") == 0) i = FAK_MIXER_100; + else if (strcmp(arguments, "100") == 0) i = FAK_MIXER_100; + + /* ------------- which mouse button? --------------- */ + + if (strcmp(key, "left") == 0) thdata[but_max].left = i; + else if (strcmp(key, "middle") == 0) thdata[but_max].mid = i; + else if (strcmp(key, "mid") == 0) thdata[but_max].mid = i; + else if (strcmp(key, "right") == 0) thdata[but_max].right = i; + } + + else if (strcmp(key, "panel") == 0) thdata[but_max].panel = atoi(arguments); + else if (strcmp(key, "x") == 0) thdata[but_max].x = atoi(arguments); + else if (strcmp(key, "y") == 0) thdata[but_max].y = atoi(arguments); + else if (strcmp(key, "w") == 0) thdata[but_max].w = atoi(arguments); + else if (strcmp(key, "h") == 0) thdata[but_max].h = atoi(arguments); + else if (strcmp(key, "arg") == 0) { + thdata[but_max].arg = atoi(arguments); +#ifdef MIXER + for(j = 0; j < SOUND_MIXER_NRDEVICES; j++) { + if(strcasecmp(mixernames[j].name, arguments) == 0) { + thdata[but_max].arg = mixernames[j].dev; + break; + } + } +#endif + } + else if (strcmp(key, "pixmap") == 0) strcpy(thdata[but_max].xpm_file, arguments); + else if (strcmp(key, "alt") == 0) strcpy(thdata[but_max].altxpm_file, arguments); + } + } + } + fclose(in); + + if (debug) { + fprintf(stderr, "-> Theme summary for '%s':\n", th_name); + fprintf(stderr, " Release: %s\n", th_release); + fprintf(stderr, " Author: %s (%s)\n", th_author, th_email); + fprintf(stderr, " URL: %s\n", th_url); + fprintf(stderr, " Comment: %s\n", th_comment); + fprintf(stderr, " Stats: %d panels, %d buttons\n", panels, but_max); + fprintf(stderr, " Display: counter: %d / msg: %d (%d cars) / db: %d / track: %d\n", but_counter, but_msg, thdata[but_msg].w, but_db, but_tracknbr); + if (th_no_icon_window) fprintf(stderr, " Using separate icon definition, background = %s\n", th_icon_window); + fprintf(stderr, "-> Loading basic pixmap files... "); + } + + XGetWindowAttributes(Disp, Root, &Attributes); + + /* the background pixmap: */ + + if (debug) fprintf(stderr, "background "); + + fak_validate_pixmap(txt, th_background); + Ret = XpmReadFileToPixmap(Disp, Root, txt, &backXPM.pixmap, + &backXPM.mask, &backXPM.attributes); + if ((Ret != XpmSuccess) && (debug)) fprintf(stderr, "*!* "); + + /* the fonts: */ + + if (debug) fprintf(stderr, "alpha 1 "); + + fak_validate_pixmap(txt, th_alpha1); + Ret = XpmReadFileToPixmap(Disp, Root, txt, &alphaXPM.pixmap, + &alphaXPM.mask, &alphaXPM.attributes); + if ((Ret != XpmSuccess) && (debug)) fprintf(stderr, "*!* "); + + if (debug) fprintf(stderr, "2 "); + + fak_validate_pixmap(txt, th_alpha2); + Ret = XpmReadFileToPixmap(Disp, Root, txt, &ralphaXPM.pixmap, + &ralphaXPM.mask, &ralphaXPM.attributes); + if ((Ret != XpmSuccess) && (debug)) fprintf(stderr, "*!* "); + + if (debug) fprintf(stderr, "\n-> Loading buttons pixmaps... "); + for (i = 1; i <= but_max; i++) { + if (strlen(thdata[i].xpm_file) > 0) { + if (debug > 1) fprintf(stderr, "%d ", i); + fak_validate_pixmap(txt, thdata[i].xpm_file); + thdata[i].xpm.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions); + Ret = XpmReadFileToPixmap(Disp, Root, txt, &thdata[i].xpm.pixmap, &thdata[i].xpm.mask, &thdata[i].xpm.attributes); + if ((Ret != XpmSuccess) && (debug > 1)) fprintf(stderr, "*!* "); + } + if (strlen(thdata[i].altxpm_file) > 0) { + if (debug > 1) fprintf(stderr, "a "); + fak_validate_pixmap(txt, thdata[i].altxpm_file); + thdata[i].xpm.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions); + Ret = XpmReadFileToPixmap(Disp, Root, txt, &thdata[i].altxpm.pixmap, &thdata[i].altxpm.mask, &thdata[i].altxpm.attributes); + if ((Ret != XpmSuccess) && (debug > 1)) fprintf(stderr, "*!* "); + } + } + + /* the icon */ + + if (strlen(th_icon_window) > 0) { + if (debug) fprintf(stderr, "\n-> Loading icon... "); + fak_validate_pixmap(txt, th_icon_window); + Ret = XpmReadFileToPixmap(Disp, Root, txt, &iconXPM.pixmap, + &iconXPM.mask, &iconXPM.attributes); + if ((Ret != XpmSuccess) && (debug)) fprintf(stderr, "*!* "); + } + + if (debug) fprintf(stderr, "\n-> Theme loaded.\n"); + return TRUE; + } else { + return FALSE; + } +} + +void fak_icon_text(char *string, unsigned int where, unsigned int col, unsigned int red) +{ + unsigned int x = 0; + unsigned int offset = 0; + unsigned hoffset = 0; + unsigned voffset = 0; + unsigned char txt[128]; + unsigned int j; + unsigned int char_max; + int out = FALSE; + + if (!th_no_icon_window) return; + + switch(where) { + case COUNTER_PANEL: + if (icon_counter == 0) out = TRUE; + else { + voffset = thdata[icon_counter].y; + hoffset = thdata[icon_counter].x; + } + break; + case MSG_PANEL: + if (icon_msg == 0) out = TRUE; + else { + voffset = thdata[icon_msg].y; + hoffset = thdata[icon_msg].x; + } + break; + default: + if (icon_tracknbr == 0) out = TRUE; + else { + voffset = thdata[icon_tracknbr].y; + hoffset = thdata[icon_tracknbr].x; + } + break; + } + + if (out) return; + + strcpy(txt, string); + + /* Deal with the string length: */ + + if ((strlen(txt) > thdata[icon_msg].w) && (where == MSG_PANEL)) { + txt[thdata[icon_msg].w] = 0; + + } + + if ((strcmp(txt, "") == 0) && (where == MSG_PANEL)) { + for (x = 1; x <= thdata[icon_msg].w; x++) { + strcat(txt, " "); + } + } + + + if (red) { + char_max = alphaXPM.attributes.width / 6; + } else { + char_max = ralphaXPM.attributes.width / 6; + } + + for(x = 0; x < strlen(txt); x++) { + + /* lookup in the accents table to remove them */ + if (! force_upper) { + + if (char_max <= 116) { /* don't convert if 8bit font */ + for (j = 0 ; j <= strlen(ACCTABLE) - 2 ; j = j + 2) { + if ( txt[x] == (unsigned char)ACCTABLE[j] ) { + txt[x] = ACCTABLE[j + 1]; + break; + } + } + } + + } else { + /* force upper case */ + for (j = 0 ; j <= strlen(UPACCTABLE) - 2 ; j = j + 2) { + if ( txt[x] == (unsigned char)UPACCTABLE[j] ) { + txt[x] = UPACCTABLE[j + 1]; + break; + } + } + txt[x] = toupper(txt[x]); + } + + if ((txt[x] >= 32) && (txt[x] <= char_max + 32)) offset = txt[x] - 32; + else offset = 31; + + if (red) { + XCopyArea(Disp, ralphaXPM.pixmap, Iconwin, WinGC, + 6 * offset, 0, 6, 9, + hoffset + (col * 6) + (x * 6), + voffset); + } else { + XCopyArea(Disp, alphaXPM.pixmap, Iconwin, WinGC, + 6 * offset, 0, 6, 9, + hoffset + (col * 6) + (x * 6), + voffset); + } + + } +} + +void fak_text(char *string, unsigned int where, unsigned int col, unsigned int red) { + + /* different behaviour than the old draw_text() found in AScd <= 0.12.... The + where parameter replaces the odl "row" one and should be: + + 0 = write to the counter zone + 1 = write to the message zone + 2 = write to the track number zone + */ + + unsigned int x = 0; + unsigned int offset = 0; + unsigned hoffset = 0; + unsigned voffset = 0; + unsigned char txt[128]; + unsigned int j; + unsigned int char_max; + int out = FALSE; + int do_icon = TRUE; + + if (where == DB_PANEL) { + do_icon = FALSE; + if (but_db == 0) where = MSG_PANEL; + } + + if (do_icon) fak_icon_text(string, where, col, red); + + switch(where) { + case COUNTER_PANEL: + if (but_counter == 0) out = TRUE; + else { + voffset = thdata[but_counter].y; + hoffset = thdata[but_counter].x; + } + break; + case MSG_PANEL: + if (but_msg == 0) out = TRUE; + else { + voffset = thdata[but_msg].y; + hoffset = thdata[but_msg].x; + } + break; + case DB_PANEL: + voffset = thdata[but_db].y; + hoffset = thdata[but_db].x; + break; + default: + if (but_tracknbr == 0) out = TRUE; + else { + voffset = thdata[but_tracknbr].y; + hoffset = thdata[but_tracknbr].x; + } + break; + } + + if (out) return; + + strcpy(txt, string); + + /* Deal with the string length: */ + + if ((strlen(txt) > thdata[but_msg].w) && (where == MSG_PANEL)) { + txt[thdata[but_msg].w] = 0; + + } + + if ((strlen(txt) > thdata[but_db].w) && (where == DB_PANEL)) { + txt[thdata[but_msg].w] = 0; + + } + + if ((strcmp(txt, "") == 0) && (where == MSG_PANEL)) { + for (x = 1; x <= thdata[but_msg].w; x++) { + strcat(txt, " "); + } + } + + if ((strcmp(txt, "") == 0) && (where == DB_PANEL)) { + for (x = 1; x <= thdata[but_db].w; x++) { + strcat(txt, " "); + } + } + + if (red) { + char_max = alphaXPM.attributes.width / 6; + } else { + char_max = ralphaXPM.attributes.width / 6; + } + + for(x = 0; x < strlen(txt); x++) { + + /* lookup in the accents table to remove them */ + if (! force_upper) { + + if (char_max <= 116) { /* don't convert if 8bit font */ + for (j = 0 ; j <= strlen(ACCTABLE) - 2 ; j = j + 2) { + if ( txt[x] == (unsigned char)ACCTABLE[j] ) { + txt[x] = ACCTABLE[j + 1]; + break; + } + } + } + + } else { + /* force upper case */ + for (j = 0 ; j <= strlen(UPACCTABLE) - 2 ; j = j + 2) { + if ( txt[x] == (unsigned char)UPACCTABLE[j] ) { + txt[x] = UPACCTABLE[j + 1]; + break; + } + } + txt[x] = toupper(txt[x]); + } + + if ((txt[x] >= 32) && (txt[x] <= char_max + 32)) offset = txt[x] - 32; + else offset = 31; + + if (red) { + XCopyArea(Disp, ralphaXPM.pixmap, Win, WinGC, + 6 * offset, 0, 6, 9, + hoffset + (col * 6) + (x * 6), + voffset); + + if (!th_no_icon_window) { + XCopyArea(Disp, ralphaXPM.pixmap, Iconwin, WinGC, + 6 * offset, 0, 6, 9, + hoffset + (col * 6) + (x * 6), + voffset); + } + } else { + XCopyArea(Disp, alphaXPM.pixmap, Win, WinGC, + 6 * offset, 0, 6, 9, + hoffset + (col * 6) + (x * 6), + voffset); + + if (!th_no_icon_window) { + XCopyArea(Disp, alphaXPM.pixmap, Iconwin, WinGC, + 6 * offset, 0, 6, 9, + hoffset + (col * 6) + (x * 6), + voffset); + } + } + + } +} + +int fak_flush_expose(Window w) +{ + XEvent dummy; + int i=0; + + while (XCheckTypedWindowEvent (Disp, w, Expose, &dummy))i++; + return i; +} + +void fak_minus(void) { + if (th_no_minus) return; + + if (time_mode == 1) { + if (!th_no_icon_window) { + if (but_counter == 0) return; + XCopyArea(Disp, alphaXPM.pixmap, Win, WinGC, + 6 * (45 - 32), 0, 6, 7, + thdata[but_counter].x, thdata[but_counter].y); + XCopyArea(Disp, alphaXPM.pixmap, Iconwin, WinGC, + 6 * (45 - 32), 0, 6, 7, + thdata[but_counter].x, thdata[but_counter].y); + } else { + if (but_counter > 0) { + XCopyArea(Disp, alphaXPM.pixmap, Win, WinGC, + 6 * (45 - 32), 0, 6, 7, + thdata[but_counter].x, thdata[but_counter].y); + } + if (icon_counter > 0) { + XCopyArea(Disp, alphaXPM.pixmap, Iconwin, WinGC, + 6 * (45 - 32), 0, 6, 7, + thdata[icon_counter].x, thdata[icon_counter].y); + } + } + } else if (time_mode == 3) { + if (!th_no_icon_window) { + if (but_counter == 0) return; + XCopyArea(Disp, alphaXPM.pixmap, Win, WinGC, + 588, 0, 4, 7, + thdata[but_counter].x, thdata[but_counter].y); + XCopyArea(Disp, alphaXPM.pixmap, Iconwin, WinGC, + 588, 0, 4, 7, + thdata[but_counter].x, thdata[but_counter].y); + } else { + if (but_counter > 0) { + XCopyArea(Disp, alphaXPM.pixmap, Win, WinGC, + 588, 0, 4, 7, + thdata[but_counter].x, thdata[but_counter].y); + } + if (icon_counter > 0) { + XCopyArea(Disp, alphaXPM.pixmap, Iconwin, WinGC, + 588, 0, 4, 7, + thdata[icon_counter].x, thdata[icon_counter].y); + } + } + } + + /* if displaying CD times, we display a little 'cd' */ + + if (time_mode >= 2) { + if (!th_no_icon_window) { + if (but_counter == 0) return; + XCopyArea(Disp, ralphaXPM.pixmap, Win, WinGC, + 582, 0, 4, 4, + thdata[but_counter].x, thdata[but_counter].y); + XCopyArea(Disp, ralphaXPM.pixmap, Iconwin, WinGC, + 582, 0, 4, 4, + thdata[but_counter].x, thdata[but_counter].y); + } else { + if (but_counter > 0) { + XCopyArea(Disp, ralphaXPM.pixmap, Win, WinGC, + 582, 0, 4, 4, + thdata[but_counter].x, thdata[but_counter].y); + } + if (icon_counter > 0) { + XCopyArea(Disp, ralphaXPM.pixmap, Iconwin, WinGC, + 582, 0, 4, 4, + thdata[icon_counter].x, thdata[icon_counter].y); + } + } + } + +} + +void fak_singlemask(int i) +{ + /* we first remove pixmap and alt from the mask */ + + if (strlen(thdata[i].xpm_file) > 0) { + if (!th_no_icon_window) { + XShapeCombineMask(Disp, Win, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].xpm.mask, ShapeSubtract); + XShapeCombineMask(Disp, Iconwin, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].xpm.mask, ShapeSubtract); + } else { + if (thdata[i].icon) { + XShapeCombineMask(Disp, Iconwin, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].xpm.mask, ShapeSubtract); + } else { + XShapeCombineMask(Disp, Win, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].xpm.mask, ShapeSubtract); + } + } + } + + if (strlen(thdata[i].altxpm_file) > 0) { + if (!th_no_icon_window) { + XShapeCombineMask(Disp, Win, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].altxpm.mask, ShapeSubtract); + XShapeCombineMask(Disp, Iconwin, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].altxpm.mask, ShapeSubtract); + } else { + if (thdata[i].icon) { + XShapeCombineMask(Disp, Iconwin, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].altxpm.mask, ShapeSubtract); + } else { + XShapeCombineMask(Disp, Win, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].altxpm.mask, ShapeSubtract); + } + } + } + + /* then we set of the two : */ + + if ((fak_use_alt(i)) && (strlen(thdata[i].altxpm_file) > 0)) { + if (!th_no_icon_window) { + XShapeCombineMask(Disp, Win, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].altxpm.mask, ShapeUnion); + XShapeCombineMask(Disp, Iconwin, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].altxpm.mask, ShapeUnion); + } else { + if (thdata[i].icon) { + XShapeCombineMask(Disp, Iconwin, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].altxpm.mask, ShapeUnion); + } else { + XShapeCombineMask(Disp, Win, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].altxpm.mask, ShapeUnion); + } + } + } else { + if (!th_no_icon_window) { + XShapeCombineMask(Disp, Win, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].xpm.mask, ShapeUnion); + XShapeCombineMask(Disp, Iconwin, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].xpm.mask, ShapeUnion); + } else { + if (thdata[i].icon) { + XShapeCombineMask(Disp, Iconwin, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].xpm.mask, ShapeUnion); + } else { + XShapeCombineMask(Disp, Win, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].xpm.mask, ShapeUnion); + } + } + } +} + +void fak_maskset() +{ + int i; + + if (debug) fprintf(stderr, "** Setting pixmaps mask\n"); + if (debug) fprintf(stderr, "-> background\n"); + + XShapeCombineMask(Disp, Win, ShapeBounding, 0, 0, + backXPM.mask, ShapeSet); + + if (!th_no_icon_window) { + XShapeCombineMask(Disp, Iconwin, ShapeBounding, 0, 0, + backXPM.mask, ShapeSet); + } else { + if (strlen(th_icon_window) > 0) { + XShapeCombineMask(Disp, Iconwin, ShapeBounding, 0, 0, + iconXPM.mask, ShapeSet); + } + } + if (debug) fprintf(stderr, "-> buttons "); + for (i=1; i <= but_max; i++) { + if ((thdata[i].panel == panel) || (thdata[i].panel == 0)) { + if (strlen(thdata[i].xpm_file) > 0) { + if (debug) fprintf(stderr, "%d ", i); + if ((fak_use_alt(i)) && (strlen(thdata[i].altxpm_file) > 0)) { + if (!th_no_icon_window) { + XShapeCombineMask(Disp, Win, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].altxpm.mask, ShapeUnion); + XShapeCombineMask(Disp, Iconwin, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].altxpm.mask, ShapeUnion); + } else { + if (thdata[i].icon) { + XShapeCombineMask(Disp, Iconwin, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].altxpm.mask, ShapeUnion); + } else { + XShapeCombineMask(Disp, Win, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].altxpm.mask, ShapeUnion); + } + } + } else { + if (!th_no_icon_window) { + XShapeCombineMask(Disp, Win, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].xpm.mask, ShapeUnion); + XShapeCombineMask(Disp, Iconwin, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].xpm.mask, ShapeUnion); + } else { + if (thdata[i].icon) { + XShapeCombineMask(Disp, Iconwin, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].xpm.mask, ShapeUnion); + } else { + XShapeCombineMask(Disp, Win, ShapeBounding, thdata[i].x, thdata[i].y, + thdata[i].xpm.mask, ShapeUnion); + } + } + } + } + } + } + if (debug) fprintf(stderr, "\n-> done.\n"); +} + +void fak_redraw() +{ + char txt[256]; + int i; + long int dodo = 300000; + char cdtime[6]; + int disp_time; + int use_alt = FALSE; + int offset; + DIR *dir_fd; + struct dirent *dir_pt; + unsigned int fx; + unsigned int fy; + unsigned int fw; + unsigned int fh; + + /* ============================================================== */ + /* ============================================================== */ + /* ============================================================== */ + /* First part: check different modes before doing the real redraw */ + /* ============================================================== */ + /* ============================================================== */ + /* ============================================================== */ + + if (debug) fprintf(stderr, "** Entering redraw routine\n"); + + if (!blind_mode) { + if ((cur_cdmode == WM_CDM_PLAYING) || (wanna_play)) wm_cd_status(); + } + + /* the panel auto-switch mode: */ + + if (((cur_cdmode == WM_CDM_STOPPED) || (cur_cdmode == WM_CDM_EJECTED)) && (panel_stop > 0)) panel = panel_stop; + if (((cur_cdmode == WM_CDM_PLAYING) || (cur_cdmode == WM_CDM_PAUSED)) && (panel_play > 0)) panel = panel_play; + + /* =================================================================== + Auto-Repeat mode: at then end of the CD, play again the first track + =================================================================== */ + + if ((cur_track >= cur_ntracks) && (cur_cdmode != WM_CDM_PAUSED) && (cur_cdmode != WM_CDM_PLAYING)) { + /*if (wanna_play) {*/ + if (autorepeat) { + newtext("A.Repeat"); + do_autorepeat = TRUE; + cd_control(PLAY); + } else { + cd_control(STOPONLY); /* added in 0.13 */ + if (debug) fprintf(stderr, "-> Stopping\n"); + cur_cdmode = WM_CDM_STOPPED; + wm_cd_status(); + cur_track = 0; + redraw = TRUE; + fak_maskset(); + } + } + + /* ======================================================= */ + /* the next track handling. We have to skip 'avoid' tracks */ + /* ======================================================= */ + + if ((cur_cdmode != WM_CDM_EJECTED) && (! ignore_avoid)) { + if (cur_ntracks > 1) { /* only if there are more than ONE track */ + if (cur_track != old_track) { + if (debug) fprintf(stderr, "-> Track change (current = %d, old = %d)\n", cur_track, old_track); + redraw = TRUE; + i = cur_track - 1; + while ((i <= cur_ntracks) && (cd->trk[i].avoid == 1)) { + i ++; + } + + /* go to the next valid track, only if it is now the + current track... */ + + if (i != cur_track - 1) { + direct_track = i + 1; + cd_control(DIRECTTRACK); + } + } + } + } + + /* ====================================================================== + Do we *really* need to update the counter? If the cur time is the same + as last time, we simply exit the redraw function + ====================================================================== */ + + if ((cur_track == old_track) && (lasttime == cur_pos_rel) && (!redraw) && (text_start == 0) && (strlen(led_text) == 0)) return; + + lasttime = cur_pos_rel; + + /* ===================================== */ + /* ===================================== */ + /* ===================================== */ + /* ===================================== */ + /* Dealing with the __real__ redraw now: */ + /* ===================================== */ + /* ===================================== */ + /* ===================================== */ + /* ===================================== */ + + if (debug) fprintf(stderr, "-> Redrawing\n"); + + fak_flush_expose(Win); + fak_flush_expose(Iconwin); + + + /* ================================ */ + /* draw the text if we have a query */ + /* ================================ */ + + /* but but but... If we see a "redraw" query, + we skip the text! It will be displayed next time + the function is called... 0.13 */ + + if (!redraw) { + if (strlen(led_text) > 0) { + if (text_start == 0) { + text_start = time(NULL); + fak_text("", MSG_PANEL, 0, FALSE); + fak_text(led_text, MSG_PANEL, 0, FALSE); + } + + if (time(NULL) - text_start > text_timeout) { + text_start = 0; + redraw = TRUE; + strcpy(led_text, ""); + fak_text("", MSG_PANEL, 0, FALSE); + } else { + if (!fade_out) return; + } + } + } + + /* ======================================= */ + /* if WINGs support, update the track list */ + /* ======================================= */ + +#ifdef WMK + if (cur_track != old_track) { + update_track(); + old_track = cur_track; + } +#endif + + /* =============================================== + We don't redraw everything if not required, + so whe ckech fo each element if redraw variable + is set + =============================================== */ + + /* the background: */ + + if (redraw) { + + if (debug) fprintf(stderr, "-> ** GLOBAL REDRAW NEEDED **\n"); + + XCopyArea(Disp,backXPM.pixmap,Win,WinGC,0,0,backXPM.attributes.width, backXPM.attributes.height,0,0); + if (!th_no_icon_window) { + XCopyArea(Disp,backXPM.pixmap,Iconwin,WinGC,0,0,backXPM.attributes.width, backXPM.attributes.height,0,0); + } else { + if (strlen(th_icon_window) > 0) { + XCopyArea(Disp,iconXPM.pixmap,Iconwin,WinGC,0,0,iconXPM.attributes.width, iconXPM.attributes.height,0,0); + } + } + } + + /* the buttons: */ + + for (i = 1; i <= but_max; i++) { + + use_alt = FALSE; + + if ((thdata[i].panel == panel) || (thdata[i].panel == 0)) { + if (thdata[i].type == FAK_VCD_BAR) { + if ( (cur_cdmode != WM_CDM_STOPPED) && (cur_cdmode != WM_CDM_EJECTED) ) { + if ((time_mode == 2) || (time_mode == 3)) { + if (cur_cdlen > 0) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, 0, 0, + thdata[i].xpm.attributes.width, + 0 + (int)((float)cur_pos_abs / (float)cur_cdlen * (float)thdata[i].xpm.attributes.height), + thdata[i].x, + thdata[i].y); + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, 0, 0, + thdata[i].xpm.attributes.width, + 0 + (int)((float)cur_pos_abs / (float)cur_cdlen * (float)thdata[i].xpm.attributes.height), + thdata[i].x, + thdata[i].y); + } + } + } else { + if (cur_tracklen > 0) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, 0, 0, + thdata[i].xpm.attributes.width, + 0 + (int)((float)cur_pos_rel / (float)cur_tracklen * (float)thdata[i].xpm.attributes.height), + thdata[i].x, + thdata[i].y); + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, 0, 0, + thdata[i].xpm.attributes.width, + 0 + (int)((float)cur_pos_rel / (float)cur_tracklen * (float)thdata[i].xpm.attributes.height), + thdata[i].x, + thdata[i].y); + } + } + } + } + + } else if (thdata[i].type == FAK_ICD_BAR) { + if ( (cur_cdmode != WM_CDM_STOPPED) && (cur_cdmode != WM_CDM_EJECTED) ) { + if ((time_mode == 2) || (time_mode == 3)) { + if (cur_cdlen > 0) { + offset = (int)((float)cur_pos_abs / (float)cur_cdlen * (float)thdata[i].xpm.attributes.height); + offset = thdata[i].xpm.attributes.height - offset; + + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, 0, 0, + thdata[i].xpm.attributes.width, + thdata[i].xpm.attributes.height - offset, + thdata[i].x, + thdata[i].y +offset); + + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, 0, 0, + thdata[i].xpm.attributes.width, + thdata[i].xpm.attributes.height - offset, + thdata[i].x, + thdata[i].y +offset); + } + } + } else { + if (cur_tracklen > 0) { + offset = (int)((float)cur_pos_rel / (float)cur_tracklen * (float)thdata[i].xpm.attributes.height); + offset = thdata[i].xpm.attributes.height - offset; + + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, 0, 0, + thdata[i].xpm.attributes.width, + thdata[i].xpm.attributes.height - offset, + thdata[i].x, + thdata[i].y +offset); + + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, 0, 0, + thdata[i].xpm.attributes.width, + thdata[i].xpm.attributes.height - offset, + thdata[i].x, + thdata[i].y +offset); + } + } + } + } + + } else if (thdata[i].type == FAK_CD_BAR) { + if ( (cur_cdmode != WM_CDM_STOPPED) && (cur_cdmode != WM_CDM_EJECTED) ) { + if ((time_mode == 2) || (time_mode == 3)) { + if (cur_cdlen > 0) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, 0, 0, + 0 + (int)((float)cur_pos_abs / (float)cur_cdlen * (float)thdata[i].xpm.attributes.width), + thdata[i].xpm.attributes.height, thdata[i].x, thdata[i].y); + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, 0, 0, + 0 + (int)((float)cur_pos_abs / (float)cur_cdlen * (float)thdata[i].xpm.attributes.width), + thdata[i].xpm.attributes.height, thdata[i].x, thdata[i].y); + } + } + } else { + if (cur_tracklen > 0) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, 0, 0, + 0 + (int)((float)cur_pos_rel / (float)cur_tracklen * (float)thdata[i].xpm.attributes.width), + thdata[i].xpm.attributes.height, thdata[i].x, thdata[i].y); + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, 0, 0, + 0 + (int)((float)cur_pos_rel / (float)cur_tracklen * (float)thdata[i].xpm.attributes.width), + thdata[i].xpm.attributes.height, thdata[i].x, thdata[i].y); + } + } + } + } + + } else if (thdata[i].type == FAK_VCD_PIX) { /************** vertical pixmap slider *****************/ + if ((cur_cdmode != WM_CDM_STOPPED) && (cur_cdmode != WM_CDM_EJECTED) && (cur_cdlen > 0)) { + + /* erase the old position : */ + + if (thdata[i].ox > 0) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, + 0, thdata[i].oy, + thdata[i].altxpm.attributes.width, thdata[i].altxpm.attributes.height, + thdata[i].x, + thdata[i].oy + thdata[i].y); + + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, + 0, thdata[i].oy, + thdata[i].altxpm.attributes.width, thdata[i].altxpm.attributes.height, + thdata[i].x, + thdata[i].oy + thdata[i].y); + } + } + + /* compute the new geometry: */ + + if ((time_mode == 2) || (time_mode == 3)) fx = (int)((float)cur_pos_abs / (float)cur_cdlen * (float)(thdata[i].xpm.attributes.width - thdata[i].altxpm.attributes.width)); + else fx = (int)((float)cur_pos_rel / (float)cur_tracklen * (float)(thdata[i].xpm.attributes.width - thdata[i].altxpm.attributes.width)); + + /* ugly, need to CHANGE THIS!!!!! */ + if (fx < 1) fx = 1; + + /* and show it: */ + + XCopyArea(Disp, thdata[i].altxpm.pixmap, Win, WinGC, + 0, 0, + thdata[i].altxpm.attributes.width, thdata[i].altxpm.attributes.height, + thdata[i].x, + thdata[i].y + fx); + + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].altxpm.pixmap, Iconwin, WinGC, + 0, 0, + thdata[i].altxpm.attributes.width, thdata[i].altxpm.attributes.height, + thdata[i].x, + thdata[i].y + fx); + } + + /* finaly, save it! */ + thdata[i].oy = fx; + } + } else if (thdata[i].type == FAK_CD_PIX) { /**************** horizontal pixmap slider *****************/ + if ((cur_cdmode != WM_CDM_STOPPED) && (cur_cdmode != WM_CDM_EJECTED) && (cur_cdlen > 0)) { + /* erase the old position : */ + + if (thdata[i].ox > 0) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, + thdata[i].ox, 0, + thdata[i].altxpm.attributes.width, thdata[i].altxpm.attributes.height, + thdata[i].ox + thdata[i].x, + thdata[i].y); + + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, + thdata[i].ox, 0, + thdata[i].altxpm.attributes.width, thdata[i].altxpm.attributes.height, + thdata[i].ox + thdata[i].x, + thdata[i].y); + } + } + + /* compute the new geometry: */ + + if ((time_mode == 2) || (time_mode == 3)) fx = (int)((float)cur_pos_abs / (float)cur_cdlen * (float)(thdata[i].xpm.attributes.height - thdata[i].altxpm.attributes.height)); + else fx = (int)((float)cur_pos_rel / (float)cur_tracklen * (float)(thdata[i].xpm.attributes.height - thdata[i].altxpm.attributes.height)); + + /* ugly, need to CHANGE THIS!!!!! */ + if (fx < 1) fx = 1; + + /* and show it: */ + + XCopyArea(Disp, thdata[i].altxpm.pixmap, Win, WinGC, + 0, 0, + thdata[i].altxpm.attributes.width, thdata[i].altxpm.attributes.height, + thdata[i].x + fx, + thdata[i].y); + + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].altxpm.pixmap, Iconwin, WinGC, + 0, 0, + thdata[i].altxpm.attributes.width, thdata[i].altxpm.attributes.height, + thdata[i].x + fx, + thdata[i].y); + } + + /* finaly, save it! */ + thdata[i].ox = fx; + } + } else if (thdata[i].type == FAK_VOL_BAR) { + if ( (cur_cdmode != WM_CDM_STOPPED) && (cur_cdmode != WM_CDM_EJECTED) ) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, 0, 0, + 0 + (int)((float)volume / (float)max_volume * (float)thdata[i].xpm.attributes.width), + thdata[i].xpm.attributes.height, thdata[i].x, thdata[i].y); + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, 0, 0, + 0 + (int)((float)volume / (float)max_volume * (float)thdata[i].xpm.attributes.width), + thdata[i].xpm.attributes.height, thdata[i].x, thdata[i].y); + } + } + } else if (thdata[i].type == FAK_VOL_PIX) { + if ((cur_cdmode != WM_CDM_STOPPED) && (cur_cdmode != WM_CDM_EJECTED)) { + + /* first erase the old position : */ + + if (thdata[i].ox > 0) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, + thdata[i].ox, 0, + thdata[i].altxpm.attributes.width, thdata[i].altxpm.attributes.height, + thdata[i].ox + thdata[i].x, + thdata[i].y); + + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, + thdata[i].ox, 0, + thdata[i].altxpm.attributes.width, thdata[i].altxpm.attributes.height, + thdata[i].ox + thdata[i].x, + thdata[i].y); + } + } + + /* compute the new geometry: */ + + fx = (int)((float)volume / (float)max_volume * (float)(thdata[i].xpm.attributes.width - thdata[i].altxpm.attributes.width)); + + /* ugly, need to CHANGE THIS!!!!! */ + if (fx < 1) fx = 1; + + /* and show it: */ + + XCopyArea(Disp, thdata[i].altxpm.pixmap, Win, WinGC, + 0, 0, + thdata[i].altxpm.attributes.width, thdata[i].altxpm.attributes.height, + thdata[i].x + fx, + thdata[i].y); + + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].altxpm.pixmap, Iconwin, WinGC, + 0, 0, + thdata[i].altxpm.attributes.width, thdata[i].altxpm.attributes.height, + thdata[i].x + fx, + thdata[i].y); + } + + /* finaly, save it! */ + thdata[i].ox = fx; + } + } else if (thdata[i].type == FAK_VVOL_BAR) { + if ( (cur_cdmode != WM_CDM_STOPPED) && (cur_cdmode != WM_CDM_EJECTED) ) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, 0, 0, + thdata[i].xpm.attributes.width, + 0 + (int)((float)volume / (float)max_volume * (float)thdata[i].xpm.attributes.height), + thdata[i].x, + thdata[i].y); + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, 0, 0, + thdata[i].xpm.attributes.width, + 0 + (int)((float)volume / (float)max_volume * (float)thdata[i].xpm.attributes.height), + thdata[i].x, + thdata[i].y); + } + } + } else if (thdata[i].type == FAK_IVOL_BAR) { + if ( (cur_cdmode != WM_CDM_STOPPED) && (cur_cdmode != WM_CDM_EJECTED) ) { + offset = (int)((float)volume / (float)max_volume * (float)thdata[i].xpm.attributes.height); + offset = thdata[i].xpm.attributes.height - offset; + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, 0, offset, + thdata[i].xpm.attributes.width, + thdata[i].xpm.attributes.height - offset, + thdata[i].x, + thdata[i].y +offset); + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, 0, offset, + thdata[i].xpm.attributes.width, + thdata[i].xpm.attributes.height - offset, + thdata[i].x, + thdata[i].y +offset); + } + } + } else if (thdata[i].type == FAK_MIXER_BAR) { +#ifdef MIXER + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, 0, 0, + 0 + (int)((float)getvol(thdata[i].arg) / 100.0 * (float)thdata[i].xpm.attributes.width), + thdata[i].xpm.attributes.height, thdata[i].x, thdata[i].y); + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, 0, 0, + 0 + (int)((float)getvol(thdata[i].arg) / 100.0 * (float)thdata[i].xpm.attributes.width), + thdata[i].xpm.attributes.height, thdata[i].x, thdata[i].y); + } +#endif + } else if (thdata[i].type == FAK_VMIXER_BAR) { +#ifdef MIXER + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, 0, 0, + thdata[i].xpm.attributes.width, + 0 + (int)((float)getvol(thdata[i].arg) / 100.0 * (float)thdata[i].xpm.attributes.height), + thdata[i].x, + thdata[i].y); + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, 0, 0, + thdata[i].xpm.attributes.width, + 0 + (int)((float)getvol(thdata[i].arg) / 100.0 * (float)thdata[i].xpm.attributes.height), + thdata[i].x, + thdata[i].y); + } +#endif + } else if (thdata[i].type == FAK_IMIXER_BAR) { +#ifdef MIXER + offset = (int)((float)getvol(thdata[i].arg) / 100.0 * (float)thdata[i].xpm.attributes.height); + offset = thdata[i].xpm.attributes.height - offset; + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, + 0, + offset, + thdata[i].xpm.attributes.width, + thdata[i].xpm.attributes.height - offset, + thdata[i].x, + thdata[i].y + offset); + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, 0, offset, + thdata[i].xpm.attributes.width, + thdata[i].xpm.attributes.height - offset, + thdata[i].x, + thdata[i].y + offset); + } +#endif + } else { + if (redraw) { + if (strlen(thdata[i].xpm_file) > 0) { + if (strlen(thdata[i].altxpm_file) == 0) use_alt = FALSE; + else use_alt = fak_use_alt(i); + + if (use_alt) { + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].altxpm.pixmap, Win, WinGC, 0, 0, thdata[i].xpm.attributes.width, thdata[i].altxpm.attributes.height, thdata[i].x, thdata[i].y); + XCopyArea(Disp, thdata[i].altxpm.pixmap, Iconwin, WinGC, 0, 0, thdata[i].xpm.attributes.width, thdata[i].altxpm.attributes.height, thdata[i].x, thdata[i].y); + } else { + if (thdata[i].icon) { + XCopyArea(Disp, thdata[i].altxpm.pixmap, Iconwin, WinGC, 0, 0, thdata[i].xpm.attributes.width, thdata[i].altxpm.attributes.height, thdata[i].x, thdata[i].y); + } else { + XCopyArea(Disp, thdata[i].altxpm.pixmap, Win, WinGC, 0, 0, thdata[i].xpm.attributes.width, thdata[i].altxpm.attributes.height, thdata[i].x, thdata[i].y); + } + } + } else { + if (!th_no_icon_window) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, 0, 0, thdata[i].xpm.attributes.width, thdata[i].xpm.attributes.height, thdata[i].x, thdata[i].y); + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, 0, 0, thdata[i].xpm.attributes.width, thdata[i].xpm.attributes.height, thdata[i].x, thdata[i].y); + } else { + if (thdata[i].icon) { + XCopyArea(Disp, thdata[i].xpm.pixmap, Iconwin, WinGC, 0, 0, thdata[i].xpm.attributes.width, thdata[i].xpm.attributes.height, thdata[i].x, thdata[i].y); + } else { + XCopyArea(Disp, thdata[i].xpm.pixmap, Win, WinGC, 0, 0, thdata[i].xpm.attributes.width, thdata[i].xpm.attributes.height, thdata[i].x, thdata[i].y); + } + } + } + } + } + } + } + } + + redraw = FALSE; + + if (debug) fprintf(stderr, "-> Display [last section]\n"); + + /* The Track number */ + + /* if ((cur_cdmode == WM_CDM_PLAYING) || (cur_cdmode == WM_CDM_PAUSED)) { */ + if (!th_no_minus) fak_text(" ", COUNTER_PANEL, 0, FALSE); + else fak_text(" ", COUNTER_PANEL, 0, FALSE); + + /* no CD? no counters! */ + + if (cur_cdmode == WM_CDM_EJECTED) { + if (!th_no_minus) fak_text("AScd", COUNTER_PANEL, 1, FALSE); + else fak_text("AScd", COUNTER_PANEL, 0, FALSE); + fak_text("NO CD", MSG_PANEL, 1, TRUE); + if (debug) fprintf(stderr, "-> No CD. Leaving redraw routine\n"); + return; + } + + if (debug) fprintf(stderr, "-> Dealing with theme/fast_track selectors\n"); + + /* ================== */ + /* The theme selector */ + /* ================== */ + + if (theme_select > 0) { + /* 5 seconds timeout */ + if (time(NULL) - selectors_timeout <= 5) { + fak_text("THEME", COUNTER_PANEL, 0, TRUE); + sprintf(cdtime, "%02d", theme_select); + fak_text(cdtime, TRACK_PANEL, 0, FALSE); + + i = 0; + + sprintf(txt, "%s/Themes", THDIR); + if ((dir_fd = opendir(txt)) != NULL) { + i = 0; + while((dir_pt = readdir(dir_fd)) != NULL) { + if (dir_pt->d_name[0] != '.') { + i++; + if (i == theme_select) { + strcpy(selected_theme, dir_pt->d_name); + fak_text(dir_pt->d_name, MSG_PANEL, 0, FALSE); + } + } + } + closedir(dir_fd); + } + return; + } else { + theme_select = 0; + redraw = TRUE; + } + } + + /* =================== */ + /* Fast Track selector */ + /* =================== */ + + if (fast_track == 0) { + if (cur_track > 0) { + /* 15 seconds timeout */ + if (time_mode != 15) { + sprintf(cdtime, "%02d", cur_track); + } else { + sprintf(cdtime, "%02d", cur_ntracks - cur_track); + } + } else { + sprintf(cdtime, "%02d", cur_ntracks); + } + if ((thdata[but_tracknbr].panel == panel) || (thdata[but_tracknbr].panel == 0)) + fak_text(cdtime, TRACK_PANEL, 0, TRUE); + } else { + if (time(NULL) - selectors_timeout <= 3) { + sprintf(cdtime, "%02d", fast_track); + fak_text("TRACK", COUNTER_PANEL, 0, TRUE); + if ((thdata[but_tracknbr].panel == panel) || (thdata[but_tracknbr].panel == 0)) + fak_text(cdtime, TRACK_PANEL, 0, FALSE); + return; + } else { + selectors_timeout = 0; + fast_track = 0; + redraw = TRUE; + } + } + + if (debug) fprintf(stderr, "-> Counter\n"); + + /* the counter: */ + + switch(time_mode) { + case 0: + fak_minus(); + disp_time = cur_pos_rel; + break; + case 1: + fak_minus(); + disp_time = cur_tracklen - cur_pos_rel ; + break; + case 2: + fak_minus(); + disp_time = cur_pos_abs; + break; + default: + fak_minus(); + disp_time = cur_cdlen - cur_pos_abs; + break; + } + + strcpy(cdtime, "00:00"); + if ((cur_cdmode == WM_CDM_PLAYING) || (cur_cdmode == WM_CDM_PAUSED)) { + cdtime[0] = 0 ; + cdtime[1] = (disp_time / 60); + if (cdtime[1] >= 10) { + cdtime[0] = (cdtime[1] / 10); + cdtime[1] = (cdtime[1] % 10); + } + cdtime[3] = ((disp_time % 60) / 10); + cdtime[4] = (disp_time % 10); + } else { + cdtime[0] = 0; + cdtime[1] = (cur_cdlen / 60); + if (cdtime[1] >= 10) { + cdtime[0] = (cdtime[1] / 10); + cdtime[1] = (cdtime[1] % 10); + } + cdtime[3] = ((cur_cdlen % 60) / 10); + cdtime[4] = (cur_cdlen % 10); + } + + cdtime[0] = cdtime[0] + 48; + cdtime[1] = cdtime[1] + 48; + cdtime[3] = cdtime[3] + 48; + cdtime[4] = cdtime[4] + 48; + + if (th_no_minus) fak_text(cdtime, 0, 0, FALSE); + else fak_text(cdtime, 0, 1, FALSE); + + /* ================= */ + /* The auto fade out */ + /* ================= */ + + if ((cur_cdmode == WM_CDM_PLAYING) && (fade_out)) { + if (!fade_ok) { + fade_ok = 1; + return; + } else { + unmuted_volume = volume ; + while (volume > min_volume) { + volume = volume - fade_step; + cd_volume(volume, 10, max_volume); + usleep(dodo); + } + fade_out = 0; + fade_ok = 0; + cd_control(PAUSE); + old_track = 0; + volume = unmuted_volume; + cd_volume(volume, 10, max_volume); + } + } + + /* ================= */ + /* The auto fade in: */ + /* ================= */ + + if ((cur_cdmode == WM_CDM_PAUSED) && (fade_out)){ + if (!fade_ok) { + fade_ok = 1; + return; + } else { + unmuted_volume = volume; + volume = min_volume; + cd_volume(volume, 10, max_volume); + wm_cd_status(); + usleep(dodo * 5); + cd_control(PAUSE); + usleep(dodo * 5); + while (volume < max_volume) { + volume = volume + fade_step; + cd_volume(volume, 10, max_volume); + usleep(dodo); + } + fade_out = 0; + fade_ok = 0; + volume = unmuted_volume; + cd_volume(volume, 10, max_volume); + } + } + + /* end of redraw routine */ + + old_track = cur_track; + if (debug) fprintf(stderr, "-> End of global redraw. Leaving redraw routine\n"); +} diff --git a/ascd/ascd/faktory_prot.h b/ascd/ascd/faktory_prot.h new file mode 100644 index 0000000..5dc4912 --- /dev/null +++ b/ascd/ascd/faktory_prot.h @@ -0,0 +1,15 @@ +extern void tes_sncpy(char *, char *, int); +extern char *tes_xgets(char *, int , FILE *); +extern int fak_parse_line(char *, char *, char *); +extern int fak_use_alt(int); +extern void fak_validate_pixmap(char *, char *); +extern void fak_init_theme(int); +extern int fak_load_theme(char *, int); +extern void fak_icon_text(char *, unsigned int, unsigned int, unsigned int); +extern void fak_text(char *, unsigned int, unsigned int, unsigned int); +extern int fak_flush_expose(Window); +extern void fak_minus(void); +extern void fak_singlemask(int); +extern void fak_maskset(); +extern void fak_redraw(); + diff --git a/ascd/ascd/handlers.c b/ascd/ascd/handlers.c new file mode 100644 index 0000000..303817f --- /dev/null +++ b/ascd/ascd/handlers.c @@ -0,0 +1,445 @@ +/* =========================================================================== + * AScd: handlers.c + * mouse events <-> integrated commands + * =========================================================================== + * Copyright (c) 1999 Denis Bourez and Rob Malda. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Denis Bourez & Rob Malda + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY DENIS BOUREZ AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL DENIS BOUREZ, ROB MALDA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * =========================================================================== + */ + +#include "ext.h" +#include "faktory_prot.h" + +void theme_select_init() +{ + char txt[256]; + DIR *dir_fd; + struct dirent *dir_pt; + int i; + + theme_select = 1; + theme_select_nbr = 0; + + sprintf(txt, "%s/Themes", THDIR); + if ((dir_fd = opendir(txt)) != NULL) { + while((dir_pt = readdir(dir_fd)) != NULL) if (dir_pt->d_name[0] != '.') theme_select_nbr++; + closedir(dir_fd); + } + + sprintf(txt, "%s/Themes", THDIR); + if ((dir_fd = opendir(txt)) != NULL) { + i = 0; + while((dir_pt = readdir(dir_fd)) != NULL) { + if (dir_pt->d_name[0] != '.') { + i++; + if (strcmp(dir_pt->d_name, theme) == 0) theme_select = i; + } + } + closedir(dir_fd); + } +} + +void fak_event_handle(int event, XEvent Event) +{ + + /* global commands handling: panel switches, quit, GUI additional modules */ + + switch (event) { + case FAK_QUIT: + exit(0); + break; + case FAK_PANEL_SWITCH: + panel++; + if (panel > panels) panel = 1; + fak_redraw(); + fak_maskset(); + break; + case FAK_PANEL1: + panel = 1; + fak_redraw(); + fak_maskset(); + break; + case FAK_PANEL2: + panel = 2; + fak_redraw(); + fak_maskset(); + break; + case FAK_PANEL3: + panel = 3; + fak_redraw(); + fak_maskset(); + break; + case FAK_PANEL4: + panel = 4; + fak_redraw(); + fak_maskset(); + break; + case FAK_PANEL5: + panel = 5; + fak_redraw(); + fak_maskset(); + break; + case FAK_WINGS: /* must be compiled with WINGs support */ +#ifdef WMK + big_window(scr); +#endif + break; + case FAK_COUNTER_MODE: + time_mode++ ; + if (time_mode > 3) time_mode = 0; + break; + case FAK_TOG_SHOWDB: + if (show_db) show_db = FALSE; + else show_db = TRUE; + redraw = TRUE; + break; + case FAK_TOG_SHOWARTIST: + if (show_artist) show_artist = FALSE; + else show_artist = TRUE; + redraw = TRUE; + break; + case FAK_TOG_UPPER: + if (force_upper) force_upper = FALSE; + else force_upper = TRUE; + redraw = TRUE; + break; + case FAK_TOG_AUTOPLAY: + if (autoplay) autoplay = FALSE; + else autoplay = TRUE; + redraw = TRUE; + break; + case FAK_TOG_AUTOREPEAT: + if (autorepeat) autorepeat = FALSE; + else autorepeat = TRUE; + redraw = TRUE; + break; + case FAK_TOG_ISKIPS: + if (ignore_avoid) ignore_avoid = FALSE; + else ignore_avoid = TRUE; + redraw = TRUE; + break; + case FAK_TSELECT: + if (theme_select == 0) { + selectors_timeout = time(NULL); + theme_select_init(); + } else { + theme_select = 0; + strcpy(theme, selected_theme); + fak_load_theme(theme, TRUE); + fak_maskset(); + } + break; + case FAK_TNEXT: + selectors_timeout = time(NULL); + if (theme_select == 0) theme_select_init(); + theme_select ++; + if (theme_select > theme_select_nbr) theme_select = 1; + break; + case FAK_TPREVIOUS: + selectors_timeout = time(NULL); + if (theme_select == 0) theme_select_init(); + theme_select --; + if (theme_select == 0) theme_select = theme_select_nbr; + break; + case FAK_FTSELECT: + show_db_pos = 0; + if (fast_track == 0) { + fast_track = cur_track; + selectors_timeout = time(NULL); + } else { + if (fast_track != cur_track) { + direct_track = fast_track; + fast_track = 0; + cd_control(DIRECTTRACK); + } + } + break; + case FAK_FTNEXT: + selectors_timeout = time(NULL); + show_db_pos = 0; + if (fast_track == 0) { + if (cur_track < cur_ntracks) fast_track = cur_track + 1; + else fast_track = 1; + } else { + if (fast_track == cur_ntracks) fast_track = 1; + else fast_track++; + } + break; + case FAK_FTPREVIOUS: + selectors_timeout = time(NULL); + show_db_pos = 0; + if (fast_track == 0) { + if (cur_track > 1) fast_track = cur_track - 1; + else fast_track = cur_ntracks; + } else { + if (fast_track == 1) fast_track = cur_ntracks; + else fast_track--; + } + break; + case FAK_SAVE: + newtext("Saving"); + save_rc_file(); + break; + case FAK_LOAD: + newtext("Loading"); + load_rc_file(); + break; + case FAK_QREF: + quick_reference(thdata[but_current].arg); + break; + default: + break; + } + + redraw = TRUE; + fak_singlemask(but_current); +} + +void cd_event_handle(int event, XEvent Event) +{ + /* All the AScd CD player commands, access to cd_control() */ + + switch (event) { + case FAK_CD_PLAY: + case FAK_CD_PAUSE: + if (cur_cdmode == WM_CDM_PAUSED) { + newtext("Playing"); + wanna_play = TRUE; + cd_control(PAUSE); + } else if (cur_cdmode == WM_CDM_PLAYING) { + if (fast_track != 0) { + direct_track = fast_track; + fast_track = 0; + cd_control(DIRECTTRACK); + } else { + newtext("Paused"); + cd_control(PAUSE); + } + } else if (cur_cdmode == WM_CDM_EJECTED) { + /* the future?. Not yet working on my FreeBSD box... */ + cd_control(CLOSETRAY); + } else { + newtext("Play"); + wanna_play = TRUE; + cd_control(PLAY); + if (cd->volume > 0) { + volume=cd->volume; + cd_volume(volume, 10, max_volume); + } + } + redraw = TRUE; + fak_redraw(); + break; + case FAK_CD_STOP: + fast_track = 0; + if (cur_cdmode != WM_CDM_STOPPED) { + newtext("Stop"); + cd_control(STOPONLY); + wm_cd_status(); + cur_track = 0; + redraw = TRUE; + fak_redraw(); + } + break; + case FAK_CD_STOPEJECT: + fast_track = 0; + if (cur_cdmode != WM_CDM_STOPPED) { + newtext("Stop"); + cd_control(STOPONLY); + wm_cd_status(); + cur_track = 0; + redraw = TRUE; + fak_redraw(); + fak_maskset(); + } else { + newtext("Eject"); + cd_control(EJECT); + redraw = TRUE; + fak_redraw(); + } + break; + case FAK_CD_EJECT: + fast_track = 0; + newtext("Eject"); + cd_control(EJECT); + redraw = TRUE; + fak_redraw(); + break; + case FAK_CD_EJECTQUIT: + fast_track = 0; + if ((cur_cdmode != WM_CDM_EJECTED) && (cur_cdmode != WM_CDM_STOPPED)) { + newtext("Eject"); + cd_control(EJECT); + redraw = TRUE; + fak_redraw(); + } else { + exit(0); + } + break; + case FAK_CD_NEXT: + cd_control(UPTRACK); + break; + case FAK_CD_PREVIOUS: + cd_control(DNTRACK); + break; + case FAK_CD_FIRST: + cd_control(FIRST); + break; + case FAK_CD_LAST: + cd_control(LAST); + break; + case FAK_CD_REW: + cd_control(REV); + break; + case FAK_CD_FWD: + cd_control(CUE); + break; + case FAK_CD_DIRECT: + /* Direct Access: + we have to compute the offset to pass it to cd_control. We also have to + check if we want to move inside the current track or in the whole CD */ + + if (cur_track < 1) cur_track = 1; + + if ((time_mode == 2) || (time_mode == 3)) { + if ((thdata[but_current].type == FAK_CD_BAR) || (thdata[but_current].type == FAK_CD_PIX)) { + direct_access = (int)((float)(Event.xbutton.x - thdata[but_current].x) / (float)thdata[but_current].xpm.attributes.width * (float)cur_cdlen); + } else if (thdata[but_current].type == FAK_ICD_BAR) { + direct_access = (int)((float)(Event.xbutton.y - thdata[but_current].y) / (float)thdata[but_current].xpm.attributes.height * (float)cur_cdlen); + direct_access = cur_cdlen - direct_access; + } else { + direct_access = (int)((float)(Event.xbutton.y - thdata[but_current].y) / (float)thdata[but_current].xpm.attributes.height * (float)cur_cdlen); + } + cd_control(GLOBALACCESS); + } else { + if ((thdata[but_current].type == FAK_CD_BAR) || (thdata[but_current].type == FAK_CD_PIX)) { + direct_access = (int)((float)(Event.xbutton.x - thdata[but_current].x) / (float)thdata[but_current].xpm.attributes.width * (float)cur_tracklen); + } else if (thdata[but_current].type == FAK_ICD_BAR) { + direct_access = (int)((float)(Event.xbutton.y - thdata[but_current].y) / (float)thdata[but_current].xpm.attributes.height * (float)cur_tracklen); + direct_access = cur_tracklen - direct_access; + } else { + direct_access = (int)((float)(Event.xbutton.y - thdata[but_current].y) / (float)thdata[but_current].xpm.attributes.height * (float)cur_tracklen); + } + cd_control(DIRECTACCESS); + } + wm_cd_status(); + redraw = TRUE; + fak_redraw(); + fak_maskset(); + break; + case FAK_CD_VOLUME: + if ((cur_cdmode != WM_CDM_EJECTED) && (cur_cdmode != WM_CDM_STOPPED)) { + if ((thdata[but_current].xpm.attributes.width > 0) && (max_volume > 0)) { + if (thdata[but_current].type == FAK_VVOL_BAR) { + volume = (int)((float)(Event.xbutton.y - thdata[but_current].y) / (float)thdata[but_current].xpm.attributes.height * (float)max_volume); + } else if (thdata[but_current].type == FAK_IVOL_BAR) { + volume = (int)((float)(Event.xbutton.y - thdata[but_current].y) / (float)thdata[but_current].xpm.attributes.height * (float)max_volume); + volume = max_volume - volume; + } else { + volume = (int)((float)(Event.xbutton.x - thdata[but_current].x) / (float)thdata[but_current].xpm.attributes.width * (float)max_volume); + } + cd_volume(volume, 10, max_volume); + wm_cd_status(); + redraw = TRUE; + fak_redraw(); + } + } + break; + case FAK_CD_LOOP: + if (loop_mode) { + loop_mode = 0; + newtext("Loop Off"); + } else { + cd_control(LOOP); + if (!anomalie) newtext("Loop On"); + else newtext("ERROR!"); + } + redraw = TRUE; + break; + case FAK_CD_LSTART: + loop_1 = cur_pos_rel; + newtext("L. Start"); + loop_start_track = cur_track; + break; + case FAK_CD_LEND: + loop_2 = cur_pos_rel; + loop_end_track = cur_track; + newtext("L. End"); + break; + case FAK_CD_GOLSTART: + direct_access = loop_1; + cd_control(DIRECTACCESS); + break; + case FAK_CD_GOLEND: + direct_access = loop_2; + cd_control(DIRECTACCESS); + break; + case FAK_CD_LTRACK: + loop_1 = 0; + loop_2 = cur_tracklen; + loop_start_track = cur_track; + loop_end_track = cur_track; + break; + case FAK_CD_LCLEAR: + loop_1 = 0; + loop_2 = 0; + loop_start_track = 1; + loop_end_track = 1; + newtext("L. Clear"); + break; + case FAK_CD_INTRO: + cd_control(INTROSCAN); + wm_cd_status(); + newtext("Intro"); + fak_maskset(); + break; + case FAK_CD_MUTE: + if (muted) { + muted = 0; + newtext("Mute Off"); + cd_volume(volume, 10, max_volume); + } else { + newtext("Mute On"); + muted = 1; + cd_volume(muted_volume, 10, max_volume); + } + break; + case FAK_CD_FADE: + fade_out = TRUE; + newtext("Fade..."); + /*fak_redraw();*/ + break; + default: + break; + } + + redraw = TRUE; + fak_singlemask(but_current); +} diff --git a/ascd/ascd/misc.c b/ascd/ascd/misc.c new file mode 100644 index 0000000..80fca37 --- /dev/null +++ b/ascd/ascd/misc.c @@ -0,0 +1,431 @@ +/* =========================================================================== + * AScd: the AfterStep and WindowMaker CD player + * misc.c: various stuff + * =========================================================================== + * Copyright (c) 1999 Denis Bourez and Rob Malda. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Denis Bourez & Rob Malda + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY DENIS BOUREZ AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL DENIS BOUREZ, ROB MALDA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * =========================================================================== + */ + +#include "ext.h" + +extern tes_xgets(); +extern fak_parse_line(); +extern cd_control_version(); + +/* The rc file functions, taken from xfascd*/ + +void load_rc_file(int mx) +{ + FILE *in; + struct passwd *pw; + char st[256]; + + if (NULL == (pw = getpwuid(getuid()))) { + return; + } else { + sprintf(st, "%s/.ascdrc", pw->pw_dir); + if ((in = fopen(st, "r"))) { + while ( fgets(st, 255, in) ) { + if (strlen(st) > 0) st[strlen(st) - 1] = 0; + + if (!mx) { +#ifndef NO_D_DEVICE + if (strncmp(st, "CD_DEVICE=", 10) == 0) strcpy(cd_device, st + 10); + else if (strncmp(st, "THEME=", 6) == 0) strcpy(theme, st + 6); + else if (strncmp(st, "VISU=", 5) == 0) strcpy(xv, st + 5); + else if (strncmp(st, "AUTOREPEAT=", 11) == 0) autorepeat = atoi(st + 11); +#else + if (strncmp(st, "AUTOREPEAT=", 11) == 0) autorepeat = atoi(st + 11); +#endif + else if (strncmp(st, "AUTOPLAY=", 9) == 0) autoplay = atoi(st + 9); + else if (strncmp(st, "AUTOPROBE=", 10) == 0) autoprobe = atoi(st + 10); + else if (strncmp(st, "CUE_TIME=", 9) == 0) cue_time = atoi(st + 9); + else if (strncmp(st, "TI=", 3) == 0) text_timeout = atoi(st + 3); + else if (strncmp(st, "MAX_VOL=", 8) == 0) max_volume = atoi(st + 8); + else if (strncmp(st, "MIN_VOL=", 8) == 0) min_volume = atoi(st + 8); + else if (strncmp(st, "MUT_VOL=", 8) == 0) muted_volume = atoi(st + 8); + else if (strncmp(st, "VOLUME=", 7) == 0) volume = atoi(st + 7); + else if (strncmp(st, "FADE_STEP=", 10) == 0) fade_step = atoi(st + 10); + else if (strncmp(st, "TIMEMODE=", 9) == 0) time_mode = atoi(st + 9); + else if (strcmp(st, "WITHDRAWN") == 0) withdrawn = TRUE; + else if (strcmp(st, "SHOWDB") == 0) show_db = TRUE; + else if (strcmp(st, "SHOWARTIST") == 0) show_artist = TRUE; + else if (strcmp(st, "UPPERCASE") == 0) force_upper = TRUE; + else if (strncmp(st, "IGNORE_AVOID=", 13) == 0) ignore_avoid = atoi(st + 13); + } + } + fclose(in); + return; + } else { + return; + } + } +} + +void save_rc_file() +{ + FILE *out; + struct passwd *pw; + char st[128]; + + if (NULL == (pw = getpwuid(getuid()))) { + /* hum... big problem... */ + return; + } else { + sprintf(st, "%s/.ascdrc", pw->pw_dir); + if ((out = fopen(st, "w"))) { + fprintf(out, "#### generated by AScd version %s ####\n", VERSION); +#ifndef NO_D_DEVICE + fprintf(out, "CD_DEVICE=%s\n", cd_device); +#endif + fprintf(out, "THEME=%s\n", theme); + fprintf(out, "VISU=%s\n", xv); + fprintf(out, "AUTOREPEAT=%d\n", autorepeat); + fprintf(out, "AUTOPLAY=%d\n", autoplay); + fprintf(out, "AUTOPROBE=%d\n", autoprobe); + fprintf(out, "CUE_TIME=%d\n", cue_time); + fprintf(out, "TI=%d\n", text_timeout); + fprintf(out, "VOLUME=%d\n", volume); + fprintf(out, "MAX_VOL=%d\n", max_volume); + fprintf(out, "MIN_VOL=%d\n", min_volume); + fprintf(out, "MUT_VOL=%d\n", muted_volume); + fprintf(out, "FADE_STEP=%d\n", fade_step); + fprintf(out, "TIMEMODE=%d\n", time_mode); + fprintf(out, "IGNORE_AVOID=%d\n", ignore_avoid); + if (withdrawn) { + fprintf(out, "WITHDRAWN\n"); + } + if (show_db) { + fprintf(out, "SHOWDB\n"); + } + if (show_artist) { + fprintf(out, "SHOWARTIST\n"); + } + if (force_upper) { + fprintf(out, "UPPERCASE\n"); + } + + fclose(out); + return; + } else { + return; + } + } +} + +void command_line_help() +{ + fprintf(stderr, "ascd %s ", VERSION); +#ifdef WMK + fprintf(stderr, "[WINGs] "); +#endif +#ifdef MIXER + fprintf(stderr, "[mixer] "); +#endif + fprintf(stderr, "\n(c) 1999 Rob Malda and Denis Bourez\n"); + fprintf(stderr, "Usage: ascd [-options ...] where the options are:\n\n"); + + fprintf(stderr, "-version Display the version numbers and exit\n"); + fprintf(stderr, "-help Display this help screen and exit\n"); + fprintf(stderr, "-themes Display available themes and exit\n\n"); + + fprintf(stderr, "+/-withdrawn Turn on/off withdrawn mode for WindowMaker\n"); + fprintf(stderr, "-geometry geom Standard X Location\n"); + fprintf(stderr, "-ti time Texts display timeout in seconds (default = 1)\n"); + fprintf(stderr, "-cue time Cue Time in seconds (default = 10)\n"); + fprintf(stderr, "+/-a Turn on/off Autoplay mode\n"); + fprintf(stderr, "+/-p Turn on/off Autoprobe mode\n"); + fprintf(stderr, "+/-t Turn on/off Autorepeat mode\n"); + fprintf(stderr, "+/-B Turn on/off Blind mode (no counters updates)\n"); + fprintf(stderr, "+/-ia Apply/Ignore 'avoid' tags in WorkMan database\n"), + fprintf(stderr, "+/-up All messages in uppercase?\n"); + fprintf(stderr, "-theme value Select the visual theme (in %s)\n", THDIR); + fprintf(stderr, "-xf Launch XfAscd with middle click on counter\n"); + fprintf(stderr, "-minvolume value Minimum volume\n"); + fprintf(stderr, "-maxvolume value Maximum volume\n"); + fprintf(stderr, "-volume value CD Drive volume, from 0 to maxvolume\n"); + fprintf(stderr, "-mvolume value CD Drive volume in mute mode, from 0 to maxvolume\n"); + fprintf(stderr, "-fadestep value Fade in/out timing offset\n"); + fprintf(stderr, "-device device_driver CDROM drive device\n"); +#ifdef MIXER + fprintf(stderr, "-mixer device_driver Mixer device\n"); +#endif + fprintf(stderr, "-showtitle Turn on Song Title Scrolling\n"); + fprintf(stderr, "-showartist Show artist name when scrolling song title\n"); + fprintf(stderr, "-debug Enable debug (verbose) messages on stderr\n"); + fprintf(stderr, "-save Save settings and exit. Should be *last* command.\n\n"); + exit(0); +} + +void command_line_err() +{ + fprintf(stderr, "ascd: bad command line syntax.\n"); + fprintf(stderr, "Type 'ascd -help' for a list of command line options.\n"); + exit(-1); +} + +void themes_help() +{ + char txt[256]; + char key[256]; + char r[256]; + char d[256]; + char arguments[256]; + DIR *dir_fd; + struct dirent *dir_pt; + FILE *in; + + fprintf(stderr, "List of AScd installed themes:\n\n"); + + fprintf(stderr, "Theme Release Description\n"); + fprintf(stderr, "---------------- ------------ -------------------------------------\n"); + + sprintf(txt, "%s/Themes", THDIR); + if ((dir_fd = opendir(txt)) != NULL) { + while((dir_pt = readdir(dir_fd)) != NULL) { + if (dir_pt->d_name[0] != '.') { + fprintf(stderr, "%-16s ", dir_pt->d_name); + sprintf(txt, "%s/Themes/%s/Theme", THDIR, dir_pt->d_name); + if (access(txt, R_OK) == 0) { + if (!(in = fopen(txt, "r"))) { + fprintf(stderr, "cannot open Theme definition file"); + } else { + strcpy(d, ""); + strcpy(r, ""); + while (tes_xgets(txt, 256, in)) { + fak_parse_line(txt, key, arguments); + if (strcmp(key, "release") == 0) strcpy(r, arguments); + if (strcmp(key, "name") == 0) strcpy(d, arguments); + } + fprintf(stderr, "%-12s %s", r, d); + fclose(in); + } + } else { + fprintf(stderr, "cannot find Theme definition file"); + } + fprintf(stderr, "\n"); + } + } + closedir(dir_fd); + } else { + fprintf(stderr, "Can't read directory %s!\n", txt); + } + fprintf(stderr, "\n"); + + exit(0); +} + +#define MAX_LINE_NAME 16 + +void command_line_parse(int argc, char *argv[]) { + int i; + char *Argument; + int sw; + + for (i = 1 ; i < argc ; i++) { + Argument = argv[i]; + + if ((Argument[0] == '-') || (Argument[0] == '+')) { + if (Argument[0] == '-') { + sw = FALSE; + } else if (Argument[0] == '+') { + sw = TRUE; + } + + if ((strcmp(Argument + 1, "w") == 0) || (strcmp(Argument + 1, "withdrawn") == 0)) { + if (sw) withdrawn = TRUE; + else withdrawn = FALSE; + } + + else if ((strcmp(Argument + 1, "g") == 0) || (strcmp(Argument + 1, "geometry") == 0)) { + if(++i >= argc) command_line_err(); + Geometry = argv[i]; + } + + else if ((strcmp(Argument + 1, "d") == 0) || (strcmp(Argument + 1, "device") == 0)) { + if(++i >= argc) command_line_err(); + cd_device = malloc(strlen(argv[i]) + 1); + strcpy(cd_device, argv[i]); + } + +#ifdef MIXER + else if (strcmp(Argument + 1, "mixer") == 0) { + if(++i >= argc) command_line_err(); + strcpy(mixer_device, argv[i]); + } +#endif + + else if (strcmp(Argument + 1, "theme") == 0) { + if(++i >= argc) command_line_err(); + strcpy(theme, argv[i]); + } + + else if ((strcmp(Argument + 1, "c") == 0) || (strcmp(Argument + 1, "cue") == 0)) { + if(++i >= argc) command_line_err(); + cue_time = atoi(argv[i]); + } + + else if ((strcmp(Argument + 1, "i") == 0) || (strcmp(Argument + 1, "ti") == 0)) { + if(++i >= argc) command_line_err(); + text_timeout = atoi(argv[i]); + } + + else if ((strcmp(Argument + 1, "a") == 0) || (strcmp(Argument + 1, "autoplay") == 0)) { + if (sw) autoplay = TRUE; + else autoplay = FALSE; + } + + else if ((strcmp(Argument + 1, "p") == 0) || (strcmp(Argument + 1, "autoprobe") == 0)) { + if (sw) autoprobe = TRUE; + else autoprobe = FALSE; + } + + else if (strcmp(Argument + 1, "B") == 0) { + if (sw) blind_mode = TRUE; + else blind_mode = FALSE; + } + + else if (strcmp(Argument + 1, "ia") == 0) { + if (sw) ignore_avoid = TRUE; + else ignore_avoid = FALSE; + } + + else if (strcmp(Argument + 1, "up") == 0) { + if (sw) force_upper = TRUE; + else force_upper = FALSE; + } + + else if ((strcmp(Argument + 1, "t") == 0) || (strcmp(Argument + 1, "autorepeat") == 0)) { + if (sw) autorepeat = TRUE; + else autorepeat = FALSE; + } + + else if (strcmp(Argument + 1, "showtitle") == 0) { + show_db = TRUE; + } + + else if (strcmp(Argument + 1, "showartist") == 0) { + show_artist = TRUE; + } + + else if (strcmp(Argument + 1, "debug") == 0) { + debug++; + } + + else if (strcmp(Argument + 1, "mvolume") == 0) { + if(++i >= argc) command_line_err(); + muted_volume = atoi(argv[i]); + } + + else if (strcmp(Argument + 1, "volume") == 0) { + if(++i >= argc) command_line_err(); + volume = atoi(argv[i]); + } + + else if (strcmp(Argument + 1, "xf") == 0) { + xflaunch = TRUE; + } + + + else if (strcmp(Argument + 1, "maxvolume") == 0) { + if(++i >= argc) command_line_err(); + max_volume = atoi(argv[i]); + } + + else if (strcmp(Argument + 1, "minvolume") == 0) { + if(++i >= argc) command_line_err(); + min_volume = atoi(argv[i]); + } + + else if (strcmp(Argument + 1, "fadestep") == 0) { + if(++i >= argc) command_line_err(); + fade_step = atoi(argv[i]); + } + + else if (strcmp(Argument + 1, "save") == 0) { + save_rc_file(); + fprintf(stderr, "Settings file saved.\n"); + exit(0); + } + + else if ((strcmp(Argument + 1, "version") == 0) || (strcmp(Argument + 1, "v") == 0)) { + fprintf(stderr, "%s %s (CDcontrol %s, %s) ", PACKAGE, VERSION, (char *)cd_control_version(), wm_libver_string()); +#ifdef WMK + fprintf(stderr, "[WINGs] "); +#endif +#ifdef MIXER + fprintf(stderr, "[mixer] "); +#endif + fprintf(stderr, "\n"); + exit(0); + } + + else if (strcmp(Argument + 1, "themes") == 0) { + themes_help(); + } + + else if ((strcmp(Argument + 1, "h") == 0) || (strcmp(Argument + 1, "help") == 0)) { + command_line_help(); + } else { + command_line_err(); + } + } else { + command_line_err(); + } + + } + +} + +void quick_reference(int nbr) +{ + char txt[127]; + char fic[40]; + + if (nbr == 0) strcpy(fic, "quick*"); + else sprintf(fic, "quick%d.gif", nbr); + + sprintf(txt, "%s/Themes/%s/quick", THDIR, theme); + if (access(txt, R_OK) == 0) { + sprintf(txt, "%s %s/Themes/%s/quick/%s &", xv, THDIR, theme, fic); + system(txt); + } +} + +/* ------------------------------------------------------------------------ + dumb functions: do nothing, but they need to be present if we don't + wanna change anything in the WorkMan code... + ------------------------------------------------------------------------ */ + +void disable_save() { } +int get_playnew() { return autoplay; } +void set_abtimer(int a, int b) { } +void about_set_drivetype(char *vendor, char *model, char *rev) { } diff --git a/ascd/ascd/misc/config.h.default b/ascd/ascd/misc/config.h.default new file mode 100644 index 0000000..6c48908 --- /dev/null +++ b/ascd/ascd/misc/config.h.default @@ -0,0 +1,5 @@ +#define PACKAGE "ascd" +#define DEFAULT_COLOR "#2FAFAF" +#define DEFAULT_BGCOLOR "#000000" +#define DEFAULTDEVICE "/dev/cdrom" +#define THDIR "/usr/local/share/AScd" diff --git a/ascd/ascd/misc/default b/ascd/ascd/misc/default new file mode 100755 index 0000000..412d3df --- /dev/null +++ b/ascd/ascd/misc/default @@ -0,0 +1,8 @@ +#!/bin/sh +# This generates a default configuration file without +# asking any question + +cp -f misc/config.h.default config.h +cp -f misc/dirs.h.default dirs.h +cat version.h >> config.h +xmkmf diff --git a/ascd/ascd/misc/dirs.h.default b/ascd/ascd/misc/dirs.h.default new file mode 100644 index 0000000..6f1c2f9 --- /dev/null +++ b/ascd/ascd/misc/dirs.h.default @@ -0,0 +1,3 @@ +#define CBINDIR /usr/local/bin +#define CMANDIR /usr/local/man/man1 +#define CTHDIR /usr/local/share/AScd diff --git a/ascd/ascd/mixer_ext.h b/ascd/ascd/mixer_ext.h new file mode 100644 index 0000000..4e82a37 --- /dev/null +++ b/ascd/ascd/mixer_ext.h @@ -0,0 +1,13 @@ +#if defined (__FreeBSD__) +# include <machine/soundcard.h> +#else +# include <sys/mount.h> +# include <linux/soundcard.h> +#endif + +extern char mixer_device[]; +extern int mixer_ok; +extern int mixer_control; +extern int mixer_vol; +extern int mixer_but; +extern int mixer_old; diff --git a/ascd/ascd/mixer_global.c b/ascd/ascd/mixer_global.c new file mode 100644 index 0000000..9d11de0 --- /dev/null +++ b/ascd/ascd/mixer_global.c @@ -0,0 +1,15 @@ +#if defined (__FreeBSD__) +# include <machine/soundcard.h> +#else +# include <sys/mount.h> +# include <linux/soundcard.h> +#endif + +#define DEFAULTMIXERDEVICE "/dev/mixer" + +char mixer_device[128]=DEFAULTMIXERDEVICE; +int mixer_ok = 1; +int mixer_control; +int mixer_vol; +int mixer_but; +int mixer_old; diff --git a/ascd/ascd/mixer_handlers.c b/ascd/ascd/mixer_handlers.c new file mode 100644 index 0000000..258ff69 --- /dev/null +++ b/ascd/ascd/mixer_handlers.c @@ -0,0 +1,134 @@ +/* =========================================================================== + * AScd: mixer_handlers.c + * mouse events <-> integrated commands with mixer module + * =========================================================================== + * Copyright (c) 1999 Denis Bourez and Rob Malda. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Denis Bourez & Rob Malda + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY DENIS BOUREZ AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL DENIS BOUREZ, ROB MALDA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * =========================================================================== + */ + +#include "ext.h" + +#ifdef MIXER + +int check_mixer() +{ + int fd; + if ((fd = open(mixer_device,0))) { + close(fd); + } else { + return FALSE; + } + return TRUE; +} + +int getvol(int control) +{ + int fd; + int status = 0; + + if (mixer_ok) { + if ((fd = open(mixer_device,0))) { + ioctl(fd, MIXER_READ(control), &mixer_vol); + mixer_vol = mixer_vol >> 8; + close(fd); + return mixer_vol; + } + } + return 0; +} + +void setvol(int control, int setto, int maximum) +{ + int fd; + + if (setto > maximum) setto = maximum; + if (setto < 0) setto = 0; + + if ((fd = open(mixer_device, 0))) { + mixer_vol = ((float)setto / (float)maximum) * 100; + mixer_old = mixer_vol << 8; + mixer_vol = mixer_old | mixer_vol; + ioctl(fd, MIXER_WRITE(control), &mixer_vol); + close(fd); + } +} + +void mixer_event_handle(int event, XEvent Event) +{ + int value; + + if (!mixer_ok) return; + + switch (event) { + case FAK_MIXER_SET: + if ((thdata[but_current].xpm.attributes.width > 0) && (max_volume > 0)) { + if (thdata[but_current].type == FAK_MIXER_BAR) { + value = (int)((float)(Event.xbutton.x - thdata[but_current].x) / (float)thdata[but_current].xpm.attributes.width * 100.0); + } else if (thdata[but_current].type == FAK_VMIXER_BAR) { + value = (int)((float)(Event.xbutton.y - thdata[but_current].y) / (float)thdata[but_current].xpm.attributes.height * 100.0); + } else { + value = (int)((float)(Event.xbutton.y - thdata[but_current].y) / (float)thdata[but_current].xpm.attributes.height * 100.0); + value = 100 - value; + } + setvol(thdata[but_current].arg, value, 100); + redraw = TRUE; + fak_redraw(); + } + break; + case FAK_MIXER_0: + setvol(thdata[but_current].arg, 0, 100); + redraw = TRUE; + fak_redraw(); + break; + case FAK_MIXER_50: + setvol(thdata[but_current].arg, 50, 100); + redraw = TRUE; + fak_redraw(); + break; + case FAK_MIXER_75: + setvol(thdata[but_current].arg, 75, 100); + redraw = TRUE; + fak_redraw(); + break; + case FAK_MIXER_100: + setvol(thdata[but_current].arg, 100, 100); + redraw = TRUE; + fak_redraw(); + break; + default: + break; + } + + redraw = TRUE; + fak_singlemask(but_current); +} + +#endif diff --git a/ascd/ascd/themes/default.tar b/ascd/ascd/themes/default.tar new file mode 100644 index 0000000000000000000000000000000000000000..1c501b84dbb2ba842764ead3870e310bdfa8c23a GIT binary patch literal 40960 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 a`uwjCy#!AArxiG@z-a|eEAR(gf&T|B=m%E- literal 0 HcmV?d00001 diff --git a/ascd/ascd/themes/themes-manual.ps.gz b/ascd/ascd/themes/themes-manual.ps.gz new file mode 100644 index 0000000000000000000000000000000000000000..21ff8603776338e83f641413641fa5cf579b4f0e GIT binary patch literal 82472 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ʑ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 ae|qX|UjOUg{O`Yg_x}LGWdqWQGzI_>A=)nh literal 0 HcmV?d00001 diff --git a/ascd/ascd/themes/themes.tar b/ascd/ascd/themes/themes.tar new file mode 100644 index 0000000000000000000000000000000000000000..a02586c51ca1b05d83f05c4eff2ecaaa156ee9c4 GIT binary patch literal 92160 zcmeF&cTiJbyeRr4bV4BXj-j{Edl5qukRnZ*fT4FpI*3RKz4wlEkS2s8H9~+;MVd$x zK@boS5fBwI-u&LVXXe~9@6CDV&AE5pn|IegNoK8CS$luK-<9mW=VR{f;^Pt^;q2n- z5ab;w0r;QMV6u`5a&j;L3?`)@C;4w*VgJ2J!eH_W(vp%ga&oeAgkjP$vI+p0-2aTl z{QEhB0s<ZUVK9KRi?2t(zwi7vpZ}x&e>3#|OK;8p$M`=A@^JF{ug-%1eEel(<z@bN z{Qp<v(f?`u|Boz?5_j`({V!R9l%%A*tn9z7`+waB{waSMS!sFcf31IMIRynm{maQJ z$N^xI|0Uo4A58N9^ZM7(yRE3?AP?dK?gId%AYzg7)cBYP40fQrswP=l_T$Ih<+|qE zx3$*dbN8^LXmsbMQAB!nDwn78^1>*(s-~{7X45lb88>r>i*Etl>FVl?o0>U0yP%Vo zjZaPu4i4<@8!av_{kXPq$)+kln~TGaTz=fOurQy^FTIpEZ>+0X4n`ezkESOl?%4#^ zH8zWgz_FNt{OsK2wGCfiZ~Ll1EN-Z-s`RR=GcxiqeqrMzzeZkOR)dM-5<4^7H-PCI zs46c#6fr+LJhr#DrKYBekB`BkJ0rs*ip#6*Eez7rQ-b{iB4bb(?9AceG2YaCVPWag z6E!<EdUbUj9`1b>p4-~ke9|~@Ro;9lZctU%Y-D86*WH<&U)tJOk({1(xVg)8(u~86 zUVYp>yX|~tADEw?>+A1*nO&o%rg|n|5FekKo}TNQ?!30VLQP49#g3L&HJ|M58r{*l zipf2jT0#?M%g){G9taPP=)(-#*w|cQ#-*iYH`g}m>Z;GyHk9R6@l#7{n3)(6xDgtK zQPUFGEG5Nv-o~Wv1*fhRH(y!=b`uuqpL&HJ5b*bAQ&T-rv)znLy^72|G4hsQ82IRq zy5!NiN-o`O?8MYH<IgTG8wW0pyic$*m;R~C=mBYQK0Iz|88ga6$8Z&#`Z2%e5VwTE z4BFe+kdhKFG<M#(qd(g{;OXi7adY=<?-<iP5EGBW?;T&>@xFA7xZK>mn%&sj+g(o1 zotmAwyu7GuZ59`YU)FV=ogAN>94{{~;qlj(`88)E23M^Eo4BRf*_qbX=GNAl6Pv(G zHCz1h#^&b6rKxvoU1xrNDHb;q9v*?i&D7O3S5?(`y4ofuC-wCWlo#jPSQxa{HRCX^ z{CxwJ<;71#4CtsClH+5_%geIUQ)6OcFYP0W^Hcr(12-|Fl%!O9A9uUaosEq(Q&TgI zjm>L>WIH^@VrP~Ymhe~CC!4$YkGogdHQDL8-QAs6{;7L}W2x)B%CET;F^K;sF-Fs~ zbAy8;xbf764$dyH5RX82n2w&IF#!J0?(t7`0LbxxXbKM9wu<3ca(WTliMGm-1Ss#F zQr-5du@n{=&y9)p>hTPiW-^B!x@Iy*$gI(J5?wo8An7z!s@GBXx<oPHaAUHg{>?K) zEG6fi&W8DF-5e3Ssm|w%^~TkA%I<VEzHPGV@O(ej)%5NKawM5kzq@(0!+o*QZo0c= z{iWZ|RGI$E*7yCP=ZEj7Uw(Z@5(Qj0^#JN_V<YKBZy(LHIe}-nT4Qk<=9$xl+T2my zLwv3?DViylo=4~V!(bn4$+BN)7n7>xtZS;ronO}Pby>-aiJ&#^!)-gbHp6zk=6Z5O zA0)#jzP%@pB>MpQ-4n1lrhR5VJJ>%LR4|B#6r@4fyxf^d?dM3GL!eH4Eo9$}UPT(Y zpNKtv3=!M@euRfPr-Z3H3nG!E0?ztKq9@(Yo<MJtoB55{;XoRFJt5Yf@S%`7(3fvG zknAqwE>&DBH<HSWuKS=$PqdGW82WBFzg%6^RbVQOw#97qY0I!H%#W=*_<gK|z;iww zSv*;x2ZEypoT=WnMde8?X6zit1tKc-+rFpxA=^1$I7>{MQmaIWtWv2|2yP{LJBd8{ z<=)d17iPz_PNj*uqA1ehOr{oX3aZFwzbuRcls(;xNGPbaDj&~n@nnkiNgi&KXrnXV zgUl&K4jYCflZ$GHY>_sBq}!mnLi{}emL0dgh=b<{!;@eK^(WbfUPZONFTv{lr%C+k z6=$w|-riOS<DAy-<C&pF^Uqx)U%8g>MZeP%HHNf=-Jc^#HSOnjwPX5iT|m*2Q>VV} zA^H$?%U-9xvFJ<k97(RQehCJF@14snVzrs;KNr|2D|`0(;I#Zw8s!0*+k07E+$l%g zm`WBc>}O=$xtI>@iBm;83gp<{7>y_wO&+H9kb3VnHn;{IZXMpJ@VLo}G-PK%JSusE z0DobgS6_77E%BmrPU#}s*6%<Z_K|j}PdefbhYPuT=^4ffO=qN8c0W$j>Ho~ucSNQb zcCWfOrCF{!75pI;Jh(u2<9$%dl%lSyEe#D%5!pJtwGfhx_IR}}r`K=Bnt6XIFKY!H z+ox-)DSgV{$qQp2Yp#lsq%e={b*!_AA4-i=B1MTbQFls#o%nKtpdTkOt~EnHtu|^3 zAnEOXszrZNCcdElj4_W{X(q_AoVr{R7jT?S0PEl%uFvNeT~yBuON}ShCXm=c_u)v~ zPS1;~{OurK@u*Lry-$L^Zws@89tTu~lKXmD(oKMV78I5JbYwkJte8k1PCsth0Xwmb zNL<Dj5VaDqy8T4(^C3ZG8pbgUs1y>$pCDTVjH-l%OqfS;c<%bE@_iNZW7JLwhjqQO zIXV?XJW+CJyf2L(6NY$29!QmK5z$fsMkVs+sRfuuxTrgI<W|~K2jC8@-BzUhD3ciH z#9?~lYpimOu6D1jREoY9m$<J~vSO|Sxmoi^N7rChF(DN7Upn3N20j6%(vx6Kt#=4= zevVh);DSP@>Iw~W#P<`uS$!nO-e8c)9u>n3?>naHytlzrH8HGcBo3|%o0K<R$SzEs z7I?Cqa$EipYn{~?#9fjqph7z5N!FUL(Y~zCq$_9L#~qrndzmB^i5{emtgco_fX-(W zeRk#;PvktxQrro+EE&hA0!ycEm=R~uWhY(s0|{>M(zy)P$+rv}KE&~tmU)w@HunPV zUJsLY;#V{jUac};c2bj%s*=lck)n=QG{AF?uU$i}p(qP-@+11h8MO|u3MjThxrsu# zW(#Ek9m$)P1bph8Bva;4CqEkcWV@9FyuZXvolkz7Q6tOHEcc>DI1a4uGfzw;Yf{5q zs3TsyMOE6kWI(<^(#V6ZAUV*81P_xSd9HG7jVr0{O~lGk4UO(P1ORmaWSpMJRQ`=I zZx-Dn&d@BH+q>$(%QjN0u{#zW7?KtbITFkD_5{J1ZHNRpncZ|_TPH%Dx_BZMvV0l` z7vEu)=Nyr(nU7|Rs^$!d*02f4O0h79(fl~pB>xGsp0Z);co?lAJ{1onk_~3f=)%WK zO-QXy*>|_OCnczy7A1wQ#5TRMB2!UDQaKrnWa}ix&mUB+y>{GUd;*OYT}D!UWMl4~ zGW_CcnG*d$+N4KY=;YCS=EkRw`S9MK(VLQU@$$0U;Eay=n=8X)3(G@XI%!~gsna;< z^+&MkL`=kIuLsvymsekWH27MEW0jzxR6kyS4SQ8(JuY9#V(5+~`ZPuDajMQX2O!fl zTQ?Pu-=<*203w*@wrLVVN8O@hB;ctj;HT=O9QpAH4^$mGE<lvUH<5npUNnzcsxilk z8gj|B*xsL@30IjRwq<o^US8`-gJt1NsjU=?>nau>a&@NWnFKzG-pLJGI=n{bg;dH` zy}ZRQU~U||SAOCh3m2C!glQ|K9}b+-mmHGtQ~#v0-;0i5s+_Be*mhr8ok-XWcxFk8 zY68Y0S+~uh79orsKuuhXIPPq>AHa84lVdT{$!@03hC77~b14*5bhpg2OwAgXk;akk z<l|}HR4j4S21o+%A{ylHW#ABLVx=a9qGP(SBUtxhqL>olXNWD9b<QPFoy(O--6Y0( zuC~I2<xDmR;jgjWHA(Lsg=OLMlqunTWuV7<w|Zs)3gS$TE~vN{3b~Mv8~oPtF!Ct3 zL(E5Kh-`B^=6Brv{$Z8~f4)Z&oE<&v{_h@BqN`j<hOHlWri$S&e|}4ejK5dlkv%3u zU&K*4p+G?r=C%1H%cxa8<;z@K*^4IXU~?jf4<f);dD7+nPxF2BP2V?9xdMCOmELZT z;R|6W0Ttjuao@u?9nssRv)+S8wT%nNQd*z=2;mA`TPjm(JGH`OozjYYhQf+kyx}^J z75Wi1G<BYpII-$h=zwuL=^}@l5>etHd+hoM*Id>F%b`1Qz{0SJE4c&$`<(wuAYky^ z!?>I?e>@^JiR-&W2>!fsA3PS_el=|np*hq>6;V3U$}uk#Pc+=gb(5*~$F|5Vl8@o1 z36{LeBjvYEG}P3+6ItDAQ3qlWIsUZuXlY9ewj5GHuQWWD-{JjNq$@YVZL0r%axcXG z4yXQWzo1r^=Flv2!W`)aeW9Xgw-+dShC{6UHOEZH|LH+{fRR*vncinFW#s0bo|3sW zZNt9+Wv&&XyzA+T^DJa=VPF9DEFx?R>KN?8EZuFx>>keA6CE)Zy>%MxYZ9%U5Y09b z%{9S1yucbND^Q?LHe?*j_>O~Ik_FsGO)LW|j^@pmf@<)I9U*nh&ID~>T<`Nk*ae}g zg+6~OA5VpZs>9gH__eeEK3FF@TP7pRC2AUHsx3*<w`A1pTSlSL7VhVE50LThp)p<) zcYU|xovh;B3gd%Tm=iQPO%pX|xuN;<tZHXB@DXH>^VAIhJx@=zAtvY`fqlMqg$Pkg z#Qpk+S`fSNc=+;mgvh*}`;J+ag`Ez9j1xfp2|@PCmAQ(=eUd(b(=YjXPjbrv(+k~X zv~Eh{L^2H-abPBVhcvZElDEgy1LOcm`{r}kDfPKD@3=p3RuP&b#cLzUawM77ye~N> z#T!_aDpPf9)m<p9Qs@|HygMwh?4BUV?Id$M`71K{?Hq8{nA!;m*t^Gk;vP6jpV6V1 z@jW!-*9y~b@{BXxjLXD~WZI`t?46L8R9{`FW{n{qqmtSEG9GVdS~FzPTW4|lWgRD` z$aH5tTFGKc%1W+$3<Z(6@90)_1Ds%ZYPqOn6G@0jp|u!millIktVhmFUXBE7j?5}h zE-dF}ygdztD-J<6*9K&;hRpHa)LSLhU(LPCnzt#FckgrVM?hXxNG_6F`fDw<(>=&1 zgvGREW_eD)14O<YGT;4qz8`n~gCS~{uzXx*KC+0Z7YQCarRLWrdz(pW&R5{}sUS*^ zEJm*|JggAOR2XYr__&t|vt<+I06A)-vaDy$`$bYvR8%6&T=uJ|SgEK^k6|MZ9Ph#W zytlaiS8*F_Nl}+|t8htoFO)X7q_4MRV6_DEt7M3^6suG^YF&y8D;;}Y=u}ZSl~g+W zt8|XFY~guHU|Z>HrLvWxvNgPL+1ts|^<QOMy=7FxrMuSUdtv1VMdgRR<;Sb#C%?*1 zS)ZMHlr0HAy9|5wv*_8CUioxU$?sp!0BjY1SD(@HRgi{PfNWI2(G^r{71UQ1P_{}s z<w^#dN~Z8i7SD>8y_Fnmm0VYqFt#cl<tjd#DuM7Sq2el$zAE@ymH1VaBwMw#a<!~Y zwS0KBVsW){U$yF5wc1rRf~`h_jg}j#=g_VChY`dbRr3G}nfC)4v(<{k*KnUwuQ^kG z-Kw#<qWW`6Wz$%D$EJn|Ml{V^N90%QzDDy0>WX9o-SH#ScBRo&uIG3}W@!wul`Qn| zBeu<~f3jAO&m_ff(cmG*4Ype}34QgUYxQhHBsxR&PuZR)gcEx}pQo`s4-J2ATLFCA z){t)V{AnYQ4FGO~5eJ4hvJ3-1Ac<EbL7{$>p^_l9auYh7vcr?OQyJ7|(}Yfj^e8vI zU~B5=YwB2Q<eh88C^rvYH4PUxby8Lve}cZfO*e23eXC6Wj*`~rhb&txU6g}|$S_bt z5=34>rs&tY7v6f9OcWVK6kb8LvDJFi*!s=0_1j_VXXO_MSFH!hFE#)#_QPMCPQ5t1 z)3)yk<U}&;=hOPs!?wa1iAZRNzO{jY$>4cc+URz<%60>}8(?HR${os~51p&1u;CF# zn{c8UB$&4F+zD<h@F5VxeL7WR2yYaTT5gAOe~0RNhw6P^HTKT8-)I`-7?_4YGIYdg zI?$&eH1Rx|!hy=j3mQPjsKz5TzoydT=6$OH>cpdBDRX1~Hguk}b>4!F%A}hzyNiXJ z#dASLbD;~OP6lJ<_rDKy``raT(`k0(8snCHz)t)jUi8$#;Dc<7Zxh|6WNY{kY5MP; z^c2d>Bl0xk-n=97%#z;Rl-?q6Z}v240kALEt2b?^H$$Z_->Xj@&{u^dYe6s!IiptZ zKp))qM)ki`1QGY>r>z3JtRBOiRD5JZ==fEjAfhg&C_ocEJ5e+MErpl{L#BTZR7?nb z!JDuT0g1`CI*Tg@`=t>GNgk9UX&#UYS2O5sIxyrXI^=+9;>EP<V7wivbRUbd41=b@ zH{PcJ`aklnv2(UR7Mk53F#Y-TCp%{0OmP1wB;5Q})|sG4RS@ZUS5=fiEU=5%8%hSJ z?D$RlIg^-o2n3MC@~L7QjIq>`6e7)7YGbT;DOT(l%O8Q27{J19N8~w107IlI*CE`i z^th<UM=5DfrAhxd42qNtegRTV_A6UYJd$F^jAadMtN6D%7-+$vx4kjcaH#pQjzRN) ztaOI}cLzio`Z&Zyu`rgD2x2oX5(pU2QXGCL8poAA42fYQ&xAdtBWAkCh8Njs2V6@x zlcAqnkIy(i^6`?4r6msE2npd}O&`F>ll%13jTS77+tU$8dq3h^#H1{QkZ!@}DukjR z3;oH$b&0Bwo{#Xf17HJS>0>PG7N9#~`eiAw^B4e>q)=9!?oXZWX`b$a&-4zAG=m{8 z^cl8A1>@;if6lWq8H*Of<CwIihlHRr76#39{eg(|=f~W!?5_>HsYn61{TbX^>6F*N zYbld{keM;_CvF&zN>Gzdyy+WLuYsVMpcbcBZmFVWm28XiZ*WM-r`Z?}g*W#8ov{-^ zXnH{zX*5mNpbdiSf#{nD{clL}(!_qZgSK%$XWq2GOuP0rXo<zQ`$o(`)5fFvCmSnY z;lS*D$a&{u+CBi5hiZnGYDDgO8jy)qm|47Kx1^Sa#cYhUwF9+P7>-U~f4u*Ola4=+ zYW@#=j(0F3&|qxsDC&ZP-wZO3Ly^S|3z!>>G2i2WbJE{9@0vK`?gJ#l2baA#-wD`_ zN8h&{b4g%@+<+s<(%{gmdYuYI0g@o@$7HytoGU|;E6w#h;bpX7I!tyduH)s?V6gER z3Gv@HT#h#;kZS(-b+>x+yW=XI67JVq>iv8W$lpv{=*(P>55Gt|pbd)E;#|^8TT-^2 z85mgaZ=N3TSsHr3sFz0hwsJ-<S^y=K)~+MY?e#VdZwEcIo#3!sVJ3lui?MrbY?z>V z=S|j(NN$u4zV|R(kv18|HI46n;r4=1r2*GK&O!mNMP}?4g1u2>POBDAK7ev&OW@E) z-Y=)_j}tDRk;nWj-wffLZ=P6kkV9FA+C__{SPJ2Yx9n)Z?8Z=~H)kw>PiD)2eeUS7 z=xz%C<MAys6}sgLATMlM>&X%yd`C@eXXMF_O!<<0>$=!6M;j2*IzYeJI8j^0!_2rH z1eerp-Fp9L)A|e5Jne@9|4mc(-8Io|3N@C%rVobYCh+<92{E15`kS7RWRDr@*@Qjo zCqu7IKFvRxU0>%Oz<yX`$J|R3{rX#Q%R~mhV7fp2=&fjWr;ljQ7YE@d?^Pa|Zh@jZ zd(fh{%imh~tbA1wh&JLSqs7cI5mS2l$MZ-g>~rr)@m}8#)_c6jF-#;bj>Y&)V_)xR ze_8Jv*x|LEZt>kfd|7OYoF3fRnMcoj01C?R><^vNJVo>D0*6UXcwG!XRewnwPG{3u zf2)+(S*Cv6ufvt+yOLBM;UZ2?q}6>o-v$9w@#IZT>T-QL;payKKGQ1lo7z6rN^96Q z-xEcUM1$k{pPq@UbeVEc_<LvHj1tM=+?frZM|4ls#dS@$T-tvHMSpD;?0>7gYdsx; z$3%=vNAWM1t8>wbAv@6UCDG;)6?^>7kl2x`-I?IS5#CoL4O<)rJ`7RA;@v~McBZ1? z7bje3YLSuC+db5#Bm+3V(>W$B`LD7OI?#E^=)uMFIwQKr9JH$&oCd7Sb$~NK=21)Q znOG?nSaFnd`n{|CJGJDc?Okjy=4j%WV=ArD_ZID+A(EB0Md1e<t>S0!hd<mhcBGFN zC;sek{=E#{J`Z^GJ>c28+p|lD2baTkOA#5Dy{VKEZ!R(E?5H;k1#g%!P0;VQ%*&Fk zlJ-Ad`u^xHU(d)mlgFR6tS{NNQ$G-*Gn!?1ov!8$X7Tjx_j7>A>;D)kJsMG+7W(iT z7ctWzKGP!JoBN_I<Z<__lua~VWnizwC&UXwLB~?TGNAqULW0{T<h}Ur@E?0@_x(!D z@nh}@QQXYq-EA)+M{gYRSp3MAN%a)60=<%&JwyEYS(ou6{KFal3@L#74osG4h>A<A z(0E%T9>S@n+7m?rC1#3BE1JL-shg0A!2zy7APA<u3<PH{RB6HB2yp~JEO9K6S6_oy z_>fPs1RiXvDmPy-k>4GqKCMgt(X<Xaj!>Dvy1&W6!EJ7?&=v~8n5>{KKt>k*0>jtk z+9loG2FRlxkPIm?3)Cs#GfNHcJ62HaF<d$4ah618nzT9y_}t=~BhKJ{%@7VW<UExp z?B<uL6hJziCl!D{;kZKYz0Q{^Xq^$L6>@K3du8MWOEf`td+W7`O*Jg{21Rd>>>Ya| ziF#5-zCWL1`LH!JegIL8|7ss=b<P$@ny28i^1{c%1-w!b*yF$BD#ub&aUAm@no%Kq z;CI9owTgz!@_fwKk)ZLZ+9qYdpLI&{6ssYT#49G}wo0Z(RtM&JftofAumnRI#5@M- z5*y4$JdZ%l%8--W87m7RMv37sl$lQ30Ey{srtZ;0x}zdP7vWA>LREXoNkh;#JgGXI zCZox>8NeHlbdr#{k938ZHB;j@{njP;{sy+|j&o3$Lu-R}rIUJv-#8x+)AEh987h33 zF_v4l)x>wG&_U4i)qSDU{@S$XC@|28X|A?aMW=>B2S6`6_vSq6=Xjxdh%~97%u%4b z(tYd&z1}&<DO<sWh|`?;wv=`dx3VsqR}I~h^RENPrwb_phoN)~6KSTO7<tUiornPP z#60pV4r%=I)+UjRB&*I@K^E50ReAX~j!IPd752>Qx$^6NM@;-{{@+c-Us3hFzE7J6 zrZR3Gr9b9rAFb9DuI>Q`+>gT+D@Yz@JNqsQ6lh7=#m>!#tOv#D!pYms4XB#dwXUXM zja1nbcXR0We!`^Xqv|+V)HW-2Arr9b(ri8X-cK2o+)lgscLi8o^4x-1-3rV$w{ARp zf{(}T`%itg5_7{iem}^M(oC*Gb9Cv-!z!c&$YE8g7ce$vUD>vjlxoGce4%W8hHR|J z70f+Q`M{CR8b%Q<spEq_Yzot7Hw3+_O#9mHE`t>8S*EzIa71-#`*EM|1!cI*dDL=F z3O=l749xd%V<nQTk2qtN%@{bJlDJV8Ffa4rI&e}<!R}(5_0}|#6LCRVfA=BJKue70 z`eZ)I04IapFzua<3T)Ud_p&iJiA~ri#s|osRS>HP&f6skM=|Y<i^^58&bJtYzl{sN zia39xbL+|XcW$qq{P>V?EArQV{j12|U&n7f{qtl0)ziO!$&dgNMFfaxE{aSG3Boh> zAV}rsqG?b_Vtz$+N{hJ|=20Z6T#q_+@LcQ-Vh3_<MGd<AIg}ukI=jJqJTziBjytLn zY{!!3ZaWmOFc3xc3ZTiqG0b&K)d3o(s5Q2+P5E|9LvA56p4X2%$tbYu*1{IeQ`w<p z+fy>8q6006JAkPT3ZflTye%z&BKjZ+lD{35V7BEz{1COneix|2JNk&&r*nsWx<^N? zJd;R+1qix`BGO=5$V72CbJHgg-GpanG94gp@GI%joB=cxGc5$<o=-lFNX&jl%un(K zsC%;wXjNB(016@RSX7|_FCspPSWj}R%n9ed9IXW{W})N^1*~!?i0MIi+G3=1G^+vB z)zhk@-&TIH2xtqC;OBz_c45$1Bu-M)=`|v7vE=;>5FjsG?<O)6Onq@*GvY^od`(9w zHN}J2z+ruPlBUARW&_DCy&{COnHA{U9I3WLZRKbV_tDBdjcmD`+En#$urZV7o9x;u zF>_(+NA4OhPm@mLKyLfq;t`gQ^9i<rcSbet+o+utr_oJUP)dEat@&=6uq&zsj?$KR z?(tfEE9aRSiK}#iwh_eN#Zo8PUNaX3EvfPX)}uF#bd#ZWQFpkHmfRiFN89U`_nwm{ z9U3!*WHvUQH<2qHSqys$eBjS8(d8`v8tiD+8!u$gjX-E5jpL0Hm)>$)8R}Zo(=8{S zb~Onr&#UKL4UT<;Xf4knP!W@NpYG8&0Os~@74#0T&bW%hdLh~tG3-g+claqjA8J7n zb9II_o*c>hWk{YJA|;3&ebU06hni-xwaeW)ljL?5^!JTl=T|oJySoQ~*U;`?Ou3Xw zOahy1irY|p(sa7_&8IoQTUrbQH}x^Iyp*YdC^$@g$Y@!LC9~P7pQI&So9Q;~xGO|J zj266c$C1}jY*+Wa_E5;z64N6>vG+JYX?R47sIKWc3SN7w{ZX1-Cch9ewl--ErAw=} zs!+VweyH9YESKZzp`vOtQI?ujigKm^+DO_z^p#o0o2T`Nifu0bFrE=_X=mXUkN@`A zITaSyISOJW!3D*{Zuhw>4x&vgF{J}pO;YsozYfLk#85r^VdX(3D{LD7C&!ZE*3_+i z+i!1^QJJjKQ$h((?j=VdUD5}}JGzADlHcgvkkgw0Qm0G0{9s*jNSCQ#GdF$-a^Bf% zm3QRcn=s>#lj<cIR*;$(N&8#fmGY5~KgdJGp}0g6c=t*u4<jop{ThmBNm+O%V3m}9 zxe&BV;-lvHHkn0CIA>3Pz`y9wt7n03?{rU3Y9ZOJ3+>(_CW3K#^hL?xY^i3gioZ(t zLMxMMKT~q5df64&ZC6&l5I?~Hb(9o|R6Zah?ED=J-Tb6oAc%XjE<<)UgO4*h$3jMq z`@34wGbZG7PA_xe5KvEk)OH%G6E$J`M*#{dFO2TL(?YAH*g#j#AJ03wU5qb}fyI4I zdPwr&)wB&J)M@W3A|&|Wd3ptT?L9Z4)y>y<c*+`x<$bEqD0~it?d0jtA1YkeQKp-V z+vKTh4SsX+Sx2r-$}oti1AWwOa4SK{oM&z|=9eQdi`MVv?g1;yzNv1*xGi(UC)KeE z)9iGAZCBN=^@>|JbJoOg!L<El<8RVs?iLS-erDS_iLEWmJFMLSr2Tokjg@)G@rGVx zhf~b%b-{2{bX#}ouAre2RngN}<`h4?Ta&PkW!0k-tBhB9CLx9wWgk*Y>qGW8lv~34 zJE*svvFq3!2djcNm;Jj4UQY{p{SP`9d2O+L<c#X<oET{f{^yFkE{`6jYS}XrMJPBJ z+O=CwokhGrXkQJts%^aZu=yevrSm@F5aD!j+J293e@yAVT`sOM>+};y*D2lTb9yV5 zy+4UAmb{+<nDm#PV$TnpjAYETX+ce}wMo8kIFHXcxb@fn=0TtQF@lW<Oig?r>IJCr z0Lg9!yxiQ$)SQNKOR2Uv)CU=tKHe7n$_jVVEq$NGFl<sDH^g<^TQ=k%V5VOW1}MoU z34U4~J`7dbuWW~LaDC#|X!(ZE;H^<J)8}&9l6>E;;d<6IJYOt+p>b7#K>=>GoelGF za1s6FB%0$w68^!lh;lcl(|@B;7#J4hfV)m{whWJa4buQwH`85{mQga5JI1J!qx_1g ze6Iu^QVM=}Tz{u?^jA9{FJhQ1huHb!&AS82t>=CEd}F5Y8~Vk4lQCL;=+t}OT2mET zX1Qb5&Ky37my^Z{<`aVoQnGie#vTyk?Ax^`uNW2EQ?00Q&gMAo)l66$llwf}!y(@T z7}a_o=QWDEiUMY?MrHj<C^ISYM-~<RB9s1^Q1L6FauOHhtb;?8Ef;Ythv~dk=bW4` z4IUkjSQ>w_J05v4{uEC<5ydbO%{LJvI}xijfij<nbDoF~oJc@TB<4;e)l87+RWf>3 zGADDhR&jImadT2ehe*a{MUb&cPr4l>xJl~epeh;I0IZ>v@+?t6mdRp{$;bmTCdbLN z(n_|o36AW_%;w2#TfJQK$-LC~{M^Z8=Q{>cJbb2AV&ObO**qfXD$*eyF=Y_nM;>v- z7=f6ow$UnH-PpDzK)Y5|$8MG9)}0q{eIZYMG4zz^6p!%Q9U<lE7Yx&F7i4IL={n*W zVI{(?LSEIWYLzNp73mozHlTL{uc9NbLJaRn1h1Uu3@*Z8baX}`d1j<^W;C^Wta)Yz zZZMh4ySP-1A2NSUDm6?iiF=27Ew7h92~Au<X`4gICs#q*&>JCl3O}}wyuGe~1Nhcu zXLo9-43&+%>B)%@3Bt$(l3pfo!(d8g`3-#y1HJ4*)57}3*^5fW#B1JnTC-=U*Iq@2 z>!XIz#x#M*goEY`NvSBQ%)t&*#rp83)#yeIjuiHbvU(!;e(!KIwzv5+;{aA{d4<vu z7_+8QEQKU@+fq2OB_sFgRX$=Fpy-yGiYYxCrEvi{Y1Qp;%)eXGK4%cL-G~i#k9W0# zmc%gc#-wb6NLe8ie|TCtCMB}TG3RXXCW^L77PTET`O99)69YyIDXx7PF1MZz6asLz zg&%5CZi>_t{kwa&t#4RzTi(3=Id41HI)zzhpAihM_0_rgkE|%LixaP9rrJBKz&JO& z_D1?9xRfp0X~E3dk(3r<vI93^xM?)$G0%KYh(oleGI3TemUM7RMqx+#mu{AfzO+mw z7n7OvnIa95VcxnGIZK-Gk4BT{XRX=LM2##7*cWnAL-IdonA471XOu5fCdk2WDVL8J zAuZq+EEMo)_WCBs;m5?YDn!la?0%)BUz7`(iLFerLv06d*XhKFXDBlL-i{zFxbw1x z0N`qOF+QJUceZFmEtUUZrjxQ!Amh@G?L2uR;ImeXWk)CHdFNJj!#P0V5x6B4uiQ<P zn&{qtcL;1nvTE+vLf@HU`SiB}`IBTik2yL$cs{-Ku!g9yRFvUrSqym$tUvAY6fQ1S zEb$!z_F3wMt)}^*b5>TGOfy(UlsJVG_gOM_bL3LmjmPy@Edn%CzW0WD&=%z}3(%s+ z3hN*JS)7uz1nb${vU{iQLlzSEZg)E+EtWAgOOf|mhN_9O0JkzzMeHY`Jbq%T?}|5# zmFP$&>5a4*je_9IM9eW7eOA56Nk~B=h?tlng(#y$ZI4nVf7h~Thj`Zg+{CPW<N_Oo zRMQspyyAW}w8i7Q;@>%1J5gKW-u0ZGjh{BQ3_EPFq%7N!PFx`o%}pEpl(FUX&$(Ge z0b>RT)aU(z&)Pd#08tM}O>$|y(PlV6rFVxWWtDtoL`K!an0QB(Q#6aDCvVL-d(11H z$-0q<zX26gz8@~ON2Lf7X5J4=U)?hQ;E^PSu!=oUtlOc@{K8Ulm}A8XWonm=5Ka81 zop>4~p-PoXDMsOylFQ|-__v}#dqjcrqVX#PnO69LX>v0whrzaiI_5qVbz*akosClr zlEU5wP=Q>Lv!hCXuTW&C)&u%{Oxc@M_+20U8T_j4JPo?EY#rC&Er%G-M#QjDyvSr| z>MQJwleKTZRZq9}-m?dly=v6^Zov#8%4tIsEgksT!OujV>Jz?jQr|I|M@AR}hNKL{ z={kP;&dd|Z7)oJ(lVSkJ3%xIdRq~Rjqs_3Upgx$BIvKjxVJulqVgeC(rz~!#a@&ro zbqj*aWc)<5-a=zGuOZZoUFUXwwOyhvACHKjQQl3LHBVGRHL`SS9w{YaDU9r;qTU`R z!+O9?v`Jw%&E>%>=S|zanI@|f=tP$GG^OGj?ZT<2$#zl?(kC(biXx1+Ki!~JV@sDq zg}P*uiy7Vi^r7(;bTKQh?^aLcHVMEb$IsQ7b1%7w#c|WcB24Z24n29%4*AKV|KN__ zZJU)cB;l#*<gXoXb1u-Y2k?9@?6=o9yvu_y&VbD(5<F7iaQh{yg3FciO=lK^YZX|G z<KR=fTk(Qhpaa);w%vV`#K7cu%yxQ|h5O@6&Xn@9o#+(z16v_fxuPwVS?Jb7`8tZC zoyD%L1?|tiw?9w1dj$CAP?I`uJHxvA6#oooFm^2dIv|4##nZjtp|N|fg5R5jy{im< zuNv>+l<QF(?77l&Q}rY@Ti)Xl={|UMr?xyZI5c{V6ZV0Rr_G|mpcUo?d;f?!ty#^p zsTJC|d?>HJPm`aWPEFeaumld==p&`=_az@Vc?Qoiwve%+K$H3|6H4B*+UQu--g_+4 zOF^BC=oD$*?%Y9coao-zv6k`o_}P?i@^oY(e#R*EHP_r_)?xy5Na|(=ytV?1|2$%J zR4MKWKzKsBku~K$<B{G}9*0-PJ8ZRF#RtpZu`I>95^bh$+2kl+4pD!}e11$R28Jq? zAWMGy@?j!BVKm~{lT=G7!tUZAd-mwtpI;o5pOmh6ocCI|x6PqXm;U&<Mgu6?G8s5( zttrubXB%JsxV{T6JihYFu=url5JmaMG1`WDAV!F8WTMyRHek!ocFS1$2Zdj^B29&l z6_Jh~gV`yTT+NQXkj(Pqygr&k?M9hP3YjeOgDzYQ+4Kofpb%fNzVnssrD0&X#}&Cu zW@v75p~WZ*{ZIMlvQ6tmdQhtGT}=9TIIsOPP6gzRm$zVFlhs+zaE!FX?TNjtg>~pC zeuYQplN+F6nm;RV>5FVxh#6U*!h90p>>&Y8xurjxp@rn99FrdkI<iXR8MXjTU!HVG z(bDfVxQs8*iYMJren+Vy*!y?x{OQ*D?zaFbZu*b*bb0F!VkHA9U(#0mhKp8$k8x{5 z^!A@RDqJ_l6m)_plLCI)LL(h$B`^iV8<fO~f{R|#{+0dreb})9DqZ((SU#k+qOpjP zF0pxLZrj^wHy`wY^P5h<GwD|I-l!x%>BGOe_6p{dav>EN&w-Ogc2mEC{S*L?1b<9Y zggm_Q!(S&P=wU*@!;p}KAAt`-0`LVt!k&c$A`--q->+5_hE!aM8A=WeY1v;FwyzZO zuOfaN?hfTWi+oV<^u>IXgmerIy<53x$|J_O0^fLrFSO^RB=&{zVTFB)w=QBZW?Y4o z8f9+TW%`%uY6kTNb#B|Axm4%b#(Fh|^d4S#gzbcd-MGk?E(_b;<|=tXT^hnw_Vihb z_~u}iyU+&qF)hdR<HN?6S54zr&F`*S_ODuhT)iOs-NyX8UGO(r;dh75?@r6#U2ea- zLw>(Z_}x?RySM&#-^<_q<G%;q{T|%^jrsBW71{L=^YyUcHMV~2CgS?$(2r5eh(P4^ z*xiT;oruY25#t#V(=V>a$FFf8BJfi`u4l(1MqY&nXM`-g_z|M;Wbwt1h5C>n<|j=L zfNx3-a2GRX6@S)V{ZU0fSuLpEur&BU^V&>^^h_^5fiq<|-B{&`ad-XI@YQThM#akh z6IH}()vCzN2i1o!tB;;Ve#ziH`C)Kqx%DmUuHci%H}Q)*E{Q@<#D5>(l^!%bQT!4& zQ}f`YJ9}z2DhQ@r5veMK`b(CmVNXiO=bQ;mL{UPdWcd!ZM_A|t8%=l*tAxxOB>iW4 z+Ie+T*)+ok5~<%4Aw?|F-k%9~TMMPFNgY1VfEb*ZqQnpYLk1|42?}s<02hieA>hQ; z(A|$Q`A1d&r*<Qkg<81|X>EKX!|-gQmbSKwl>FgLr{_Dg&(WpWMvwoeWm{p&73EG- zW?LVM(ZUuI9m_qtUj=Pj1!2D%Z7bgKKgf?Jdy%U8<IF%gRG#?|p1Cy?OU@|ZeE_Yh zNMaCE6Ps-RuAANB@X%W<`BIyR)YS2Wv*To{7#G9V(dchrjC861Y9h!)C1HK`8tm1j zVdwk09hy2w1OAwS$!Jz9$<3&*7l-dU9zNqzY=ddh8mDnF7Mn6TSW@$|)qFM9=5$dv zXDZab;UcnaxnyJwd{jU(2SZp`Am#EOn1c)NF$ABy?iwcN4!>k|uVU3I_GnPAvC(E; z4Zg3_(R;wA2OAW+qk{jUx?TA&q&={}Xo$*<#R_3X{vhx}uXC=Y2=}iV5(EQDp{b2+ zUS6K6XD@$&9hT$)G&aobhNPPBI30BDJuwyMq|(R+;py~&fxatOTolusUXp#lop?7u zPShC+VBs3x%aq6yPHN*WP_>eoOxt3?TgyCV;h}r?OFNlULO00=WfV1Q%^k$G6ICN~ z?&pGRI4k|A*<CxAgGSlP<U^BaMwhZy_}pjVwqeSW@=8|_?Y&a9E>9L4O;Q$QwuYRF z{qrg1)ohX$QtnT=Y#(X)Gk3U^n)P|T<Hr;EmHRS1>RTFYs2J@;7IX^x<i8GZutv?M z@7B2pf=$wt%keTh?|&UdP3K=V`KxnB`2|2Tk{F+9fJ~&G<2?tY#zh#X;YAvEbcKo+ zZ-<C9T3Qbob*g@OnJvXjv;%1he%}`b#D%_$693?0;5EMI-|en`l)v1Xe2QzjjgfU^ zW5;|CuDM^&x!B17QWsJ>oYrP#T{5?6fBwO#bCWUH()}H=kQJO|C)Xm(2R!=JV*Sa> z0wmkzdxu=^u);C#laQr$F_m}OA83!BGlRlcvzL%MkT9`L*G~Vi^cMIzXus@nYsr48 zM0E*JBG*Xyur|X;c|Rl8SNW*))k)uB$NE{H@5`4RI<G>T0*}z$wl0XbM=XoNjVLkw z0rOaX8vHrB;miZvYNce+{&m&s!}@qF4||@FR{LD#k5_{GawhX^KfCm|0}=~N0}~83 z<9)$OFa3T42cJAN2JF1L514obq-%>J?Yw_M9$OxC`q}5+$n*tE2TNXatlvX3mKpdW z3%^tw|J#wN^)@UE6;~TdK$V-)tU*H@=o=><!A<2PG{U;j7B4@*O&!oM!g<z~pmxm- zjTIW@rfW~Ut;$1}(=f^}-JWC=!NX83G$w4?o@_C|!_?6*Cg$IsVt>uUG9rYN%5G0} zRpn({Y{1E(+tYj_csX{2#+4S@(}M?ixy~ELZ=SVhL|*g4h=eE9>HJgcermAOu|in+ z@X}ddGlyk#ttRwL{i6=IMi^@zk*GBzNi1PQjLOeb_{`C{@)1<`MGBejshMzU_uQss z@{1;6aG+)`)TAXxM6ng33xufjB5XA(ikwBo#v(pa9c+P^JBOHwDq@8r=4r&{hHtRz z?hrpI()w8n$zxdB+7uYc#^yHO3)6!-Dt{_0j4~^Q1M9yk!-yXZRS|)=qJ$L@`c^|4 z9FmGorf0E$K(uqAQ)q>}8+qNYrFuW`0!=q`>jog{A%a91nqze~M><BML02_riLI)v za&Cy+9bxB`uULRwI{`6P09K2>=z=s_;IavH(%F9owFWhC{K)>(BF0Dw=!7@)F`X=_ z(l&ROoN?0dF)Huw5Ip^v6dhf4=PqT}O>wJ=CR-~6@z08MCG+Yg6X_ju8!Vp;<XqxK zc(Ov+74{@c*m`aVxefZ!e8K5j7_FJp6=QAe^Jv;<6ZGx@Ny$wh%AJ__lmvJx33)IB zU{8~C!cDm~e`V;1^DpDJ0~fq2)LW7eka^o;j*}h|i>l)h6?;9mBx14wcT+hk%Di|v z$kqS?HbzA=O+>}{r$$rx8OMoCL|J=MxF`=L(SP9kwaR$dQaXR9)cdtyPkr+8-L<g8 zF0SsLJXxcL%-GpohNFH+Vddb-lNXPBqn59lom(E$-wCYOADN4MVSxXr^4a~(kGzVD zprJ3nC9Z4Ca|RafMyQQE@cn|m>Cab*1?k2BP=FM^i=1a>iq)Zy4r`u?$hBVGa{vkV z43T2#V|WY%NyK^pddjC|9HZr#0Ny1ij|)oB{;ug-JV0&RP3Tfofb)X){SD3e4g;gg zZ<)@j0~~RAW|7kQ9dP<Kmg?s=c<I6s_?}cw?~JROOz~p#o?J)oYu`wj(jEA|(qivy zFh-{Qym|lTdGDJ@yi5g=*nxURFH4$14OP`VsoB5|co`;J^CC3C$TXaUwkL+YwQW*2 zAUv64&V^RvCYkMwI#?S(c7t4apg=Lc;?|75;~E_c3hVb)4c{3=&0In9^t70oDASe9 z7D47$L(98UqVHP9r$&D?$8=$Gs^=oTAoa6Lf$UG2*Uh}+Y1G{Y^_9u)1V&%B+fmQj zU{S)6+bIt&oY<~=KC>QuJbuvC$NwcYDBKb&NOb%#feF2-Ft0b8zgn^^Ns($Fd}Z{o zyGJ_X`41J9PqPtDx7D6xK?58X{48#%F6U(_Vr1+`c$$}LF^a$R75q@LpmA$TOmaMQ zhMr3Mr-Hh{ls+!=*@NB;HI2TsWlBqKv@Uo(HU{^y$&s&dAP-)X?QVkfUAX)t2sf!J zS#nWZh<ZDqQ+Y=Du8KatHgrtafi`aTwM9^#?VzvJQ~ZY+y|o~Y;3!7BxG_fYtrRd6 zz{CUvP5;uwENgH#>Gpyv(s_iE*ZI~|oUg-txRa|<NsVHnrG_ow9urow`@bNz!|9qr zBl?^C>RV>F*<<M!c=3%%UknYgW}Mi&vp!?(5dp1qsD|#{3lY7+Y0A6k1;O&j7q7UP z<>`4;RfE~;$!R6#$#bhRy%pPd(tBL-Eo86%lns65-dTLi;`Cj?=8f3XOleVWt0!u@ zVhl!?M}ZHH9*VZhDZzh?a~5>8M|~cpNZExH<uELVT&%lV&wO8fcFCQOpEQ=^VD@(a zvJx{h!7LdV)>FTanUhoXe9oU8fcQ$4XGT1r!Ecuirj7rMHn3-$4FKi^*D}ijqNh4< zM1R)C>lR+;Et1||c|$(w^DA{?GA@H7-Dr0-^y1m;iaU-hTbswotFSE^u4fQ_V8lHK zCF1g?9*Rqn1WPTW9~5I_(D_%~aY)IeU6aixz*z?rStu-}`1-?>?|Ku&dIyYo4Wl;< zk=lg@MhCU;ESz(9o!RX|p&Yk$sN%K5)W>^_)5VS}nMAh2d~}F##>C{&@f7Ou)>TkG zbx0-=6Xjb=A?<f>$C`vzzU6&1oj^ia0Z+8_psEQnyE^7s4<;C9mmj@X2E=J?YlSoF znE2Uc_ulhIls&pYmML1T-dq>LQ=^h9`k>)Z2|Ar8D>Ul{dMzA<au$g~u2jPEPS9XB zQWF{?I|?U7>WpnRa$rW&x6E(E8R>u0LA`~lr13z2QJAq|Mp}*(l;#-&X)A=I)xo&X zJyTJdrnQLIrh?a1Xr{5nrkRBFLKbR3#plGpr(}Uk=dT$KTH|YpWr9b|{+j05o745L z(JSY>Q&)4Rl~2FBMjyjxfU7Z><uhEaG2G-c+N&`-;k$cTa~IENOj>J9&2Pd~Yr<8_ zMM4Zv`42qj|L*=zoq_*r|115!%KyqKND<8MfA{~=((?b}f93zT|NTz|^8exBfA4=I z<89S&sdeQj9JZ8zf9tAHm()}Q`MX@#+<ixuV1B#NxdZ~djBB2q>Lkct*Q!8*_9Z~z zS^0nbF9F^XurNXU`sRBR7%V{z6SQw@V{Ug}^RfsZf%+0CE5Z2^<R$@W64)<6^_IID z5Lht*_7eDNd`ut?+ev`E%d?{d%Sq6<1ei&Hy#&-rV7~;nOMu4z*kRm2c{z%Ui-X{K z33QYIU<sa=fO)$cYY4s-hbbkHUV`T(pkM;&wYN7XSXvCal)!lZ0lfBs@sR{-nmUVZ zo}C>fC|UyJU0YrvcvS*TCGcT_+a-wIRvWF`eW-uTZ%l4$Ybik)6R_$PW|W{}30#+8 zbqPY5fLoWRMhPajyOn@dM+xqgpkITNo#hvDXR$Q|DqB~Vdcwv)5V!=B+udC=J3ByN zxfl&u0tDXM+`wT6_IMZwy!Rg!Ti2QHZ%)vu1Qu=48bLtNT!%FT>iaLkmw=Az9JOL% zPy|E$kMor`CpcdM@+ELxf|DgUUxKhEAm2>^4ua|>AYX!dCJ5j3aB~9nB@k(x8a&_E z;5IguAbE|tQ3UoIACDq{Ujp(a;N^dW@4xb1g6bvkUV_>tkY0l4C8*xIx*CG&O;1ma zjEo?t-tzKN0=exrQk}*9Lz3-n%>Tvq5(M$`R5L;N;&7dq?otdvaOdaF;%W#0nLv8G z(KQ5SJBypaVF=-HUY=Sacw~aVCU|Fp?ImDd0^lW3XoBG-C}{%SC2;J2M|TNOm%w%Z zLA!(tn5xv}g_+r@f!4;-S==arMHAfjEVh%tjn|ep2;BGzLvY8P%TqJmt(^qwi^Go2 zPR;!NztR2&?d$UYChcp<5Cwc?GTGeb@G6nMn%{LrvOPfpL$&5yi~eNDjH!_Hh(o)L zRcJnbveO%|Zy3vrak#yD_GzkIVG#DYuj637r-`n~=T|#cAr^Vgg$b+inC{IHbqlWy z^b%!`yo(ule(`8}OuIaOaG*b=QX(FIzdY>}{8eJtWzcVY&7XDc#jELO&(2f6uasc; zI(^63unK$DS?`}lLkj2EqalIs1uo<cU<SiUZeuX=UNiRCh2B^w<(J;>N!S6WmI12G zE(-LJrmmq%Pq5E|CFnElu8~*E7`Zj-4Ta_VsG)m;+-ahu!p=G~1Ef|y2OLXPUa`=e zVok9<#f`@xph9Wx1myXre7Y=)5(Jl{)f_vQy*#<h=!*LsSm{caH<&1B;zLaLBai(u zim_8R8PI%o^+OW7q4kn7DW@%jqRnNT^1VLZ21awV^*U@-tcbt7&~a_UWjAi0z<Zyc z+Z5G3aYwSgv#zXE7cv%sD&w(+?sIWP%btQo<0gsj&n;B-pfKh;?j+A1E!W-99C#ka z;nFRy!Y78KBX02;kH0!-cxak$=O^3Rm{~K<xm4G&+gRAZm`csSmDa~oLoLRa{G&4F zfwc3s%P14dvG;m*|8qdvAUUt5N{^;~*7M-on>|$4%E#*9vkM{$K8DQoQAWY|_ObII z#YU8#2E$0;8%E|PMu@ep6NdOpzZr1c9X3qUGYpq`9l5I75pm<Z*fWTDuyU_>fKi9X zMU#0~vVp{9eUZkPs@muVT{CE!T4e}L>1eEP*!9-RE6wtqndOr>j>R7IcONO}RkhdZ zYjSsu>g@eTk&t<1FZ`ftmY>1Q;Q;I>c-(vY$Uc%tO>sMZC$ER}5Wy3}#?BBK>i8}= zSbZqoh59|BAyV<>g1E}SWaVM6$aG*^ns~~hiuE7LF}?<w8K(OU0jYB1x<dtT<!6R< z7$`Q%f1eD#<5&427gZ6XFrbR<|5~wqW!tCqpkQq<H#!lUBUJc<W3!*Fgp0DoR2buB zM2W&oD-JJ=gU`&GXQ^JkY9o;v9YD+5pFIXj7_^f!B4BjC)*t-ElNA7#Uj)-5P*ODx z6!>2>vWwD?MQVSssh_TTJLe{CYo9PA_jFKo!qlyfpSq_H__(;;0kZb<WOPf$oKX`u zV5O3=YP&9E_$Q&7H&`m;Z)-(U%uZ}G*|&{wO_3yO)Np@U8csIwQ>SFws-$5Y>6_|F zhxJS4AWnj$P4Brv3(ShzL^S!WJ71@+XjmKFF(#mrO45fFRySlNQwx0@SX5cYBpcic zI0t}CbTDm3gb9f`aa9J}PbU}|E3+mR#1z`;a3h|Ul&!FW>181w_wSFw+qUBFKE<!W zs_$f`%%@=|iI#6jQEk!?N3!sdB#v-EvdlmgY4I@;Wx7<Vnvn)=&Q_rvU#1*|!7x;f ze4M*Ra>kKsr*JYAEJLdYLSAxng(0G4aG5dYNTQ;tWAJ0P^QkjgQqI*dBBv><qM1@O z>>SU*AhRS}wNJ%E3fe9;s*@tV;T%I*bgT&^x16!Ea$>3u9TwV08&V7mvnKeBvZX?a z9jQkup?N~KB3JQj3`6lGq48Wj?a6FV-WvK(bjS@L%dtAKx=_XEn`Bkg#R6%9O|Te- z)VXvghKl$ub^%IP2e8*1mQ$rC<t-oLt;0_sM;w3%1bCEMEC2nu$&+dGrf~D**F?}T z=SH*2HHofk05}_<NxAIMqjs^)c!Qsm#!-sGW+jF=<QMRsWqUHnyn=I=Ay(C~(h_sU z*!J+(@PA|PI{=zk*0$*-6hV<H7zjO-BqUTpY6KLdSO7(8=$%Lr3sotih=_ooRHaH) zX-W~rf(kZ@4GW40ii%z0zq0{@iszhrF6Vyt`+jC1+4s%v?Ck8!^OkAyrP5IhH{Tua z3|Qgv<#X@JqeddVvd2X0=;I5{JYrVKh}F8(8l!vFnM1vRlH^)qdJ7XD#aLvsOKZ(# zyWV!yn*BNDOr{&o3OJtSFY_If-6XQ%S~_Vl&f*XgJ~v9(=b?WmT?eOgn~fcv#L+;Z zGNzNrz3XMqa%{Y%6q@kRe*CCW^UVtSrP?X?#zH<cUDZx<jO*JaTwK6H=yq5?CX%Gw z=X}hs-=B+-(~-U)vXzBcWxJh@gYvnz<x>62n1&65jL$wO9RC>QI239(!oa}ua_}Xm z+e4<!m;j|Jp6Xi&3umru^>fuWfk_U{>6ytNkB?xmiEF!@1(bIY6bwwtLe?x6x{%Qk zeM2`SFx$>9mySO)q1u(C>mu8%d&%HxphM+d-K;BpEl+Z4D2biBNkcY_q7Dis0W48_ zj^NID)#PH5Z$&kY=IfqYX2G=Jq>TW3N1vDOOBEy~`?2nVEroW{frH1JJajp-y0`Fh zx=b)4@$65SWy9#rtr<riHRoa+keiE1ueFP|Ezn#0S>NeGaHX;#;#TQlvocbf;OeGV z5r?*0xgrYmZC|NK+11*KIkY`<$_=Z$BN|8WF%?+4VPbI?<#BEB5)M!6LyBt;jn+UD zemqt;DZ5W}F&n=Zw0X_Gj_=J)H;cjSwp>P)Hmn6Hu_IS3^C|YR%jVOhz*n0&jQPh( zxQnmbul}I1f}ippY04w_l6l7`!P7ZyoY@!z*SNY;JbpriVlM0`=e`vhcpqjLVsWDE z-iarI{uSpnUIv~a6PG(>C+FTjoseR-Q>g7BnUss<(0hK&I7XHcaa}IyYOc!(qf-o5 zpRHwCpH9EtP))O4rf`B`^eF?Xvdc&Ft)Mf8W4#H3#m!^p`l%N6LzbLDUB3HV-siWT z-pAD-9_14x*1e4-q_b(e!OI}G50kH=PBoOe23AJujNb2%*Nu&jD;;S5;>2^uP-GY5 z(R1tX8!>fm9bS2Yoog-8K*iZ&c;djy{jqoRBPb|u8F4qej{GZ^3r8>1YVYpvg&z3W z*vms>U*s8gC?Duwbqh;=CAnD6)$&2Cznf%kc7CK~d6dqJT8eefvFkToi`<(}mhYWB zEbxMXWHWZ_(Dw05Ne+7$pP$k47$ELvOD|pgR14vw$JuzNz14{%z^7{a$vx~#?~dy6 z9VdAr*@+vN1zlN(chFrB7?9w2xgy|DYj8)9|J@oPW(%RtHw<IxQEdCRGK$MOvIcfK zM=zD#d_)x*-WRe?SIqN`ChIX=&3M$4f~B(Rf?fv~?mWLr*O1}!WA3jjB2IQA`eOyG z;|S+YFcXbAjf-Pf_41pk#~OvjvKz-Geh^cy-+a}b*+k#}biRy7hgWq__`nLDv>syW zGjWEVol@i_UdB8pT%&zD!VZY@IJpXI2r4_{0`<eJww~Nj>&Qjf@5q62<jQaph>s=2 zvc>HWh)<}GPafvqThF-9BR;-3e)k1|+M{B_x(d53Lp-eLh9kL0&T@*&dIzWq7RB@A zsq?&v^$H30Ir21dY*7+Zn|Pd_R9=|Zr#B3Bm_+%T-s1TS`PMnb7Soe33Fa8~4h%zw z1>Kdk0+-c;PCVY-Et`DPBROba@|BOf6`m&F3QJz9%71WzMc>svYltZ^GQPqhIih;c z7+YKi9hlpTS?BcO<DS>*>~C%2p3Cwtk0gY5xXbEtChBs;e`H?P8W|Ora-x=*qd#SV z(7qa@eTa)G+#2jWa{HJy_{&=NY~yhyIdVC4BgTiBF4hW2$x#etOHvg|*p%eLym|M_ zJl~D)PgT}Pb=Sk*CNP>0F`;xhNZqW%!|M0ym<>%hhzV(?;R5>4(~QE?Od8U3h4`g} zT)9bH<`@p`!UI<CST=cbIm)G5+oro`rn@v8@S^8PXBO-nXV{h-v_<?Nzs9PNgo8nO z2g&^h12i)9#W$AeriVCX#G7R7?$6kxA@CtIW3NUgU)%EZ@XU<N%&dmY?EcKdpE7fW zvhp>u3gxmqOgKHlvx*wB%KNj9f66-O$yHpERb_kVWcZ;<xkDuhV771lMV^uwvwbgQ zW?yQ^?&{CJ+Miu3bm*E!&Q05#+u=EF30disgq*wmIrlH-3|w6PK;!U}PdR}^o}nLS z`@YdQGGTk<u}LPF?b~qVYyS}nJlmH+GndH@%=Tr=%0)EJGTYZ!kCoiYpfFA+_8FQd zODAT^{UnmHVKC3lkwtPOuZ@TE+Bm1AY5rylR>?#yWz&4+QtpxQe2r3Wr9lQwP436z z`OEDJi6aaa7Occ9uE*mASH`(7@f2OcEO^_^x%xA=vKRN$NJjPCBDvCn<?;)*wB|`R za$b()8j8&G+QK7zDQ_tvU*a>JWEO|aXF9pY`~}_l(TfV6U<wG0hRVA6(TvQLO{(g# zh{D(b$7_QHyAg$ZGz*gL7#|N7yrL_zl2>16R}$^Qy`i-zJ1c)zDYrr{y{jp=n;o}j z<Iz%i9<f1swMcd~p0a90Sq+%v%YAYSqh?l_S^(3DMvf+)vevA!vzN*`gv)E}%1<Vi zK~`IXY1t*yGW%}kvc}R1Q|OY4^Owp`j*uF*FkKKXJ0oBIFsppXtNe*w+2h9Y=EmdK zOv~;jR@|_wC><z0J;Dw3254e-2&*xEwPR7SU}3mZDZHMJ*@AI%039n2YhV>CR}<?2 z3-+bs^f|ip{CC)M0$4+H=@5||^(oBAL(B;&Y%&21hX>iA^$D_a+0D8sO!TcR^gMJz zm|7todQ}nnJS3ZD8S}C-W`=Qg1|*#?rp`B#iI~I`n^G6c!`Ly%amKNV?=1Hy&%%Q9 zyv4zb^dB!=>%FjuMevylGFRG(c>tQ&yyNpoC7sre(MX2ANG`^eRoZE+78Z<%%T=5l z)eE(%`Ms-!%c_^Ws1`@pNO{-DmDMP|sKH)l!EdNlE34HML1=T->8-3Yil{SP$=1Qc zXwGp$5_tkiPc9jbmT-1gE=EeUi3_)hDbhDqT{ma6jNuTE=5FH@NJ$J}2_*02cCR-j zbt}0S=ZfjFRwq_|wqxlFI5|0Tl1Zzo&#@}Jv}&^m>q3$0MOxLNl*nqKrfSg_)%BQq z$q0rWku^KInH26YVJ@>IuB=rPq1W`TO}Si~DpIGvp@A6DP>bP!Tu!ff7O>o*7aivv zX~kE)_w09)tz#3cN>k=!@G^aC9sSn(biH@Eek1QP9@(m$-uhx}TU(u9C^2M<axSrW zUDt+d$2mptl-lr|IWNd|Oq^vE=W*{`Vc|02W0id=m6|+N0e33>wYZphIN3Q)bBa`l zZ#dm|=hTkNr=`A}j@?kd>r2hBRxNL9z3QDa>R-;h)@qnYsxuWy|J2kn`K3k7l0}41 z=0p<FHB9qi6!(=LeB5>Np<2JS`tFBZc!b!<b!APR?VNdK-dnHBQ{3;GV|L5N1k$@N z6CsUvB9`om3zUu;)gd3%<kdURy06n?R47k+r*GFz|78-5FEm&8;+gcs<+9sb%5v?I z&1%Rh_YExPUR=@`J$YV>u`hs|VI|kL6ppssD(;9=VP&Tx4xJvjT%EvEvubON+#ROJ zN%g}b=hk#VQ}__qmmuC~wdh6EZOpF2=E}$zVxcO0Btp9Ca(na9rE+)M*XHB9d(jL| z5=N)w1sqYu^ns(RgiYUey0FQ=-);JGk!S}Gu7gKZpv$X!m-yRN(mEHAscM3$D|o|} zT`lyu+FCEaOQpO1P&YK}XK78ekImIG)M|4pLD8-w<ZkBbfs+?DP?|j)&(d>Lolv;U zcO$>&#bu-BQ+$!tXS=GSv}&Z#XC9Za(|02*?si$-t?wykPT6q9AmU1TS>2tj`iBOu z400}%U?V?m)(Bq}P<CB@b*MVYqC4=7m~-ES&-^=%UJe`ECz;?A^@(8zV_;KW7w7$G zocHP5jzPVF9*q@Nf*;oLuH}+o+I8zh*{zhjx0qkwa$(w*w~FWa8y10aK|jS6HAlsJ z0_iPyTG=%#Dut^mv#)DLUa$Uol4T_w6XwSCFPCp!KGlDy;~ZzrvU?pnc<5FyyZOB7 z%pNqG@s(Orv%0>GEi3P+9>xY~q%cZ4T^r67dafptR3Ww-dyO%#F<h~|lENs+5TWkO zu!AF;%yjx*$fRZ!Q%DfgYB%j}{;_*c?~&ci&fna2LhT%~GnwI{S)cL+;WG^PioeK@ zRy0Nl#@CE9r-<HT)vU;fWG9p}mS>+;-^$(jrC+fkP-hjLnBzbMUG*)FF7+$bf@f~T zBz5hmVnFlUth2vEoUGr6Zb`dyXX7Z7Io&N5t-DKKw+454cf0$`Pu#v=b1%#+;8oQ2 zx?*Mb<oo<r+P&@X`z6ad1Sov#+SOy(`&nVd_=hI~iabmgc`$pLaQ7cS;tEu~pCrz> zg#8M6HAC;2eNXC&(dLJ9M<cn;zTnQ8?C01%m}}26>S(}(Wa@6JTCnnTi5Wem_r)n8 zv6>j~8XfyHX1W|#H{Lv{$aej1t&UcU%;77s0mAoIF)(82lquq_q859)WNrsYCyZY3 zT_Ic^cAtfF$syE5Iwk2zmXVKLUP(SY@ArxA<Rmvf4G#3_c1!LpZtwg$#^Vztt-F3n z*v8JBtDHy7oAM4vsfS9NId=w@Uu)fdX>v?t#r7xc2qv%Xj2$nYel8vKeL2RW^Gp)S z#H-Z7RrV}m`$NIwbWtM1OU%zG9;$zyG)z1_tX+0)@Ad`@dr>~?=i++znCZQq^S0-% z+rel_S8<!DoV&_2RKs^-eC<LnH}=5YxYrC$<5qs-%H2f%Yhm$QU+22Cv3A|(qNj{A ziI0ysdyQ}7VY`O)uiDO<M^>$Q!yNcnqbS#;m5#3<S#t2Kq%N0^16GE4;)X7kMQea5 z($}<Q0`Iy$v}hv4h{5dIgcaXA{mK<9h9<1``daN>Q}_0r<#H{n<?qdRtdWtLU_o;~ z(yjE)Vlr`@aCe|Hdhj;*T7%w>31;1aEfeqRf+xanevm1$aDVkKJcTto;v+N90y_r- z<9B9XzI;sH@gAD<i#Yc&tn$52<<WK|=f$k!Pe(poH63_3QhsIN)9{w^;vI}}fuGAC zd{#qpv!r~gS^lNY;mgS#U+Ry1X>9q@{NPL3z){i9dE%P+Qg+8eO}Q0Dz~Um?O}SMm zjbF7d6(|Sv?=I!mH!WP@RcJC&eEUJ6(s-ejW|4AA$t$G#hN>d<0E)v%ffDj)k=Id= zkuMyGul|}}gG{-}iTU!0U&DntBQAZVv)r|$L2<*TX2CEXL|P2%Exo|kJf_LQ%7;sm zGI&wvH8*1#1F*+g&WFBA%UKgr)!!WIrjT_fnvr#tiW@)CH7|-eDe7|i0kq7@VT*jD zWHF<xHP5Y%n&s%k39lVlQ@XZd)48m*FV-EmKe8n^*g!$ETJ1tQVZ(c$ff}a6Ri^`) z#x^#ng^Xn#3VnI6d8<Fh<>>lx$9DV1Fr#c6Xtr;%=<=OQY;o%rw4X1{X<GMoquM!D z4vJ2+nv&CL@1En#tLzA#XZPE;>=Xq_o)C#}M4s$=Db(y<AH4qPhk%Fe?p?ALxi(dd ztnWLb&K$cwv0$mDW$XQt{n1(ozQOhbMqASj6U?*6pFF>IM0)HH6I<&}GnUUn5_k6^ zja!h4c1&MctGMXz=rXXQRZaZ%n>oj~2{@#pqL@PZOh&o*EW%>Ye05tMwsSUYd3d&8 zp@XM}TiNhoT%M9-sR6HV43;Pqc;kxtgGeo<6{5kbj%PS$#VBb$Fa1K-G^*K9(DbTZ z<XZDvip8r1W_V9!olhgR3tA?b*rn1|pWzH>WmvqmAkwVdv0IN#s<0dF#-hh6iiVaP zOcY#va7$E1UU|G}n;A=<b-J2Tq_w%GT7zS;nyQnVyxZNpu`BA&%HNt7y&)%Nay&9z zaZL>&DtT5wN!BGLPyYaR?OOW+R5>b0fl<VVoV#|{<E?j+*ZH``%N%yx?(>pvSSRE7 zLW$+ZR!x$p85cCaH4ngP%ifDv&HL$i*PAnnO`{9CFzkAdx|jrfx(~ALf04^rdDCQ# zEXS^q09iLx^1*ZYcRkk>o4F<&Eauz$$hw<jq8aI+r&jkUTB}92UXDZ2XxIHh)2;PJ z2^G%kEfc;RAZ<!k=0`XrIV?fs;1ZpQt8L>wCwlg%NlJIGbw6s0)wDpOSJ=8&yt{1^ zK|#mde*F0YO5ka9-0?n_3hR)5shqg?{S!9}()rQV=agF}>u)_;ME7k`K$qF)VFNwy zOWLO*$*1KD?9<e^7RRZmne-sYN__{58$`@(tgV^jY9Hn^q%%KQy?4NMO>3Z%sZ$=` z4)O62v+cIBe8NT#qs^Y(#Rul$jab)~4d@^y>Yp9|^0Il?Km<vh)iP6-jJ$P7)>`h^ z#)+)ZXQF79pr$r=yX42=&xFX@?x)yxgwtI>qW4|xlSp23yGd%1=NntuUhn8jAIdv$ z-jgqHNTm6l=68fDm`3;NpnI#ixz%|hRED%{`Q_bgWnX24qzVZnF4iGOaoI<P>_H(O zQo3Kq;RF?;YApH$;^|xS1}j~dv~mwG<HugkD+m~qU||y>=}p=fZ4bHRaAu-&`9bzl zS-P)lc0x;&kY(8{=*x2``bM12&GZ<_7xp}H8GNpidJ0XK4pg`W><R8pOy~@hP)%FG z@Kj^9@y*Qp`_k$PU#G~}Svs#iP_=gHYxYMvPhVW?y(x3@{po9~UmsfP{ANO0^Bx!G z(7@6$3p$z5lyXk7n+KcC-PBq+8C=)Ny5`H;G+oFnjUx6N?tDX3-)Ff;NM`>zl(In0 zR_xV1SwW6#pN5@X{U|Ub{c*uk`sdubPpf4|0(x6r_3CXALmWygZ!+hmRExjAe$eoF zG|7*GMCXaFNIYM1fP1S7qmN`K7n8@;;8??Q7pG_3##Rf%s$>qk=gFA*u<0H+u6o4B z(`@-h>w*l<C&w7E#mT2H>2V9z7Ef-YXC?*gN;LnZo^F&XE5Y&PnX2Kbi1a8|rqeeb zBIVLJYz+95zAnL*@2k9pz3L(sQOvqD^P;(`SJa2Ez3YZQS#BNKAM$nNSrT2NNnQYt zoWzD41!s8<_S;rRTRJ?I`{|p5$5*X5WAfC`b#t~S-yr+hD(5BdF@;`Mxm#{q>k2f< zRV?o~#$FM*ck3gABe=u0%PH*EeeFX!YVxvT+v1O{=gyb4<}ElN6jT!bzP}9`%H&#T zb#rjH1f@QvpDZilyYHzZyWW<I3q!f>g`U!}w=O?ETCv`?tZCqLUw!&$q0oVHPTn<q z4I?GipS-081BQFE(@(nTUbv!^V%%)sW~yF6ddhcXaHk_Xn!EdK{yy0Nvtv;!O3kBF z>+bGf8x%xfnCR#4Z7tPdfJPv?=4@DMjEST#ez!WVaQ&pPTe99ptEL^+d<EQ6m0a6O z9e3VDyR$>{3x_?A@+`iDY!%TkJ8{`X*)i=YpLfHa?Z=z879qAbeK|TxIc0hG-OC+q zFO*(4zBH?Lv2Vl6^y{A~K8l~z-)Yw5l;spdKin60E#qQG9>Z8d7n3l-SeLF)!mMP~ zm3-^!i{+GJ)#ph!@)xeFTzuxS8?wP;qf+n!COSIZ^?JPde$Gm<CSh@D4ih6h`xC4p z7h*p#Gk^3>*c6|(X!1a9jH0RQde;&(N8Bamu$aXAm_9-iN0(^L&9&)E++;qWFRjq1 zZQ@K!!>RWNS#f8!C|!2aKP$`5iKHhW4U;`r8JuIzAXf6*-(<@<(ZhtK>)%xEX&$Pn ze~2=HPQBr2)uMkRUn!Ej+Dx^^$oLj-^yYk%))AWz;<swe&FQbFs|LmwbhogCRK?}U zyCPBg$@d8b)}ni#EF0*NOd-=_xWu2IAGme8lzd`;!H8GqmLBhX)v~f}w>KTQbGL5i z=B1DJ`Fc?bY#T2=e^)i6?Z2h>4!WJ^sKZwY{%uKa57b)gJU(g*Mpd#XetR0~NXZX~ zQEOIsgo!*Yr4yLA{_Z1z&am@@Gl6?T?>=728h*h@Cn)vc-6t|D!Y{2o6O?}H?vV1X z@UoCMbXm(C={a&g=?0{5Wc9it@e7EU!>^%{jD;v@Flsj|j=Z_pWf;+Ke4`V)CoiUB zWPK<--$=kapWTd-S1|0RPqBO5A}2)+)OKmOroPX@YfEhDK5M?EhSP_M`Dtip(>c=B z=&U<So-GhGRtlrg9of$~+@@~ab1{${EyyJP#DdL$=S}o3M^|ya>I+1C>b^Z5hb1K0 zznH9Xejn3HF_dP;o>k$G=G7jKm3cDGZt+GZV&K{xMzMN&>8JS-?HNP`v)+~VAIDxr zFL*HcjrfuD-fC;z`1S|Q{$^uE%O|-WQ1GQ1Nv~oKA5>2;`gq^L>`n5kfHxe{pKq?% z@j1q08=3Bkx^A%I*KPZktkQa=vGPjAzWRb3H>}CBA&mv4S*h<WViyY*1R>%kKgugQ zrl`m*Vir0WXtJO6;t6)kmOdme(rWjj2A-AG2RnOeI5<z<+0U^taJfkMa=vcf&oR<> z61f!;t6dvnC@&;r^!C0>S@^+%X<Qto*q>GDrOSj}Bp%ypq_;ol;t7VT)&vgT@cn!S zC45I)(~i>RsK2K0S@15lqF27ht9OHeqgtKY$RgGiAusv41gBo+SR6AT%wHs)>fL_S z$1B~P{<!S(!|&;iF^<YB(YFio91OGI+04-)?#d&v^3Vyr-I?vh#f~h@{VBxeMd{9l zDRTHtBWNDU%Dq@bB!eE;lcnL7LSF-g_R8fc-K|>JlPqMxWQRO!_q;@uUf8WHrXcXJ za|sgyxo|Arz%BD^F}v;(&Vmnp)nYf(D;}mJm!WIr4^`Wq*Jp|}G!WRV$7zQ{aD?;j zkwZ`t_J?>X94*dBXTTTa3NAl;evP<j0Y;QVFGrwCq**TUw)oE8sH$|~x{Su0{TD^K z@J0?J(N*<Pk`%9V>(<1&3LeJAr)bipBg3&pJrzeEp7WJhP*Qys(<m&4MjiKTRUL?B z6E8!Nk8Ij^q{E_P#ZBdF&okaK<nuR5+NK)rY+Q6<-;$`(>|O2Isjh;?Uzen1XD(`7 zy0{dz$}8_1eWBn&z8$HeA_zXa0Rho1jAC!Z-MTxsjf!dxL(}u1>BeUtI_(+?k5muP zE2=rSp-@b+TI@khwZp~*uXu^tUBVUgHOTBk$&&QUo+#&WgXJ1!DcR?JI#wL0W?2wa z_I007lUE5}U}3jud3-c-6;5GmN8x$R72X~PTgVD+g_)M61<~}P>$eoIkx%q<mAbs8 z$maSYJ4D8Y=rqd40jX2_E^Wf?M#;xmI>wZB@aD=@i<w+N+Ljh&irr5&h-YdQd2;xn zu*_q5;$$}i#j&-CymSwF>E6Q2IFm9%!^{<sh9oDbrN3f}Mp2Zf$+n9rk$Hvb;f7oK zB_k1LS<6h^y06|7l8ca+yJ}cMTvI}k?<$Ne84yp*LS$rmT|sWSGMK<@CwMlJ%o;W> z<fFdpaU`BKEV?inE72oSTw48Yzwi?B0?`}tK?4SU?9BBO`)zMr_~3cuNrri4F7a;o zss*2A`2tumsIJNWlnc*^{SWiDiZ59mv??!hmF__Cd9NM;`^8wT>{haXkhay9iGz8D z2aMWq`NQRt?38oeR<f_Zu6A%{^{GmFOt)u{ZcneBMc+WCT<LOwvx@A5g%4uoOHz$r zmdJmMv`)gyP%O-!y@^u^S6Y72dRV_^B;P#0UiR&hyw1|AUkXcJ;u0<OkPObUTw9=8 zl5K8Ru6G5!mKQVnM%f~jnaLZ;{J5MYsgpHElHL2@>XNF-HJO|@s?eemNIC|dn8mgY zJhsnE?1f5i%QJ>pU>U~gR5u#qbGiwldrJ}*%G=VHU6j|jmtI+8t>s&wEmfvt#%ajF z=@v%6c*s&0k@LPhS1%xYr8A~ucjc$gl@~RHLduns-}G#Ke%_b4YE7m}lDLX&lgu)M zD4A_B+FFS^l%(2Sh|bGFmg~lfB#jw7wleylqKE}7L~p#kb)Dyhm}Tk*)!10nkWp5T z@#~xV){mdmX|=I_Yh!(xu-^NG?OVKe0ZWZu?MBogwXm4bm4pp92n&t%!`>1)ba`D{ zQ>1<L^{V+cUpQtsreev<yLq^`^LcOXw(JGrF*>FyM>Zdf{cKybxUaM*SrVg~m1AqX zvMxVJb#Vc{f~4nNds1>j)qcNia&pQt;|&#Nhn5X5CYRxZlkV7Rp}iNWW%{1ln50(r z?oLZGVynpVC#W;87bQh%skaTQ?OL?F$2j?ow|YF|-HSyVQ@=!AI(N70*4-;Zcdx#? zdyV;C_u_j!Quip=3HNT~m}+J;WHmKpqju%I&`|l<z{;jsgw{O2QZpaLdNjE4*qx%{ zLye_p8_RBJrnIu{qf1EDWhV(X)u0n%`WQ}}jT-iCN{-ZOh|zAW)w(%!KmAVA8E^Ym z6l+`3y?(C7jwbskk7X0d%{z0K#iy`!HN{`K(Y)sZn?P|?poiv=Q9=)zy)G#svoAW! zMq9u`do=m<*w}q$qQ?8nyW7T3H})kwF{poKkc6soY;rtvW?bz}#Kv3d%USrHQk%3N zygmJ*$+5?nm0$f~PyJw9t0VV1u9o35JHI&IR^QEg+i73znZdx8haTr1A@|@cPUWnW zAJ#~J(VZ{klP+9NIa@bzc$tC{)0);w29c-oDGcJViqf&{Yiy-_7Ij?E<;qN4lyC{B z<I*qqxN}ff=1Z&0%E!>;S}}fXuID+;`^lTI!feThM?5Y_Iyu+Yb<@?IeJD(PoNbMK zEBsU<Md9fqC8dKN8}!xW_)IkreC_-D@`(PDX(k$c%cD$7J;a5=^;fDQR-H);UzI5T zwS0BLqBTMXtTYa&r#~*uWxVU%@_|E`Bp+k*kt<a8=wgLPZPgn(+s_`|XHfk7=$Oz| zFQ?0opPcpklr`b$s`%8|ed8j-QN+rv0;|$4Zpkw=zk4ufqBd`^ER;RtDE?B>N227~ z#K7IPL3a^;_XQJd4Hr`S4OxTs6?`;2x2nrBHnXjK=(xw?hQ56^xd)`q$~dk&Dr&;* ze6oe@1h@L#d<utoo$G>E)miT5u6X?;n2XMmVXi)hZGE3625&voZQ03aA{u)05aUA; z_Aesqx37;gRn9}kSm+}&nni+hE}C7+zT%W2+_<0YDsnqbpx)NA(P_o4@D&*$Io*~+ z4T$2814l%M+M}K(D(TW=mbE!Ec#B$C<Yh&%g-71amUcW)eg931LWca3C9SX8_&>>_ z-n{mhutlvn^Hk8IfURex>B%E}HLLjF91-x6qIWKaI-%4bcd%Z+hPZSv4O()xbjd;I zC82cUyrx|Tcs(ixP1>H0QpzaV?VGN5$(su6U+O_f9wlE|dfn@Iu4yHa(bX!y>ZRQM z)}8j+!bPUu%f&7y`n?!rDsYUo4$Ajlx~!n`^KkRYr;03=EqhQJ&O%n#S6N?2U6V`{ zwigYMl4L-S#Rd7C-Y0qMc;SBf^?R(YwePz*S@P0i0MX_8O3S(D#bMF4h|G2QUbc=Z zwm7p5?NV%ZS9<Rw&ZclYeqQVNl8a9_fMpva15b3J1i#D0<%APrZ$7q&a1a~B&T|`~ z=-FzT#hy1U<9VKTEPOy96*16|Rj_%T|Lu#0(F4f>)z3Sh3KX8nO(^wBl3(5UxrZWA z67)XtL-R4d<t2+NS5xGLOqWm+zmfyjoRIs*7f_1XZST>7CGB!Nzq9}L;tdB5JmHDn zG<m7Y@P^Q$b$;#RoT4(^@7jV##;dH3tq5U|i9EufZpl-fCQ+oYWgj|H+o!#wOp@Rp zqvw87xK6kwvdwu7+Z%<gh6-Qlk>?p!>7NKbS=xoFP>{0cmA+LjQ?W+w-Gu4E@}7Yw zvPTjX7AFNowk&CQyyn_1Hn|&i_@_H1gEtmiobt1NUtB7k;!Gl(XKr@pdFHH>qqKZ1 zapk+0d$3hzW5><k9a>G=`8w6)t!9Xkt+Jbka%Lk6Z4qRfq8ui-KC`3p^FZZ<o7`BC ze>o;(LO;Z8=;o`S5KZ>AeL}ZBQ}V0Ss;kzlU2D~eSg-vq2<a^=ah7Q;xnh)Y)4R1_ ztzR7byvJ^b_siFZ6D7|ZZfaey<E`V~+fM6uhxyioGq(k#G0R`(K|h|z8>(3_S{rGG zz}7CoMO;^j7*Jirry8Mxe1J%1)!VZJxA&TgUeEf1!CryS>-BDgmLvO=bxs<)oFuK> zQ-6gqz&k)({ls3b6J}Z`)_k!kJ!o5ggb?_+FX@1i#Y#2ui_g`oPh};ioDuz8$Eq5g zd@|1dbKzdq%}F+{1^qfa4C1o;ZBci8v^H#vh&jEBP`kE{0jbt-;!Z>Io!SxA)>DLa z8x}#+gfBe6IE{CnGuz0ex-k+(SKl{CiK^vzQ{dQ1Qopd4>*9g0H&)!ctcbX^V)AzA zWbdxYzJrtf$0i3(P2TC8yxTi@@7d)250ejADT4yzOH=6BZJ2KhY_E8lH{?!v8cKP# zi!ywW^86TO<P_yaCuQ{1cJC?{u}74by_9i*Z?9#(y;1x2cEz`e_21sPe|sPL?Wud> ztApP@9sBnA)VD94-@f*Kn|$_-@&Q_Zjm?{Zb)kYsa-Xg<f(z#}JTBf5zfi`i+~c*x z`Q5^5L8HU3Q5TYGh^Y!1ILS+CwAsGy*UN=cGcIOphQ1kn{#K?tSAW++MXw3j>xHHV zalRuHayN^uj#*WBy_3IPZg(nZj56{L-B;z@nX2ghUSVLyY~N?sE4)7_-EZFV;mz2K z(BY#k(X5M<d_GoPGh4@i@*Dk#eR9F2!s3314en`ImRj)Bl8m6CtNAPTD_uE-8$P#g zRjuFH=M(+6s@!iJ_Z1l#y3-i?_NBJf$ybBzyY^l+`>OutNo*9_|K-<D8y|Ncv#u0N z5ARtjY0Ovka#Abon(3D%H;@lke4H35ak)lu&S=zGxUOxWn(=n&yU!oqrf*97irrBB zmLY0{sh)8MyZo?Sgh9^-1bbz3{#DEd9=3QwXKM{|m#E)U2aBqS*X`WQ6ME5_Dx3vv z92;aOOt*_&Qh)5k#jtoa8-+{PpglpRlrf%#p8<E7bDPaO282J;yXe$qSh-sw$`|6= zH>|!MRz$M(^0Zwo#mCFVJHprMvQ(Ip@d^LgzDFHcEI+feN!P~KE{-m`bDRglMhe}n zJB;Nx5Fv=gvNi#3=Q465tiq)Vh!=YpW!=je#WQ7>)IP%Qm&GOQBNF1<kJM~rkwGNo zTNBNT_K>|+8>^ZuOikRghD9@Sr~G9Rlh04&O-i$vyiAJD*jXLVd-M6w>V`e!QR4>g zl~%`-7)aTb$29NAp49bKiCvIsAugTyiYs`P!tE~HB@RCZx9cJ?xhlsLhKqOZaXx<{ ziCZP=dC9ZWpDwk(LSElv*hN8haYpYi7HHPGP?_Xw#e#_G(zqaiYOoGyZ9&K%Yg@Ak zQ=P`UdSkW1!e{6z1%VcCnKl+wS0CxirEvOlOI5zU>il$H{xZk9W|2Om>6unR>|$2p zE9d&g7q7ZB-M>tjm+^?l@hXNnIWOH->>`J_-RI1u_;Q&DUx+}i{AFe<hTcKu+q-Xy zmX)8J3~46SB?UFzJM9#gx^*v^{!;lRtMklPSkg~jm+q10z{a(ht*Y29U|#<)lBl#Y zwA(NBlSurldulah`H9@^0%_(_&t>e-lxmSxVr~emdty@L(|RR2yqRr7t>(?s-y)3^ z9gCye_@#;Js}wrMc`i%V^<AgPS)R9T<5U@T<;>o3er#pYd!{4y*B_ytvoFz<Rk^X{ z(m_^!tmc@tw~Xd+lJXA6m&KJ6FSK?oe=)SBeO2n=M`|r=huF8pGB~u6hq$%4@;O6N zuY4osYcoohhMvB$lhY~%q2<BIb3UStQ~Kc^p^j+Qf{b(g-3EN47mlxy9lkn1j?Ssi z^V%6B82fe00a<#6T|y5Q-81HvO}fQ%e9g;`4+p)5&Ze^Y#YdW^`2>-)V{OZ$^<CGF z8Ml{8-8S1=pmm4h$gI)g`-u63e8J-yK~2TzeWCna>8k6*8P|8Ya|jnlW$p>&)a`N% z@33G(++57}ID^YBUm{`0s^0P)S18=RY}TnKaK!T5hl#uC@DK7W0+x3?G<8Xh;m0!F zQop><m5{m8mia+dg7oG+sYkR5i?f*2iHZJ}cQ~AxwQN_`+2?M4wI!3uB>#f>8<Tkd z*ntI-<2x7K(iJ5Xw{djEMhm52_#LXw$;IKcM|w)=7S?Wh;qw8zzd6^j%Z7_9-0kYE z@s%Ocp8ajN`#Pc)mveF}43%#UF+XRd<gWgtsI8EE%`x9`%Y@Y~lX6Q;9G2UKZT}Sk z{()8{D}0U#KH=RHa%~@T4`N5^6-aiOkL1-RcWy-~Y)|PDg$9B4+-$`j6-u({a^yF+ z*oEBjNZ)l>o}|4vLo0;8!$|ug#WYgFYrpfk<A)g#>s(^=U*z9Jdwfc?$r~6byZKtX zgr(Y6R@Oi@Z}FPaGp2SU9`=^|OV=H)wklSR6s*&iTJD*=x%jfz3lqf*E8DMh{LBHK z_z&^XnLAVXBl|2iEh@9AGe#DbIX;ga6|@fJNoP$Q)Uy#N>%ZbBm04!|RK$3^3s0ZV zxonW0u-J)fb8$-T@om0q9kEN<(}ov!$Ch{=REx3Pt<P8yrx*~^ly=56^5o^+qnK6R zNj7Owf_1u4Oh?-#kDv7NvEEm@#*!#7hB(!<QtqX^hnDo_LU!h58BAWjdPz3Ob+Qdl zE`HkGut>)~&LS3<W<Gv-@#L0MEfhTyw?L=ByB_In{QGT`7T7s)FjXX#>L>TEM+qo8 zM|mA?arWZzU#n2Vf^HW$!5cM-;47lU+8PfCC8+FP{jEk_ZXzl!^4ov}U!T7CvZ`|e z@%#D@BNZ`2TMs@qb>@=Z*JY3QzJ1-bS1I+0`lgTasN7XVY?4sFxxkCQQoePVjcZ3w zA8PH@uUJv@&JyQr`bbbJ^K~4DkG>;IZDy)P?hqC|dckyqEoMWxd%=YeR)4`oVwW)q zwl<GjQQfcocQ`lIa=n$=(s*y7yU@z<TZuvSns=XO4QcF5PEFH(eMtV@dBJ#GfxT7q z-B$UXmDNS&{ANlQi%HM1#4_J&Cn;j4IG5e|ifjW%J2E=h%BvqA-pHcPF>x_#@!8WW zx&4Bo?FOC8x3*wvu5J64@94aa^YZ2HuJqvcTDst%GxCovIhq7Z(C;$Vexdl~_RIGs z4r7b#mNmu*nhT!Ov%@AZNDjzgIrBwUzF{UCvnTm{y&{6JHJ4X3k2frL<h2YiOx$U? zGkzmB!1;p=d23rdo8l33rehaVc^S#Wl=WQpAKUl0G!1EdifaC15u<o>1FwTD86DQ@ zm=_RXnS6gy!G?n;TNFn)c$q)8j^f`qmZl!aUSrjhdU$M^f5bYjLdjAA_MpHoOJsIi zO5Iv|daX@k^Qe^Vxg$npQMkx44s#0%)1iE=w3O0dH=TrxHeAS%$l(fi$+t&|Xs&bn z6>gf7KEBbpvp8&rw^q{8kQ2G(yWEwW>&9(WPc5??GE>}jWAw1H$x3$}#wrabrbJnT zrJpDFHy#~r(}-&4s7Nbg5Ij)gAFxZaN<`lCp3a&@((ErwnihHTwv>r;JMX{xS*8H@ zx{TqAmvzClb%Wm~-roM!CNgfL<gFfMyzN3ssDjVrYngME-pXgOn=I(Y_qR;G+_C)Y zW94t3ZnRL|w=Ac;t^79mw&fe;+j2UVTsm$fJ%28}7?MFcmq7{1s5<ED!5XTb%XE?@ zJY(008Ir~H{DS5cTe~_J*R|pYSFr3EbfM72M+QW*Wwf&_VPijubXsN{yNZpin2vM2 z9p!2q(U8miG%h|R{&n$LCZzrX;~1%Cwl8DIg^kQB6&LNr=V?|kNCiak*+hPj%3FLX zZrfefde>wYT?VcUUB$cYS#=i|r#o%9xy#4sLWnCDLj9~1oq@dL*`vDG)yU2joa zzex<i{FY#}lp$_>%n+F+zN0(cXK^e4(>9LbHo3kiO`fw2g&dAH#ATND=El2@498yh z%9#rFkZSLX=y$S|L~MI_{ob~G8Qw?HvAtW%+cA#Gw<X!xZ5S6sCiig~@o`04*mKmR z*vP3SE#A5yYR!^s!w778M?wO(a-!h1>K+Qt>Ocq~d*{2G6dxP&ulH*l1#$VDRXzps zdsxs{SJ3laOK5J#vJJe!Uwb<!QQLO?e%q=X>?%FmovnsBGNL?LdR^zASQ(P|u6D7f z(Wfb;@9yT|x>#;{`fI@r9@A@IyDE)^E{=*G=<DU`;kbmCJd|*)BkdCN);8%r_AI%J zr(0LHD?n=w62q$VS##Jb&Gdy*QrPplneDPQNmspM?S?;cL3^Vt%-d+LaP<*c__%`k zyN&tcCk9?(%%AMO{-QohYf|FVuzArf)B3mrMN#*T+NTxy>ZMe3-(w@ur?u(IlS=cr zD!BHQ=SaHqtPkw(=@N}$U1C6(uzW*zop%!@-`T<nGj^?5bji>Om#Q%5<|h`pgDWp= zE>v{7Q_p;(n$XQCG(;CAkryv@cv;fq#*HU>IAp~RNpYJq=vi^wA0@6^(Co9QDd+C( z5|W$bqTBczDyJitWwR5m+$3-vI(Opohw_~s`5P8|vf@4<1?`65ASPz;JXWjR_95HV zM|fVQ%zeepy6fG<zHQ{Yw!5cFePgW<Zz<i-P+cd7%`=BhmYpU`^iNonwDQosEA-Mo zA6}le+cT1dE_zi@n%P6XenIp7ORipt6zz}N=#^?nUnf*fmXJjZiSP_XoK8IBy6|#* z>f^1ul_xj$VP$2{n93>L6W7jh)s$`8m>T8mahtN)N?g&ZEB?_I38HC9T0Gv`@uG|q z;hNLEW4AJwWR>h&eUkH1{7`mx*9Nnp2dj4LP|TEU+b&paTprbB9Ke?ww#K|WC+M>W zv0uo9ldthWln$?D(YC_2Wh>L}?_S<-&1@pp>?5Y0cXZ2P4;Q}P1>Kzv`vg{*Y$VyI z`%)0Not!2fixo_g4(PUJZohS8_<qg^WOzOvBo~a@-&$bV>(6i`ZHfGGg(F%X0-IkS zt~6d;p?hHS=>x5fw+gai-yAS3R82T+Cy*sUbl8?2nZOUJ0)9oey{;5m@E8awbnA(+ z?o1yEy}-ZiYkw@$I=>!1|4;nKxy#n@^d3LS7cSL1nv6XdtS3;d^TL{Mec*u$$GKv{ z^Ljap1tRp&r??OL5cpkrGor*ZqQ^F#I_z!PoN?r0AAfg7^23Zhd1cM~aX!!Oa=GmC z`rGqf-)Vl2wwO3@eBb&?jkZeWJK3Mn?Z?xOl-QrXx8cl~NV!CLOMCW_i%YId_FOU3 zznWRr5i;6eWS@KD-kozfo#{U2vG@6Riz%kve_nL>;+aun$0P6A4L>ZtXy0Dml66G8 z=)vcQ<=6HYs+Zop#;w@HU(q9`cwM^Ux{~4z)ruQBiZ_WBH_a7qt*f}TN%6K@#ce;u z-jIsk?TURnEBf{*_7i<|>Hfy#_@Cx~`?&i%`1>h^1^E8W`NXqb_Q(4_Be7T&ESUd| z!Q<6bR8*k7;IVi-US)p%_iSZ3*F0r;q>Y6cQeGJmOm-rBx*%OVoPv<@NNPoJfGWZU zd!#lp0wIA_hu#YktBS-*C?F(|>@G-ie?Jm9R)UUEL^uEv4M-yc5CN$RNJBjk0f_>n zfjWqQlmw)zkr7ctACOXj)K>)&kkWv}tAhwgc|hu^fCxx!Kx$w?1f(J$)rcShQVWo( zh9Ck`2ap7P5CN$GNEKBO0VxAW93DhK$^jCK0}+r)zXZTV{}KRK_m=>;C^$%hPxu3c z0$eFLNJ>jdNlQw?$ABvjf~2IhygbB}1O(t}LjfYCwY33P5~2WCQBe_%g1Cx`5Eq4p zNWj&C0t$_i*3y!Oz5oTdI&eUNPxwPe2XLi-34p8cO8{J%UjpFD{SqX$X(8siqRyxH zkCebq_#fco6h6o3Pe=Es_>YA!AHshXyc!O#2G;+;LvbGe|B)B|Q=-4ae^3!-L4U$O zfc`YNhtZz~_b~d?;2uVQ8r;L^PlJ0H{b_IyqdyJqVf3fLJ&gV|xQEey?%|#a`qWPV zlmNG(AVvKGpMX1szfv?D23!F9P>`a2flt5%LI4U<+S({I^$>6&)CWNd!g}h_cOd`^ z0`LI`a4uEk%=`pgs%V(`3An$=00qGPMFz;s!~IW<>VFaWGyXem4kpdD0QhtKr|SOz z{|R6%C@fwDGr#`Rf20=u7XLv-m=^%kia!9pU(|gx;QpfS>jLgf=$}mhOv?ap?z8|v zOG}}t$ACKp{8DIn=noEnO9g#xDKz+l1K>`fz7$#!`hx@DPQyNA0Khf?)e4vi{nTS{ z?o8;X9s};Y0Qirz!T-sd{Dl8*p{{d{{(nLL$H43V!TP@{2I&7PY6QZ({{Nq-7r(=Q zs3Odx|385KF!29^{;={tZ2<t}g#jN{{--Sfz=Z)HR{p0g0KkO-A6EXSEdao!qCX7$ z^XUJdXi5J&@9`7<le|bS<hhmr|C0WX#{vBxr#kQd`Hxhn-{C)85#}YpKOh07JpEKB z5J2iF{U0_Ye%1dW9ev6a_$~op2axu!`oE%-;*|RTg9AuQYT6P2Da`Bt|B-g}zvU)B z;Xh2yb>9CN|FJ4K6_xMne-o(sKc0Y@*Z=4GJ<OKgua1g8=O9o>J!=<NB+<#6>>m`4 zBzus2Nk|ueKQ~YJ%|TEvvcDhF&C`d3oDoonKTr49GoU-If%eivLVwUrkgg;*r_DZO z>J1f;KK}mRo__8~A5U)*HAfgFEDpNHc{3Rq>PhxMIw9Rip-5l<AQIq_{r!TKkS1iL zpMNM4a&HAgzpFcYlA$Zy{QbyCUnf`Syl|v78M==j(vIZmP6|?ja^U3W4bJuPCr|$- z4qan{bn*%IM>@H>l3bC#o?)aQWbo#I0RJE|($fza;N(a02}a_P(nx|50y=%lNi*{c z;;tb1k%F9jpex+`gM8t#`^~usKPO)jJ3Dc6FeqPH`1yXgs~jST<U?`_hECGZz(9^v zgwtlShkp<|yCKOBDnWh!%|WCs2$HXprw=>3E8yt{1^X$vxkCRzh|NI|*Mm$B&{S3q z^$+rK4JHMJK+o?FrRhgfa`E?7hR#-Y3U+Z-B>5?MkbQj+PCfx1PFN^0Pj^ppFwh&R zEZj5;2lJ?9!Ol)VI6Jv`y9fDi_H$(ihw!hnKkv+Pq8>x%S-S+0NPbA@F?~sX<X{D; z()s#Ar9p;XgOATtng8YVrm7_Lf<f)w>_>*)Ahix65D^GC)9e@oIXr;G9^e_~>lA>X ze!*FHn7RUf1Rs(cnSF}k;Op;7Lil>Rvioi(lMq3k?jB_JP^g{;J3ybV{-KC4DAjQ2 zf7>s&nMwjW0}Gvj17}b(1m}?)1Qi+5Z?muSzm!q<GFmp>VA{ct6h?+~IYkFUh3Z0{ z%I*{|Acz#=>AyJ`$`4-UH(7>Lqvi>mHe0@ZNx{KR?of04>xJtZ>`u#Zu!n!BgR3)~ zTlm;1i0m0WomZ&Ik%C;HYCl!3*x3qpDhpEui^U>B*)?XAE&!K+Ab<BDD5pqgXaLM# z&na{^oYOFNHSo%?v%J;mlpUaRX;p}N8C-K*J%gaKcW`oXf$l%`3IaEKx_CPTk=zhY zLGDm>2xC_V>CdbaAlJ}o<SA_XYngTl@(-rARQT55`d{uyt)SFrnH9KxZJe`lpoR#V zmYZi#Fqzsm|7z2mHMQ?;5<6R)gqtg^&cGxR2kMNg57d4E8Y5Mvo|H=Ap)w_amiXgK zn?3cZaS;%_k=fzCaq1{J1YC%KF-sw0H9+EOGi$`Gi3gMY1K^y4!~roi1dp0HV9*jL zgDMQt#ltmW8m4AVH(<ImkD6<^N9TuZ0}hlPD1*O{+;`sqHD53ZU9AG@<zKiwh!i^G z^-Lj)0}LP2Zwam`Q!FZOLKjVC@Gq3(l=GUJJ%DdNrtaeuJk6md4xK-pe`@Ogeq9eH zL28ieTvm46U#{%f>0<l~m7Ur$fux#Nj5KM&Xx3Z$K;r|0VAl|}d?Dp4zyan>RZf@z z4IicQrXb{p%0GL$?*8BFK5g_V?H)5zcY#!qAn29N(y(Svo8$*~XV6k}fH*T#gz^hG zQ?>ZVIyP&Po_^#YT83fjht^8v1iLu-P3QB+rZH=xZjj<hYa4)qF8;Mu&U&qX0MNH* zAo}#%|Dk36`!$>F;Tbelu{D3v^~wJJKEeMUv{UONHS1JO#6M_8ja2!|jo>H5$4<TM zSrH$~G7zD^X$7<16Vj;tr^m>_s5;H~p%P3pr{3a^)oRvsp?vsKs{oJzF!e)<RL*yu zjY|HYvQb$xG`3k9$*i}ARa08+`$<!U(fuzx0C?meQUJ+`3K8HbXsjO|g~pz>lFyn) zSjYPg6EO8dnlp948Pf5)#xQH5Q!VF*gsAldH&ttbokBoMoFU=<{dPn(+2H&9drf9q z^yB|-+xSr%0vhHl+R&`2!_VLVKLf4eP>YT*Rdhe9uej-!pgvO>`h&Ecr;S4h?D0U0 zf-n63KBumv=3|Cd{hdL}RxpWb<`6JaQ(03ZNWnUFh`-NfU+{jZcYy7Ip9=MRdedtA zcgA12L4b?zbm}Uqvpo3s#Qs+5KAwKxGY?s3GmObODIFIe72%SBY6Y-EXLDf8rl<eP zgRWfz;VcePjNnI}OZABGkJkQKQ-_}c2N`oy{);J9=ZC8OM;Ff8Zh$cYOZ*?I;Z!o7 zv)2@mhZ$DnU#aP!Dg6_7aCU+W*C`YApGcn^L~{0lt2^PJdIV}~g$(R}sEFVO1r`23 zl>8JNKtS^k-2k$rJ)u_&JA(d^I^^r=0;NxtL7)!J=B}RS&2b4pzzq4ul&3uF$}}r) z4)62F{XfwBKP=Se59WUmppJj){0{;KH{bs=hp*tbN&T+>pIQ;-2LSvt0|5S|86Z;7 zED(4Mz|;T$GzuO7fQCi@fH44o3v@1M_J<Tr_X6A*L%^m+0L%{n_-#Y@|MZ8S@PEol zJGX`Lr}#f}{4W>_0FD1uo5%mTeF?uw?RWSORfKu)|3|=|I{yzI0u11IYV?;hZ3r;n zP7V2@;{HtV*M`x5N(9irpNjs|B7g?|)R`aD0l@R%|C=`O|1<k1{GZXaId`S<r}(b| z>;G72>=!`|2ao?%Rl(2e|8w^SexK6s@PDcz%pwE+i3LC~7$T9n;>Z8(1wg*9{sE0I znE}VstA9Xab7z46^y(jwEC5{UbiePbf6UPQA+3LU^$+L}oI9oNPp|#~k$^ir$!}`) z4~PWZX#qt02Hd|q^$$Atm-Rp7=4XWezQz4_{qi&Z&ln^!XBqu-{D;T?LjE6U`nM{; ze*y+GKmTLS%KwiN`YrxXRfPYL0QhMQ5F<k@28)5w39Q64hiib)Ru}mnSpkYVu8{f( z9&gGK1X%;W%n6hR)4~4}Gs36E0_}TV0Q^yt`9JmBPx$XMHDYQ`pYc!ee|r5V$p5c` z1^ypA)coi5|2cgLze((O_zx<=EZ9H4{?m+AKmbhsjpaWe%%279A@%#`3E))6|4-BZ zrriF&OaO;n|G!QEhb@8m3E;n}{qyXf@PB$F>YNtLU&Mb%>sKSFs^S6uW7OvTKXdvH zewWzq@E=r!{~7$JDf{sHf3yYv;PwA#3;x0D|IrrwgV+C~E%*no|3_2y|7+I&L({r` zp(7wcW}lD^yfJ7f(gHYlx?6zu4Y<HFL=_8DbHcy(3Df$6!4WtY$OH6@_@ig&Z<+w} z%f0@tdHvV(f5QLo(;Mb8<9~+#I1B-d{{i@~iXp)Me-+&P`cHHD3Vxf`@9>{GkLB0p zzcv2e0<btkC<6R}m;a{WGQ9jZZE<ke`)_~)5u6MQ0NPsNumI4<fe6DMa1GHMN3Z~( z2@hBR(1Zsp0BFJkcK*<W2fP3*O?be|f74cr75{N@@a-^(!9fvZBLa|rdGT0X)RX}M zE}fPIQYgtOxd1;k74zj$Kmx!4ep-O?kmCr(dT>kwdFraKKd<~pRr=Ae0YG#AQ+@wv zD$s*;VMjj-{zOxq{(wte<P|LseWR(zfIIEILZhIbW;g)uclSRUg{B?@t~f1hryc@s z#4lmKtKhe7^FQ&!PxwDQ6?IN;{b%@(o%a8L^<QECKOQ^3{@<Lwg5M_gJN$>IA^!^h z^ZuWI-~R&?{wd@KkT|8`1Lw~SxS!JSf%9jU<zHtJ0?_*3kc$7a5CGafnz{wV{PM5A zZ3O=x|L_z3&z#UaXQlFI_zx`r^<Dob5P<)OfKi$E|Ib<Z|4~A}!~f}u@T(692W<ib zaO+>)0qEZ_0f-uUYI<tmrmzV>vj|`x5X~ZhO#q_$^oBsN2|zOtU=x6-s-~|-+X0AX zFTf@M5n6YQ0BiNI2|y&MLJ`~-HUVhX1Z)D(tO?izpji|4z#^bo6Po}@vnG^b^Wpne zL$WaW-I{O!<iBf8NYAh)rUZ#LN>O@-Ac3s{NQ2jbxHJoaY75B70WQrJfVFoNl}pp) zw}UXng^#y`a)Q<SDK1sj2V8qNptV7|)MLQ?ZcRX3nl-U$IzZkp>LEB+nHFTJhk)zw zOPIGN{;09f^4m}NKV$WoSx)|&tUtqlXaztj{zE51u7BYFpCAAEH_Q58z3g}RKUEQa zh5vtK12`H;|Aj4ph<J<<9tLR`{%NQT!#@p`Vfd$^G7SGTRQ|6T{xiEp0CfMK96->_ zFTjQUKQsprr27Ld)!Pq%kqYTrFqa1J)82hx5zrh!v-dwx;eX~QNOzXKe<1F62hjI} zc^Ljz+u{Gz`9I<R%%xT4EK7fe|9HZb{~rsjTB!=g|3K4!=lg%>to;8dq2J;EbVc}8 z06-hG{w@9g#~na1hDI18+76%uf)UOL*6^ua0N?iqg}VR<D$vQW9!~87_`W|VEC2{t zLn9-2L(ti}0PsXZeIq!-umGS*3s?Z)4GlF6;q5|U{hua0VErGjZ>VMn?;Z;4|1?<w z>;HHSLxLe}O2GO*O|($;e>Ee#kp=)wSpSFY7bpS}*8gceAXNPyXP{~T@0JSd|N8ny z8b)x+nm^hO%V1-{0J;^Ns|_&u-IUk{+YUx}Lp=OMTVWEfht-3(IK{%`-`~>|n!5lE zc7k><1w)>|gTR6Vg@Sf3m6ArG00p?Py#aMoK;KYz1)u;I7ELIW7WA!!g8M4~7ZyS& zlmhgv0NEl?09;tepipwqw;T!uD8Qv!At=z7upNa06yQ?r4V3++h)wn=6rcbXwh&}d zC<n9y3I*E>fU60@vMH-$86W_6101$(h4@>y!N-6Ltx51x_&+Mre!_p+;#^>Xqdx+U zf8n=3!+&Vf*R=kxf>VR>Uj;wE|J%P%!hiqN-{C*4BFrKI=A;(@f;cFG#s+r+)0{f6 z1b_e^ir`mR0vJM|W(aT1Jevfdd2wI~V5kowxE3}6^x*(VSOOTSfCxxf0?-5kECGzL zAc9|E37|&=5s<J1pvebV0vHlO1i!)(z(5T|K*AD$CN5wJV1NY?{0d6|4K)w}2}=My z0*HWwB>+vBz!E@RA4KpgECFcJ1C{{rrq=Maumpf48Wh3Fumpe;h9wUy0cegW91sZV zsvv@sRRD?C01=RQKx*iL2uK1TX*;H>0`f1rslNA_fRx}7vr`;cmcYUU?hrwv!9)l& z8q9oy$40@z1dY~(_QOV_f7l9J8;w@f($Ycez@`vL7q(TX>B`8#1mMDA1C8DufksEb z$FSI-`lHaB?3K}IS(pIl!rmk_TFb!!>NL>=6yPoc0gc`OeQrZ<+X{UFE-W47<Z#fp zikuuC`T|_SF9C2>e+mCR!sI9X|GpUH-$#^RulzIohYAnIf4mx2l>qtzplJZO`TZY$ zEy#a>^E>>fE(7`xbpX@wYc}YoIfG#6r-3^R{WQGR1{g#`WElEsm<vNc4Mt(;r(quq z{WLg(q5t22GoVz$xHhdD!*GUzKEIFm1VBL5n4wQ-sUB(oT-e0}4fdgp_XJ!R`hfn7 z{<Q-bpxi9`{{X%ES0@nY0G6aVfu{7|8DHRTuqOV)2jI?h|4@$s7uL^z`1tREvwxt4 z{NGOUC;SI$-<+=j@hA9?!(hJa{}AxQ`ac0X-~am`sd&G`{}~nGR|5e5_qu?I>UhW} z0w4q)1xh4hpa4i%3J_IQHB@2M6E*;7gF;~`Ks3NYjtp=zECp!R0xSiLaO#HYuuFt$ z0BAtx!U`GH0Ql}Nf~5cv2jmY(3YG#!`UE2aoGVxg(8LXF02pF{QV&js4FDnruZoA& zW!L~9>SG~!1b&4L0Ge!q4FCfTHM|;Zw7>>{J^|X?7JgpX0MJ(hvI|@b8vryx1{(lI z2EeTYeuWJHA^}Il!6(B8fB{w&au|VMVFSPjLr@{WCJk%=(1ak>0D$cSa58KF=wk^6 z1o&Fm05DPq_7M0LHUNk!pr0O)R0BYbKqSETg$)3j;D!wVBUKdx6*wt1Ad87OFme}M zs}4w2gXzw64L}+ari&c|NSZ8m0;CaM1uA)Ptur9?_3?Umm~?@!#p0m=esu*TO>mO{ z`4<JZF66<Y+6VB)-BS`65`@5m1p-JP7Q4W61$n8c$AC)}pE@WV$XP`brBoRRd8))w z+o{LkT-YTAIisXDp-{>&0l2atIG{9_K@O-5FafyRK$sH6+hGE5w?YAnLg8>Y6%+~& zD8L1#g(?c#7@44sLTLaBa6uDta&o4AxqvUgb^Rp(F6oyrFS!3BAp2i@lb_^2d3qm$ zxy<sP$bYN~0owB$%>Teb!@ks@y}%*+Z+`vPxqJh^PwRK`AFc?$N`Bm&to~%A3L=1{ z+3dfcTz@ok5SIKj`3y^bLlqD~Qn2KwNnlv=8yJEJeuX7JO~%5KpXRTFB|lAU!jhln zuY)B&&6I>CKh3EKOMaSQge5=CBL_=<nyiB*Kh3E~?fm)Pfdfl^*yRT66tLuneTuM9 zg(d%Alyy^b4t#;9pXqZ$fA7G7jnwHg;UUT3T<VAe$QniKP8|X+RTe_lC>r$afB?AE z5eJafi3UA8AOP;P9f?MRo*fVXciLh^qe0IO2!KoV$%%_YF1o2hz=g#o^npg(PaOj8 z_mN>}<*7r!g@q{)t`3@0hky&Y>3$0HvTimM_zC~vZr?fV`hjoxFW^5k6o88VD)aq6 zv%Ri4=lu@<sr~=I`2XhdA0Ywbzkxo8FjfH*gx0+e<3CLt!1xc0HLAG=<3CLt!1zyq zh7nNv^Qrj%y~_#4f10iH@8dr}erW#T4^B$B&job40BHc82L^pA`0GMlF;D<p81kv$ z4|To#fd5qRhun-m;6D}o70~chLy!Ut`Bd<iL&K8_0T%}S?Vxi80()2l0504mvuR2+ zZ2}bFQV0IZIzW5}>L_5q1s(Z62EhFZ{2_84|7Qb_pYR{rfp4zSpN{U&@E<$9{x`J# z6XXDd^nVq#`Tf6Vds}nQ`yKv+tr&knf9#xe{L_4Qu=Y>W?_s|m4LbkV^!~uw_4Ee- zpDB;dj4!~2)opm~r`f0f{k;4W1hUWoUND9i1dsxl_D9|P;pgR_s4IW}y8IJ734DI} zr@3!8|5sA{3IAv8COUUd_-FX9f`R&csOx{IVW{i>sjA}U*Z-TlH}LzEeuw|ln`8bW z1EBsnxM$^Go&NXZ@ZX<g06Z3o*7rjb2~-(?(}N-aK-dFBlMt{6h~^NYjsW<sYr`HO znnOqj=;@#bhS~=Mdw^&TA=m>%z=H^G1bcvJ4k4-s2>9G#r#ox`&>TXr2MF|pQ2Rn) z3jnIqPy|WA7647<hb;h_QV&}I|Atd%TI|5z;C3^F&i`xg3VYi&lIUM?Aom<gjV<mj zm*mAZC`#m?2y7!Q`r&YQ=^^Nc0%`o)Z{8bHOG%U{$wZL0qz#cAt#-*3^=4=0&F)0V zy0Srv@J1cxxRhxH>tF}CQ}0Niy1dt<5|l-N;{l9;%lA`F4ull(|Eno<*tC@!h>VXn zv+3paw4VCo8fBRe_+|DoooQy^3v&Zik3BYYhFYi9o}9<Vx%2dd8bZ2Xa|CJQ>IwUC zP{04DebhVM_+PJydsGemg?jPdtml79jD!Ctwuv3_zrSZ+U!o8HfBm#;2ArD#KAZ{& z>qUIWDj?`7RRL>_(B%XQ8i7urzlTPko9FTAu}%u#aP!<gHOPN^y9fJ4BkUxAKaU#W zBv9DG{}%24*H1%vg8J}3F@pJZ{5SEyf0+hw%>O&-dpx)BKK$Q!5O!67i_WqFB5R|H zBY85w;=&6F48LxAf+WQwDH-bQVt|><xsp_*E(VyZye&C&rxlPES-fI|nN~nBC};&Z zd2>b1Q*8yL*^*?VE(VyjZlRedv;q>FNOlRXnO1<YcBNS?v;u;;K`X#Sw$MN~Re(>D zs0b+_!TL}upt70X0#$%bTpeLx93U7Yj04!n35^Sv@d0*?#LbM(kn$C$!8Z8=r>3w~ z!LB0%%vo2n<;VaFdWsA%<E-=^Y9<3L%5aA`RR)-mrr1|xfCamT46vxQ(iRGn0cO0l zDXSs_Ot_t~lDfzM6MB=)*pp;{1r10B*ijl#t)H6Kg=S&d{7~COYVSY_%R{0b61l$5 zbJDsXg`FePhxhM)%yZJZAVru$q}P&Wm@BIbQbeH{H_3C!HN*xfG?Yl+KIi$fYAZ+) zj1cJ^UOq4~0V&vBBKU)^5zZhLj+YUs<<7;jgB1B9vm{AnmtD=~vV-*F&IajEJKISb zumk_W{~ZeY2}^zWABXuroIElZ|IbY_j{nca^iw`}2>(e0-*EuR0<8l$fDUK>g>*kU zfR+{h@U#>91DbR6zeNEG{vd_^Q?ma8Ge@)kKtiQbQRo-Gt}=EB{eeD2=nnu*=wETe zH52*+B6IW~68Z@JA(~9+57A^oe*kDge;^kT`a^ynp&za?b(RtO13(k{L%JWKKjin- z&<_yCH7oR=pDdnB=vawH0>-FNgC7!RuCx1E`F+!TCZ%gy5z3NC1WwWY>_LwqWdh2T zgq0<xyp`YgGP@@SiNR!)B|@Vu*_1&9DO^mF@F%eURwBqagA@pTH+zxS53|`<oIwiK z?bTJJJA)mhc4vbW+~m#eBnUp`=Q?=VE&TuQzkdJa^Yc$1fByG?8lVUN1@bljBT2+W z*7e`Sc{<|%;9qt}h5PV7JP7T<zcUvmz1YVAjT8CfP-TWuCr5BZlN_hzd{t>?HOKxk z&z_qH4rmxSH;XR!@A2Ea5$woi0EpLob~(XoeJ(P}u@~mcd8LIHBNzfS+uC!`S<BaT z@04mj)lphz%f^P+?ITD;MltKIN3=)mc$fWMSC=@iPfqLaQ~IQ%pT{1Q2W0EnYtB2K zs<8*Ve)E629{gr2s_fDKp9fU`z5Fj(?TP-s#7lhD(f$(;;HdvQ)b-$^KK=iBL;Xh= z@x^}o&&Z4|bl*|^Cq7;Hp#BTvN{x?G{oBRTr!1@~AWK_Y9(4W1eo<##(*8?JSr~v- zO*ybwl&P9!YX2>ak64uw0L(&G4})f-{g*9dNin|rKv$SwEi<0d^;eY6SQe)1&-r4J z@|5;pYLZe@C29Z77nNKWYNq{HE}~Qysa5;0%$$;ZY5y(KIMrDyY5z%D<ve4}wEvdI zmYOO_`_Gky*LG?Dr7~5R-Y@OHNOC{5UD|&uF9sf~qWxERx6oUl{pTbHQ#+RS-`rPa z!NRovk}~zJ<j>6eQagwCUn%~lh=pnYNxvG+y1aRLvG;eEm$yHD$R8iwd-IRy>uC1n zi%dNE!`rK`lc%SLhw1d6Z)hal+@SE?EehvuxA0`6@JThZ@LlUNuH`ajhc08^HDAd! zKh@X#&A@i>U+?QH?81LDumRX-|4Cl7;(s!q%Y54!{~!5Zum5t7sNo>~>x0nN0FtR` zpai--fQJo0&aA~q0yRLe2ah&awhF5CLeuXCHd;P8em8!0?5lkb%=m1K&&Hu=qfN0q z<@kH_3h&eZLmGfR`d`+1*ZhCse)uTj`Y#e19__y))c3249Mu0Z4&1Q+MDJ%8X7BvZ zM!sn3ciMktEYn@m!Atk=Dm7We7#_9%vasHS`JanQ>@W^0X#a_^Sm|sqN&t^znGOfA zO#3fOedd^ZN&7D?#Bo5Yh4x=krB>4^Y5yfAt#l0++J8RImOfBF;&hQEoP|^Eze4OV z5>jaY*-C6F@)v0TS+lG%UIy(y6N@PsQb41$%+xBS{Wo8Vu}F3UM=iK`xO+o7J|sjB z956hF0}>c;z%)!8kT#72618!_m>3SgU<gY)Lc$;!4w!9;1J22hgLQC>oNP$eJO=Xc zzDIO=_2WHO(9;85`2YOBkDq?|&u|{VKKzf6^Dq2AiT}+dzzX{>o{#gt^?Y-~ynPt| z`5?6IKgrHIJ^(0u?_xoKqz2e)|Iq^|Q?l8V)UgdK4>*MwU$adbK_hAjRJ0+0XiJc* zU}NCMO55#fD@~*8I*>aLVO<A@$+$TAU$=s{Tg`6OaaOaKc%SXAThU11<@!2yN}Shh z-AsKiyf5~BOCV;p)ZG$jskK0Eo|j6&=H33C{cOYhT8FQt3;)f)3ZM`Fg$l67e^~>b z_#gXm{6GA+y<6i!{I4H`wgM0bU~;tokCW*GN{RnL9SrLK0~Ahf1aw+Ef{+M-#dwcJ zHiR|6>u2!}i_z+rlbLuD>mATs{fZ*g3LE9XH>6nI1V1F`h|lT{AiO@?{4B3)vt0n| zPP=*)dUs8`D0agAvS;ww4KzOCWV8%+e>Z;>$rk?qhWQ_d&Hn74|HVZ*{+Ib59k~FJ zlSGLT|G&jEwh#ZC2ceDqQ7SWYV*KA7{i(PCXw?6+EaeZU>i^ly{&cQ#8kgqiKUtL3 zl0F>zf5P+t^#3GjxmpJQ&ldkX@nx~3(}w<^E&g|6%dDWohyI_yGt|_ds7wl8B>g`j zBS$9ysLHrtvdbHA2Li*J{vW?8Z9x|n<Ntx-P5+OVRmTdtzn<{fz~N^6-^;3Fdi#w3 z2bMPD|6W!dD|o+MQ-FC{b*$k1GXC$aOhV1BWBmW9ycsB_^~SG=YOK}?G$tUWGpbP( zOs9dA4k>xjMZRzbsS>r{z0*a$a0V$Rf5fJXeBlUEI;Z4C7x}^&r1kudZzf#i3ulnh zNhL3nN!L`6GQ~1@+_Ir;GcSOw{MDKY5|nOicXyhNqXchc;B0QAn)?=jGqy4Iz}N%d HO%MDZM36Q8 literal 0 HcmV?d00001 diff --git a/ascd/ascd/version.h b/ascd/ascd/version.h new file mode 100644 index 0000000..d651757 --- /dev/null +++ b/ascd/ascd/version.h @@ -0,0 +1 @@ +#define VERSION "0.13.2" diff --git a/ascd/ascd/wings_ext.h b/ascd/ascd/wings_ext.h new file mode 100644 index 0000000..3d830fb --- /dev/null +++ b/ascd/ascd/wings_ext.h @@ -0,0 +1,75 @@ +extern WMScreen *scr; + +/* the main window */ + +extern WMWindow *win; + +extern WMButton *b_title; + +extern WMPopUpButton *pop, *pop2, *pop3; + +extern WMButton *b_autoplay; +extern WMButton *b_autorepeat; +extern WMButton *b_ignoreavoid; +extern WMTextField *b_device; + +extern WMSlider *b_volume; + +extern WMButton *b_scroll; +extern WMButton *b_artist; +extern WMButton *b_upper; + +extern WMTextField *b_cuetime; +extern WMTextField *b_fadestep; + +extern WMTextField *b_minvol; +extern WMTextField *b_maxvol; +extern WMTextField *b_mutedvol; + +extern WMButton *b_mode1; +extern WMButton *b_mode2; +extern WMButton *b_mode3; +extern WMButton *b_mode4; + +extern WMButton *b_save; +extern WMButton *b_quit; +extern WMButton *b_close; +extern WMButton *b_about; + +extern WMButton *b_help; + +/* the database window */ + +extern WMWindow *dbwin; + +extern WMTextField *db_artist; +extern WMTextField *db_title; +extern WMTextField *db_track; +extern WMButton *db_avoid; +extern WMButton *db_playauto; + +extern WMSlider *db_volume; + +extern WMButton *db_close; +extern WMButton *db_prev; +extern WMButton *db_next; + +extern WMWidget *db_tlabel; + +/* the 'about' window */ + +extern WMWindow *aboutwin; +extern WMWidget *about_th1; +extern WMWidget *about_th2; +extern WMWidget *about_th3; +extern WMWidget *about_th4; +extern WMWidget *about_th5; +extern WMWidget *about_th6; + +/* misc */ + +extern int en_vue; +extern int db_en_vue; +extern int about_en_vue; +extern int pistes; +int db_curtrack; diff --git a/ascd/ascd/wings_global.c b/ascd/ascd/wings_global.c new file mode 100644 index 0000000..cf85ac1 --- /dev/null +++ b/ascd/ascd/wings_global.c @@ -0,0 +1,79 @@ +#include <dirent.h> +#include <WINGs.h> + +WMScreen *scr; + +/* the main window */ + +WMWindow *win; + +WMButton *b_title; + +WMPopUpButton *pop, *pop2, *pop3; + +WMButton *b_autoplay; +WMButton *b_autorepeat; +WMButton *b_ignoreavoid; +WMTextField *b_device; + +WMSlider *b_volume; + +WMButton *b_scroll; +WMButton *b_artist; +WMButton *b_upper; + +WMTextField *b_cuetime; +WMTextField *b_fadestep; + +WMTextField *b_minvol; +WMTextField *b_maxvol; +WMTextField *b_mutedvol; + +WMButton *b_mode1; +WMButton *b_mode2; +WMButton *b_mode3; +WMButton *b_mode4; + +WMButton *b_save; +WMButton *b_quit; +WMButton *b_close; +WMButton *b_about; + +WMButton *b_help; + +/* the database window */ + +WMWindow *dbwin; + +WMTextField *db_artist; +WMTextField *db_title; +WMTextField *db_track; +WMButton *db_avoid; +WMButton *db_playauto; + +WMSlider *db_volume; + +WMButton *db_close; +WMButton *db_prev; +WMButton *db_next; + +WMWidget *db_tlabel; + +/* the 'about' window */ + +WMWindow *aboutwin; +WMWidget *about_th1; +WMWidget *about_th2; +WMWidget *about_th3; +WMWidget *about_th4; +WMWidget *about_th5; +WMWidget *about_th6; + +/* misc */ + +int en_vue = FALSE; +int db_en_vue = FALSE; +int about_en_vue = FALSE; +int pistes = 0; + +int db_curtrack = 1; diff --git a/ascd/ascd/wings_main.c b/ascd/ascd/wings_main.c new file mode 100644 index 0000000..37cbb25 --- /dev/null +++ b/ascd/ascd/wings_main.c @@ -0,0 +1,1070 @@ +/* =========================================================================== + * AScd: the AfterStep and WindowMaker CD player + * gui_wings.c: window handling using WINGs toolkit + * =========================================================================== + * Copyright (c) 1999 Denis Bourez and Rob Malda. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Denis Bourez & Rob Malda + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY DENIS BOUREZ AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL DENIS BOUREZ, ROB MALDA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * =========================================================================== + */ + +#include <dirent.h> +#include <WINGs.h> +#include "ext.h" + +/*int old_track = 0;*/ + +void close_win(WMWidget *self, void *data) { + WMCloseWindow(win); + en_vue = FALSE; + muted_volume = atoi(WMGetTextFieldText(b_mutedvol)); + min_volume = atoi(WMGetTextFieldText(b_minvol)); + max_volume = atoi(WMGetTextFieldText(b_maxvol)); + cue_time = atoi(WMGetTextFieldText(b_cuetime)); + fade_step = atoi(WMGetTextFieldText(b_fadestep)); +} + +void close_aboutwin(WMWidget *self, void *data) { + WMCloseWindow(aboutwin); + about_en_vue = FALSE; +} + +void close_dbwin(WMWidget *self, void *data) { + char txt[127]; + char txt2 [127]; + + WMCloseWindow(dbwin); + strcpy(cd->artist, WMGetTextFieldText(db_artist)); + strcpy(cd->cdname, WMGetTextFieldText(db_title)); + if (strlen(WMGetTextFieldText(db_track)) > 0) { + cd->trk[db_curtrack - 1].songname = malloc(strlen(WMGetTextFieldText(db_track)) + 1); + strcpy(cd->trk[db_curtrack - 1].songname, WMGetTextFieldText(db_track)); + } + + save(); + load(); + + if (cd->artist != NULL) { + strcpy(txt, cd->artist); + if (strcmp(txt, "") == 0) strcpy(txt, "unknown artist"); + } else { + strcpy(txt, "unknown artist"); + } + + strcat(txt, "\n"); + + if (cd->cdname != NULL) { + strcpy(txt2, cd->cdname); + if (strcmp(txt2, "") == 0) strcpy(txt2, "unknown album"); + } else { + strcpy(txt2, "unknown album"); + } + + strcat(txt, txt2); + WMSetButtonText(b_title, txt); + + db_en_vue = FALSE; +} + +void quit_ascd(WMWidget *self, void *data) { + exit(0); +} + +void save_cb(WMWidget *self, void *data) { + muted_volume = atoi(WMGetTextFieldText(b_mutedvol)); + min_volume = atoi(WMGetTextFieldText(b_minvol)); + max_volume = atoi(WMGetTextFieldText(b_maxvol)); + cue_time = atoi(WMGetTextFieldText(b_cuetime)); + fade_step = atoi(WMGetTextFieldText(b_fadestep)); + cd_device = WMGetTextFieldText(b_device); + save_rc_file(); +} + +void track_select(WMWidget *self, void *data) { + show_db_pos = 0; + direct_track = WMGetPopUpButtonSelectedItem(self) + 1; + cd_control(DIRECTTRACK); + old_track = 0; + if (cur_cdmode == WM_CDM_STOPPED) cd_control(PLAY); +} + +void theme_pick(WMWidget *self, void *data) { + strcpy(theme, WMGetPopUpButtonItem(self, WMGetPopUpButtonSelectedItem(self))); + fak_load_theme(theme, TRUE); + fak_maskset(); +} + +void help_cb(WMWidget *self, void *data) { + quick_reference(0); +} + +void mode1_cb(WMWidget *self, void *data) { + if (WMGetButtonSelected(b_mode1)) { + WMSetButtonSelected((b_mode2), FALSE); + } else { + WMSetButtonSelected((b_mode2), TRUE); + } + + if (WMGetButtonSelected(b_mode1)) { + if (WMGetButtonSelected(b_mode3)) time_mode = 0; + else time_mode = 1; + } else { + if (WMGetButtonSelected(b_mode3)) time_mode = 2; + else time_mode = 3; + } +} + +void mode2_cb(WMWidget *self, void *data) { + if (WMGetButtonSelected(b_mode2)) { + WMSetButtonSelected((b_mode1), FALSE); + } else { + WMSetButtonSelected((b_mode1), TRUE); + } + + if (WMGetButtonSelected(b_mode1)) { + if (WMGetButtonSelected(b_mode3)) time_mode = 0; + else time_mode = 1; + } else { + if (WMGetButtonSelected(b_mode3)) time_mode = 2; + else time_mode = 3; + } +} + +void mode3_cb(WMWidget *self, void *data) { + if (WMGetButtonSelected(b_mode3)) { + WMSetButtonSelected((b_mode4), FALSE); + } else { + WMSetButtonSelected((b_mode4), TRUE); + } + + if (WMGetButtonSelected(b_mode1)) { + if (WMGetButtonSelected(b_mode3)) time_mode = 0; + else time_mode = 1; + } else { + if (WMGetButtonSelected(b_mode3)) time_mode = 2; + else time_mode = 3; + } +} + +void mode4_cb(WMWidget *self, void *data) { + if (WMGetButtonSelected(b_mode4)) { + WMSetButtonSelected((b_mode3), FALSE); + } else { + WMSetButtonSelected((b_mode3), TRUE); + } + + if (WMGetButtonSelected(b_mode1)) { + if (WMGetButtonSelected(b_mode3)) time_mode = 0; + else time_mode = 1; + } else { + if (WMGetButtonSelected(b_mode3)) time_mode = 2; + else time_mode = 3; + } +} + +void autoplay_cb(WMWidget *self, void *data) { + if (autoplay) autoplay = 0; + else autoplay = 1; + + if (WMGetButtonSelected(b_mode1)) { + if (WMGetButtonSelected(b_mode3)) time_mode = 0; + else time_mode = 1; + } else { + if (WMGetButtonSelected(b_mode3)) time_mode = 2; + else time_mode = 3; + } +} + +void autorepeat_cb(WMWidget *self, void *data) { + if (autorepeat) autorepeat = 0; + else autorepeat = 1; +} + +void ignoreavoid_cb(WMWidget *self, void *data) { + if (ignore_avoid) ignore_avoid = 0; + else ignore_avoid = 1; +} + +void upper_cb(WMWidget *self, void *data) { + if (force_upper) force_upper = 0; + else force_upper = 1; +} + +void scroll_cb(WMWidget *self, void *data) { + if (show_db) show_db = 0; + else show_db = 1; +} + +void artist_cb(WMWidget *self, void *data) { + if (show_artist) show_artist = 0; + else show_artist = 1; +} + +void volume_cb(WMWidget *self, void *data) { + if (cur_cdmode != WM_CDM_EJECTED) { + volume = WMGetSliderValue(self); + cd_volume(volume, 10, max_volume); + redraw = TRUE; + } +} + +/* --------------------------------------------------- */ + +void create_about_window(WMScreen *scr) { + char txt[256]; + WMWidget *lb1, *lb2, *lb3, *lb4, *lb5, *lb6; + WMFont *font; + + aboutwin = WMCreateWindow(scr, "AScd-about"); + WMSetWindowCloseAction(aboutwin, close_aboutwin, NULL); + WMSetWindowTitle(aboutwin, "AScd Info"); + WMResizeWidget(aboutwin, 350, 170); + + lb1 = WMCreateLabel(aboutwin); + WMResizeWidget(lb1, 35, 20); + WMMoveWidget(lb1, 10, 10); + font = WMBoldSystemFontOfSize(scr, 12); + if (font) { + WMSetLabelFont(lb1, font); + WMReleaseFont(font); + } + WMSetLabelText(lb1, "AScd"); + + lb2 = WMCreateLabel(aboutwin); + WMResizeWidget(lb2, 200, 20); + WMMoveWidget(lb2, 50, 10); + sprintf(txt, "release %s", VERSION); + WMSetLabelText(lb2, txt); + + lb3 = WMCreateLabel(aboutwin); + WMResizeWidget(lb3, 280, 40); + WMMoveWidget(lb3, 50, 30); + font = WMSystemFontOfSize(scr, 10); + if (font) { + WMSetLabelFont(lb3, font); + WMReleaseFont(font); + } + sprintf(txt, "(c) 1996-1999 Denis Bourez and Rob Malda\nContact: [email protected]\nhttp://worldserver.oleane.com/rsn/ascd-en.html"); + WMSetLabelText(lb3, txt); + + lb5 = WMCreateLabel(aboutwin); + WMResizeWidget(lb5, 60, 20); + WMMoveWidget(lb5, 10, 70); + font = WMBoldSystemFontOfSize(scr, 10); + if (font) { + WMSetLabelFont(lb5, font); + WMReleaseFont(font); + } + WMSetLabelText(lb5, "Ctrl:"); + + lb4 = WMCreateLabel(aboutwin); + WMResizeWidget(lb4, 260, 20); + WMMoveWidget(lb4, 50, 70); + font = WMSystemFontOfSize(scr, 10); + if (font) { + WMSetLabelFont(lb4, font); + WMReleaseFont(font); + } + sprintf(txt, "CD Control %s + %s", cd_control_version(), wm_libver_string()); + WMSetLabelText(lb4, txt); + + lb6 = WMCreateLabel(aboutwin); + WMResizeWidget(lb6, 60, 20); + WMMoveWidget(lb6, 10, 85); + font = WMBoldSystemFontOfSize(scr, 10); + if (font) { + WMSetLabelFont(lb6, font); + WMReleaseFont(font); + } + WMSetLabelText(lb6, "Theme:"); + + about_th1 = WMCreateLabel(aboutwin); + WMResizeWidget(about_th1, 280, 12); + WMMoveWidget(about_th1, 50, 89); + font = WMSystemFontOfSize(scr, 10); + if (font) { + WMSetLabelFont(about_th1, font); + WMReleaseFont(font); + } + WMSetLabelText(about_th1, "A"); + + about_th2 = WMCreateLabel(aboutwin); + WMResizeWidget(about_th2, 280, 12); + WMMoveWidget(about_th2, 50, 101); + font = WMSystemFontOfSize(scr, 10); + if (font) { + WMSetLabelFont(about_th2, font); + WMReleaseFont(font); + } + WMSetLabelText(about_th2, "B"); + + about_th3 = WMCreateLabel(aboutwin); + WMResizeWidget(about_th3, 280, 12); + WMMoveWidget(about_th3, 50, 113); + font = WMSystemFontOfSize(scr, 10); + if (font) { + WMSetLabelFont(about_th3, font); + WMReleaseFont(font); + } + WMSetLabelText(about_th3, "C"); + + about_th4 = WMCreateLabel(aboutwin); + WMResizeWidget(about_th4, 280, 12); + WMMoveWidget(about_th4, 50, 125); + font = WMSystemFontOfSize(scr, 10); + if (font) { + WMSetLabelFont(about_th4, font); + WMReleaseFont(font); + } + WMSetLabelText(about_th4, "D"); + + about_th5 = WMCreateLabel(aboutwin); + WMResizeWidget(about_th5, 280, 12); + WMMoveWidget(about_th5, 50, 137); + font = WMSystemFontOfSize(scr, 10); + if (font) { + WMSetLabelFont(about_th5, font); + WMReleaseFont(font); + } + WMSetLabelText(about_th5, "E"); + + about_th6 = WMCreateLabel(aboutwin); + WMResizeWidget(about_th6, 280, 12); + WMMoveWidget(about_th6, 50, 149); + font = WMSystemFontOfSize(scr, 10); + if (font) { + WMSetLabelFont(about_th6, font); + WMReleaseFont(font); + } + WMSetLabelText(about_th6, "F"); + + + + WMRealizeWidget(aboutwin); +} + +void about_window(WMScreen *scr) { + char txt[256]; + + if (! about_en_vue) { + sprintf(txt, "%s %s", th_name, th_release); + WMSetLabelText(about_th1, txt); + WMSetLabelText(about_th2, th_author); + WMSetLabelText(about_th3, th_email); + WMSetLabelText(about_th4, th_url); + WMSetLabelText(about_th5, th_comment); + sprintf(txt, "Faktory: %dx%d (panels: %d Buttons: %d)", backXPM.attributes.width, backXPM.attributes.height, panels, but_max); + WMSetLabelText(about_th6, txt); + + WMRealizeWidget(aboutwin); + WMMapSubwidgets(aboutwin); + WMMapWidget(aboutwin); + about_en_vue = TRUE; + } else { + WMCloseWindow(aboutwin); + about_en_vue = FALSE; + } +} + +/* --------------------------------------------------- */ + +void db_prev_cb(WMWidget *self, void *data) { + char txt[256]; + + if (cur_cdmode == WM_CDM_EJECTED) return; + + strcpy(cd->artist, WMGetTextFieldText(db_artist)); + strcpy(cd->cdname, WMGetTextFieldText(db_title)); + if (strlen(WMGetTextFieldText(db_track)) > 0) { + cd->trk[db_curtrack - 1].songname = malloc(strlen(WMGetTextFieldText(db_track)) + 1); + strcpy(cd->trk[db_curtrack - 1].songname, WMGetTextFieldText(db_track)); + } + save(); + load(); + + if (db_curtrack == 1) db_curtrack = cur_ntracks; + else db_curtrack--; + sprintf(txt, "Tk %d:", db_curtrack); + WMSetLabelText(db_tlabel, txt); + + if (cd->trk[db_curtrack - 1].songname != NULL) { + sprintf(txt, cd->trk[db_curtrack - 1].songname); + } else { + strcpy(txt, ""); + } + WMSetTextFieldText(db_track, txt); + + if (cd->trk[db_curtrack - 1].avoid) { + WMSetButtonSelected(db_avoid, TRUE); + } else { + WMSetButtonSelected(db_avoid, FALSE); + } + + WMRealizeWidget(dbwin); +} + +void db_next_cb(WMWidget *self, void *data) { + char txt[256]; + + if (cur_cdmode == WM_CDM_EJECTED) return; + + strcpy(cd->artist, WMGetTextFieldText(db_artist)); + strcpy(cd->cdname, WMGetTextFieldText(db_title)); + if (strlen(WMGetTextFieldText(db_track)) > 0) { + cd->trk[db_curtrack - 1].songname = malloc(strlen(WMGetTextFieldText(db_track)) + 1); + strcpy(cd->trk[db_curtrack - 1].songname, WMGetTextFieldText(db_track)); + } + save(); + load(); + + if (db_curtrack == cur_ntracks) db_curtrack = 1; + else db_curtrack++; + sprintf(txt, "Tk %d:", db_curtrack); + WMSetLabelText(db_tlabel, txt); + + if (cd->trk[db_curtrack - 1].songname != NULL) { + sprintf(txt, cd->trk[db_curtrack - 1].songname); + } else { + strcpy(txt, ""); + } + WMSetTextFieldText(db_track, txt); + + if (cd->trk[db_curtrack - 1].avoid) { + WMSetButtonSelected(db_avoid, TRUE); + } else { + WMSetButtonSelected(db_avoid, FALSE); + } + + WMRealizeWidget(dbwin); +} + +void db_avoid_cb(WMWidget *self, void *data) { + if (cur_cdmode == WM_CDM_EJECTED) return; + if (cd->trk[db_curtrack - 1].avoid) { + cd->trk[db_curtrack - 1].avoid = 0; + } else { + cd->trk[db_curtrack - 1].avoid = 1; + } + save(); + load(); +} + +void db_playauto_cb(WMWidget *self, void *data) { + if (cur_cdmode == WM_CDM_EJECTED) return; + if (cd->autoplay) { + cd->autoplay = 0; + } else { + cd->autoplay = 1; + } + save(); + load(); +} + +void db_volume_cb(WMWidget *self, void *data) { + if (cur_cdmode != WM_CDM_EJECTED) { + cd->volume = WMGetSliderValue(self); + save(); + load(); + volume = cd->volume; + cd_volume(volume, 10, max_volume); + redraw = TRUE; + WMSetSliderMinValue(b_volume, min_volume); + WMSetSliderMaxValue(b_volume, max_volume); + WMSetSliderValue(b_volume, volume); + } +} + +void create_db_window(WMScreen *scr) { + char txt[256]; + WMWidget *fr, *fr2; + WMWidget *lb1, *lb2, *lb3; + + dbwin = WMCreateWindow(scr, "AScd-db"); + WMSetWindowCloseAction(dbwin, close_dbwin, NULL); + WMSetWindowTitle(dbwin, "AScd database edition"); + WMResizeWidget(dbwin, 350, 220); + + + fr = WMCreateFrame(dbwin); + WMMoveWidget(fr, 10, 05); + WMResizeWidget(fr, 330, 105); + WMSetFrameTitle(fr, "CD info:"); + + lb1 = WMCreateLabel(dbwin); + WMResizeWidget(lb1, 40, 20); + WMMoveWidget(lb1, 16, 20); + WMSetLabelText(lb1, "Artist:"); + + db_artist = WMCreateTextField(dbwin); + WMMoveWidget(db_artist, 70, 20); + WMResizeWidget(db_artist, 255, 20); + + lb2 = WMCreateLabel(dbwin); + WMResizeWidget(lb2, 40, 20); + WMMoveWidget(lb2, 16, 40); + WMSetLabelText(lb2, "Title:"); + + db_title = WMCreateTextField(dbwin); + WMMoveWidget(db_title, 70, 40); + WMResizeWidget(db_title, 255, 20); + + db_playauto = WMCreateButton(dbwin, WBTSwitch); + WMMoveWidget(db_playauto, 70, 65); + WMResizeWidget(db_playauto, 255, 20); + WMSetButtonText(db_playauto, "play this CD automatically (WorkMan)"); + WMSetButtonAction(db_playauto, db_playauto_cb, NULL); + + lb3 = WMCreateLabel(dbwin); + WMResizeWidget(lb3, 54, 20); + WMMoveWidget(lb3, 16, 85); + WMSetLabelText(lb3, "Volume:"); + + db_volume = WMCreateSlider(dbwin); + WMResizeWidget(db_volume, 255, 15); + WMMoveWidget(db_volume, 70, 88); + WMSetSliderKnobThickness(db_volume, 22); + WMSetSliderAction(db_volume, db_volume_cb, NULL); + + /* --- */ + + fr2 = WMCreateFrame(dbwin); + WMMoveWidget(fr2, 10, 115); + WMResizeWidget(fr2, 330, 65); + WMSetFrameTitle(fr2, "Track info:"); + + db_tlabel = WMCreateLabel(dbwin); + WMResizeWidget(db_tlabel, 54, 20); + WMMoveWidget(db_tlabel, 16, 130); + sprintf(txt, "Track %d:", db_curtrack); + WMSetLabelText(db_tlabel, txt); + + db_track = WMCreateTextField(dbwin); + WMMoveWidget(db_track, 70, 130); + WMResizeWidget(db_track, 255, 20); + + db_avoid = WMCreateButton(dbwin, WBTSwitch); + WMMoveWidget(db_avoid, 70, 152); + WMResizeWidget(db_avoid, 235, 20); + WMSetButtonText(db_avoid, "don't play this track"); + WMSetButtonAction(db_avoid, db_avoid_cb, NULL); + + /* --- */ + + db_prev = WMCreateButton(dbwin, 0); + WMResizeWidget(db_prev, 110, 25); + WMMoveWidget(db_prev, 10, 190); + WMSetButtonText(db_prev, "Previous Track"); + WMSetButtonAction(db_prev, db_prev_cb, NULL); + + db_next = WMCreateButton(dbwin, 0); + WMResizeWidget(db_next, 110, 25); + WMMoveWidget(db_next, 125, 190); + WMSetButtonText(db_next, "Next Track"); + WMSetButtonAction(db_next, db_next_cb, NULL); + + db_close = WMCreateButton(dbwin, 0); + WMResizeWidget(db_close, 55, 25); + WMMoveWidget(db_close, 285, 190); + WMSetButtonText(db_close, "Close"); + WMSetButtonAction(db_close, close_dbwin, NULL); + + WMRealizeWidget(dbwin); +} + +void db_window(WMScreen *scr) { + char txt[256]; + + if (! db_en_vue) { + if (cur_cdmode == WM_CDM_EJECTED) return; + + /* 0.13 fix */ + if (db_curtrack < 1) db_curtrack = 1; + + load(); + if (cd->artist != NULL) strcpy(txt, cd->artist); + else strcpy(txt, ""); + WMSetTextFieldText(db_artist, txt); + + if (cd->cdname != NULL) strcpy(txt, cd->cdname); + else strcpy(txt, ""); + WMSetTextFieldText(db_title, txt); + + if (cd->autoplay) { + WMSetButtonSelected(db_playauto, TRUE); + } else { + WMSetButtonSelected(db_playauto, TRUE); + } + + if (cd->trk[db_curtrack - 1].songname != NULL) { + sprintf(txt, cd->trk[db_curtrack - 1].songname); + } else { + strcpy(txt, ""); + } + WMSetTextFieldText(db_track, txt); + + if (cd->trk[db_curtrack - 1].avoid) { + WMSetButtonSelected(db_avoid, TRUE); + } else { + WMSetButtonSelected(db_avoid, FALSE); + } + + WMSetSliderMinValue(db_volume, min_volume); + WMSetSliderMaxValue(db_volume, max_volume); + if (cd->volume > 0) WMSetSliderValue(db_volume, cd->volume); + else WMSetSliderValue(db_volume, max_volume); + + WMRealizeWidget(dbwin); + WMMapSubwidgets(dbwin); + WMMapWidget(dbwin); + db_en_vue = TRUE; + } else { + WMCloseWindow(dbwin); + db_en_vue = FALSE; + } +} + +void db_cb(WMWidget *self, void *data) { + char txt[127]; + sprintf(txt, "Tk %d:", db_curtrack); + WMSetLabelText(db_tlabel, txt); + /*WMRealizeWidget(dbwin);*/ + db_window(scr); +} + +void about_cb(WMWidget *self, void *data) { + about_window(scr); +} + +void create_big_window(WMScreen *scr) { + WMWidget *fr, *fr3, *fr4, *fr5; + WMWidget *lb1, *lb2, *lb3, *lb4, *lb6; + + win = WMCreateWindow(scr, "AScd"); + WMSetWindowCloseAction(win, close_win, NULL); + WMSetWindowTitle(win, "AScd"); + WMResizeWidget(win, 320, 370); + + b_title = WMCreateButton(win, 0); + WMResizeWidget(b_title, 300, 40); + WMMoveWidget(b_title, 10, 5); + WMSetButtonText(b_title, "1\n2"); + WMSetButtonAction(b_title, db_cb, NULL); + + pop = WMCreatePopUpButton(win); + WMResizeWidget(pop, 300, 20); + WMMoveWidget(pop, 10, 50); + WMSetPopUpButtonPullsDown(pop, True); + WMSetPopUpButtonText(pop, "Tracks"); + WMAddPopUpButtonItem(pop, "1"); + WMSetPopUpButtonAction(pop, track_select, NULL); + + fr = WMCreateFrame(win); + WMMoveWidget(fr, 10, 75); + WMResizeWidget(fr, 110, 140); + WMSetFrameTitle(fr, "Playback:"); + + b_autoplay = WMCreateButton(win, WBTSwitch); + WMMoveWidget(b_autoplay, 20, 95); + WMResizeWidget(b_autoplay, 90, 20); + WMSetButtonText(b_autoplay, "autoplay"); + WMSetButtonAction(b_autoplay, autoplay_cb, NULL); + + b_autorepeat = WMCreateButton(win, WBTSwitch); + WMMoveWidget(b_autorepeat, 20, 115); + WMResizeWidget(b_autorepeat, 90, 20); + WMSetButtonText(b_autorepeat, "autorepeat"); + WMSetButtonAction(b_autorepeat, autorepeat_cb, NULL); + + b_ignoreavoid = WMCreateButton(win, WBTSwitch); + WMMoveWidget(b_ignoreavoid, 20, 135); + WMResizeWidget(b_ignoreavoid, 90, 20); + WMSetButtonText(b_ignoreavoid, "apply skips"); + WMSetButtonAction(b_ignoreavoid, ignoreavoid_cb, NULL); + + lb6 = WMCreateLabel(win); + WMResizeWidget(lb6, 80, 20); + WMMoveWidget(lb6, 15, 165); + WMSetLabelText(lb6, "CD Device:"); + + b_device = WMCreateTextField(win); + WMResizeWidget(b_device, 85, 20); + WMMoveWidget(b_device, 20, 185); + + + fr3 = WMCreateFrame(win); + WMMoveWidget(fr3, 130, 75); + WMResizeWidget(fr3, 180, 140); + WMSetFrameTitle(fr3, "Display:"); + + b_scroll = WMCreateButton(win, WBTSwitch); + WMMoveWidget(b_scroll, 140, 95); + WMResizeWidget(b_scroll, 130, 20); + WMSetButtonText(b_scroll, "scroll song names"); + WMSetButtonAction(b_scroll, scroll_cb, NULL); + + b_artist = WMCreateButton(win, WBTSwitch); + WMMoveWidget(b_artist, 140, 115); + WMResizeWidget(b_artist, 130, 20); + WMSetButtonText(b_artist, "add artist name"); + WMSetButtonAction(b_artist, artist_cb, NULL); + + b_upper = WMCreateButton(win, WBTSwitch); + WMMoveWidget(b_upper, 140, 135); + WMResizeWidget(b_upper, 130, 20); + WMSetButtonText(b_upper, "force uppercase"); + WMSetButtonAction(b_upper, upper_cb, NULL); + + lb4 = WMCreateLabel(win); + WMResizeWidget(lb4, 80, 20); + WMMoveWidget(lb4, 135, 165); + WMSetLabelText(lb4, "Theme:"); + + pop2 = WMCreatePopUpButton(win); + WMResizeWidget(pop2, 160, 20); + WMMoveWidget(pop2, 140, 185); + WMSetPopUpButtonText(pop2, theme); + WMAddPopUpButtonItem(pop2, "1"); + WMSetPopUpButtonAction(pop2, theme_pick, NULL); + + b_help = WMCreateButton(win, 0); + WMResizeWidget(b_help, 80, 18); + WMMoveWidget(b_help, 220, 165); + WMSetButtonText(b_help, "Quick Ref."); + WMSetButtonAction(b_help, help_cb, NULL); + + /* the volume */ + + fr4 = WMCreateFrame(win); + WMMoveWidget(fr4, 10, 220); + WMResizeWidget(fr4, 300, 80); + WMSetFrameTitle(fr4, "Volume & Misc:"); + + b_minvol = WMCreateTextField(win); + WMResizeWidget(b_minvol, 30, 20); + WMMoveWidget(b_minvol, 20, 240); + + b_volume = WMCreateSlider(win); + WMResizeWidget(b_volume, 220, 20); + WMMoveWidget(b_volume, 50, 240); + WMSetSliderKnobThickness(b_volume, 22); + WMSetSliderAction(b_volume, volume_cb, NULL); + + b_maxvol = WMCreateTextField(win); + WMResizeWidget(b_maxvol, 30, 20); + WMMoveWidget(b_maxvol, 270, 240); + + lb1 = WMCreateLabel(win); + WMResizeWidget(lb1, 40, 20); + WMMoveWidget(lb1, 15, 270); + WMSetLabelText(lb1, "Muted"); + + b_mutedvol = WMCreateTextField(win); + WMResizeWidget(b_mutedvol, 30, 20); + WMMoveWidget(b_mutedvol, 65, 270); + + lb2 = WMCreateLabel(win); + WMResizeWidget(lb2, 70, 20); + WMMoveWidget(lb2, 105, 270); + WMSetLabelText(lb2, "Cue Time"); + + b_cuetime = WMCreateTextField(win); + WMResizeWidget(b_cuetime, 25, 20); + WMMoveWidget(b_cuetime, 180, 270); + + lb3 = WMCreateLabel(win); + WMResizeWidget(lb3, 33, 20); + WMMoveWidget(lb3, 225, 270); + WMSetLabelText(lb3, "Fade"); + + b_fadestep = WMCreateTextField(win); + WMResizeWidget(b_fadestep, 25, 20); + WMMoveWidget(b_fadestep, 275, 270); + + /* the counter modes */ + + fr5 = WMCreateFrame(win); + WMMoveWidget(fr5, 10, 305); + WMResizeWidget(fr5, 190, 60); + WMSetFrameTitle(fr5, "Counter Mode:"); + + b_mode1 = WMCreateButton(win, WBTRadio); + WMMoveWidget(b_mode1, 20, 320); + WMResizeWidget(b_mode1, 55, 20); + WMSetButtonText(b_mode1, "Track"); + WMSetButtonAction(b_mode1, mode1_cb, NULL); + + b_mode2 = WMCreateButton(win, WBTRadio); + WMMoveWidget(b_mode2, 20, 340); + WMResizeWidget(b_mode2, 55, 20); + WMSetButtonText(b_mode2, "CD"); + WMSetButtonAction(b_mode2, mode2_cb, NULL); + + b_mode3 = WMCreateButton(win, WBTRadio); + WMMoveWidget(b_mode3, 90, 320); + WMResizeWidget(b_mode3, 85, 20); + WMSetButtonText(b_mode3, "Elapsed"); + WMSetButtonAction(b_mode3, mode3_cb, NULL); + + b_mode4 = WMCreateButton(win, WBTRadio); + WMMoveWidget(b_mode4, 90, 340); + WMResizeWidget(b_mode4, 85, 20); + WMSetButtonText(b_mode4, "Remaining"); + WMSetButtonAction(b_mode4, mode4_cb, NULL); + + /* the global buttons */ + + b_save = WMCreateButton(win, 0); + WMResizeWidget(b_save, 50, 25); + WMMoveWidget(b_save, 205, 340); + WMSetButtonText(b_save, "Save"); + WMSetButtonAction(b_save, save_cb, NULL); + + b_about = WMCreateButton(win, 0); + WMResizeWidget(b_about, 50, 25); + WMMoveWidget(b_about, 205, 310); + WMSetButtonText(b_about, "Info"); + WMSetButtonAction(b_about, about_cb, NULL); + + b_save = WMCreateButton(win, 0); + WMResizeWidget(b_save, 50, 25); + WMMoveWidget(b_save, 260, 310); + WMSetButtonText(b_save, "Quit"); + WMSetButtonAction(b_save, quit_ascd, NULL); + + b_close = WMCreateButton(win, 0); + WMResizeWidget(b_close, 50, 25); + WMMoveWidget(b_close, 260, 340); + WMSetButtonText(b_close, "Close"); + WMSetButtonAction(b_close, close_win, NULL); + + /* finished, draw everything */ + + WMRealizeWidget(win); + + /* + WMMapSubwidgets(win); + WMMapWidget(win); + */ +} + +void update_track() { + int i; + char txt[127]; + char txt2[127]; + + if (en_vue) { + if (cur_cdmode != WM_CDM_EJECTED) { + + if (cd->artist != NULL) { + strcpy(txt, cd->artist); + if (strcmp(txt, "") == 0) strcpy(txt, "unknown artist"); + } else { + strcpy(txt, "unknown artist"); + } + + strcat(txt, "\n"); + + if (cd->cdname != NULL) { + strcpy(txt2, cd->cdname); + if (strcmp(txt2, "") == 0) strcpy(txt2, "unknown album"); + } else { + strcpy(txt2, "unknown album"); + } + + strcat(txt, txt2); + WMSetButtonText(b_title, txt); + + for (i = WMGetPopUpButtonNumberOfItems(pop); i >= 0; i --) { + WMRemovePopUpButtonItem(pop, i); + } + + for (i = 0; i < cur_ntracks; i++) { + sprintf(txt, "%2d ", i + 1); + if (cd->trk[i].songname != NULL) strcat(txt, cd->trk[i].songname); + WMAddPopUpButtonItem(pop, txt); + } + + pistes = cur_ntracks; + + if (cur_track >= 1) { + if (cd->trk[cur_track - 1].songname != NULL) { + sprintf(txt, "%s", cd->trk[cur_track - 1].songname); + if (strcmp(txt, "") == 0) sprintf(txt, "Track %d", cur_track); + WMSetPopUpButtonText(pop, txt); + } else { + sprintf(txt, "Track %d", cur_track); + WMSetPopUpButtonText(pop, txt); + } + } else { + WMSetPopUpButtonText(pop, "Track 1"); + } + + } else { + WMSetButtonText(b_title, "No CD in drive\nor the tray is not loaded"); + } + } +} + +void big_window(WMScreen *scr) { + int i; + char txt[256]; + DIR *dir_fd; + struct dirent *dir_pt; + + if (! en_vue) { + + /*if (cur_cdmode == WM_CDM_EJECTED) return;*/ + + en_vue = TRUE; + + sprintf(txt, "%d", cue_time); + WMSetTextFieldText(b_cuetime, txt); + sprintf(txt, "%d", fade_step); + WMSetTextFieldText(b_fadestep, txt); + sprintf(txt, "%d", min_volume); + WMSetTextFieldText(b_minvol, txt); + sprintf(txt, "%d", max_volume); + WMSetTextFieldText(b_maxvol, txt); + sprintf(txt, "%d", muted_volume); + WMSetTextFieldText(b_mutedvol, txt); + + WMSetTextFieldText(b_device, cd_device); + + WMSetSliderMinValue(b_volume, min_volume); + WMSetSliderMaxValue(b_volume, max_volume); + WMSetSliderValue(b_volume, volume); + + /* remove everything in the two popups */ + + for (i = WMGetPopUpButtonNumberOfItems(pop); i >= 0; i --) { + WMRemovePopUpButtonItem(pop, i); + } + + for (i = WMGetPopUpButtonNumberOfItems(pop2); i >= 0; i --) { + WMRemovePopUpButtonItem(pop2, i); + } + + /* update the database stuff. AScd 0.13: I deleted all this + stuff from this function as update_track() do the same! */ + + update_track(); + + /* make the theme select menu */ + + sprintf(txt, "%s/Themes", THDIR); + if ((dir_fd = opendir(txt)) != NULL) { + while((dir_pt = readdir(dir_fd)) != NULL) { + if (dir_pt->d_name[0] != '.') { + WMAddPopUpButtonItem(pop2, dir_pt->d_name); + } + } + closedir(dir_fd); + } + WMSetPopUpButtonText(pop2, theme); + + /* several options toggles */ + + if (autoplay) { + WMSetButtonSelected(b_autoplay, TRUE); + } else { + WMSetButtonSelected(b_autoplay, FALSE); + } + + if (autorepeat) { + WMSetButtonSelected(b_autorepeat, TRUE); + } else { + WMSetButtonSelected(b_autorepeat, FALSE); + } + + if (show_db) { + WMSetButtonSelected(b_scroll, TRUE); + } else { + WMSetButtonSelected(b_scroll, FALSE); + } + + if (show_artist) { + WMSetButtonSelected(b_artist, TRUE); + } else { + WMSetButtonSelected(b_artist, FALSE); + } + + if (ignore_avoid) { + WMSetButtonSelected(b_ignoreavoid, FALSE); + } else { + WMSetButtonSelected(b_ignoreavoid, TRUE); + } + + if (force_upper) { + WMSetButtonSelected(b_upper, TRUE); + } else { + WMSetButtonSelected(b_upper, FALSE); + } + + /* finally, the counter mode radio buttons */ + + switch(time_mode) { + case 0: + WMSetButtonSelected((b_mode1), TRUE); + WMSetButtonSelected((b_mode2), FALSE); + WMSetButtonSelected((b_mode3), TRUE); + WMSetButtonSelected((b_mode4), FALSE); + break; + case 1: + WMSetButtonSelected((b_mode1), TRUE); + WMSetButtonSelected((b_mode2), FALSE); + WMSetButtonSelected((b_mode4), TRUE); + WMSetButtonSelected((b_mode3), FALSE); + break; + case 2: + WMSetButtonSelected((b_mode2), TRUE); + WMSetButtonSelected((b_mode1), FALSE); + WMSetButtonSelected((b_mode3), TRUE); + WMSetButtonSelected((b_mode4), FALSE); + break; + default: + WMSetButtonSelected((b_mode2), TRUE); + WMSetButtonSelected((b_mode1), FALSE); + WMSetButtonSelected((b_mode4), TRUE); + WMSetButtonSelected((b_mode3), FALSE); + break; + } + + WMRealizeWidget(win); + WMMapSubwidgets(win); + WMMapWidget(win); + } else { + WMCloseWindow(win); + muted_volume = atoi(WMGetTextFieldText(b_mutedvol)); + min_volume = atoi(WMGetTextFieldText(b_minvol)); + max_volume = atoi(WMGetTextFieldText(b_maxvol)); + cue_time = atoi(WMGetTextFieldText(b_cuetime)); + fade_step = atoi(WMGetTextFieldText(b_fadestep)); + en_vue = FALSE; + } +} diff --git a/ascd/ascd/workman/include b/ascd/ascd/workman/include new file mode 120000 index 0000000..4251ee6 --- /dev/null +++ b/ascd/ascd/workman/include @@ -0,0 +1 @@ +../libworkman/include \ No newline at end of file diff --git a/ascd/ascd/workman/wm_cdda.h b/ascd/ascd/workman/wm_cdda.h new file mode 100644 index 0000000..831b19e --- /dev/null +++ b/ascd/ascd/workman/wm_cdda.h @@ -0,0 +1,117 @@ +#ifndef WM_CDDA_H +#define WM_CDDA_H +/* + * $Id: wm_cdda.h,v 1.2 1999/02/14 09:50:46 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * Information about a particular block of CDDA data. + */ +struct cdda_block { + unsigned char status; /* see below */ + unsigned char track; + unsigned char index; + unsigned char minute; + unsigned char second; + unsigned char frame; + + /* Average volume levels, for level meters */ + unsigned char lev_chan0; + unsigned char lev_chan1; + + /* Current volume setting (0-255) */ + unsigned char volume; + + /* Current balance setting (0-255, 128 = balanced) */ + unsigned char balance; +}; + +/* + * cdda_block status codes. + */ +#define WMCDDA_ERROR 0 /* Couldn't read CDDA from disc */ +#define WMCDDA_OK 1 /* Read this block successfully (raw data) */ +#define WMCDDA_PLAYED 2 /* Just played the block in question */ +#define WMCDDA_STOPPED 3 /* Block data invalid; we've just stopped */ +#define WMCDDA_ACK 4 /* Acknowledgement of command from parent */ +#define WMCDDA_DONE 5 /* Chunk of data is done playing */ +#define WMCDDA_EJECTED 6 /* Disc ejected or offline */ + +/* + * Enable or disable CDDA building depending on platform capabilities, and + * determine endianness based on architecture. (Gross!) + * + * For header-comfort, the macros LITTLE_ENDIAN and BIG_ENDIAN had to be + * renamed. At least Linux does have bytesex.h and endian.h for easy + * byte-order examination. + */ + +#ifdef sun +# ifdef SYSV +# include <sys/types.h> +# include <sys/cdio.h> +# ifndef CDROMCDDA +# undef BUILD_CDDA +# endif +# ifdef i386 +# define WM_LITTLE_ENDIAN 1 +# define WM_BIG_ENDIAN 0 +# else +# define WM_BIG_ENDIAN 1 +# define WM_LITTLE_ENDIAN 0 +# endif +# else +# undef BUILD_CDDA +# endif +#endif + +/* Linux only allows definition of endianness, because there's no + * standard interface for CDROM CDDA functions that aren't available + * if there is no support. + */ +#ifdef linux +# include <bytesex.h> +# include <endian.h> +/* + * XXX could this be a problem? The results are only 0 and 1 because + * of the ! operator. How about other linux compilers than gcc ? + */ +# define WM_LITTLE_ENDIAN !(__BYTE_ORDER - __LITTLE_ENDIAN) +# define WM_BIG_ENDIAN !(__BYTE_ORDER - __BIG_ENDIAN) +#endif + +/* + * The following code shouldn't take effect now. + * In 1998, the WorkMan platforms don't support __PDP_ENDIAN + * architectures. + * + */ + +#if !WM_LITTLE_ENDIAN +# if !WM_BIG_ENDIAN +# error yet unsupported architecture + foo bar this is to stop the compiler. +# endif +#endif +#endif /* WM_CDDA_H */ diff --git a/ascd/ascd/workman/wm_cddb.h b/ascd/ascd/workman/wm_cddb.h new file mode 100644 index 0000000..bd5b4f9 --- /dev/null +++ b/ascd/ascd/workman/wm_cddb.h @@ -0,0 +1,42 @@ +#ifndef WM_CDDB_H +#define WM_CDDB_H +/* + * $Id: wm_cddb.h,v 1.2 1999/02/14 09:50:46 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +extern struct wm_cddb cddb; +extern int cur_cddb_protocol; +extern char *cur_cddb_server, *cur_cddb_mail_adress, + *cur_cddb_path_to_cgi, *cur_cddb_proxy_server; + +unsigned int cddb_discid(); +void cddb_struct2cur(); +void cddb_cur2struct(); +void cddb_select(); +void connect_cddb(); +void update_cddbserver(); +void cddb_request(); + +#endif /* WM_CDDB_H */ diff --git a/ascd/ascd/workman/wm_cdinfo.h b/ascd/ascd/workman/wm_cdinfo.h new file mode 100644 index 0000000..f783465 --- /dev/null +++ b/ascd/ascd/workman/wm_cdinfo.h @@ -0,0 +1,78 @@ +#ifndef WM_CDINFO_H +#define WM_CDINFO_H +/* + * $Id: wm_cdinfo.h,v 1.5 1999/05/28 03:35:58 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Prototypes from cdinfo.c + * + * This is just one more step to a more modular and understandable code. + */ + +extern char *cur_trackname; /* Take a guess */ +extern int cur_index; /* Current index mark */ +extern int cur_frame; /* Current frame number */ +extern struct wm_play *playlist; /* = NULL */ +extern struct wm_cdinfo thiscd; +extern struct wm_cdinfo *cd; +extern int cur_track; /* Current track number, starting at 1 */ +extern char *cur_artist; /* Name of current CD's artist */ +extern char cur_avoid; /* Avoid flag */ +extern char cur_contd; /* Continued flag */ +extern char *cur_cdname; /* Album name */ +extern int cur_nsections; /* Number of sections currently defined */ +extern int exit_on_eject; +extern int cur_track; +extern int cur_pos_abs; +extern int cur_pos_rel; +extern int cur_tracklen; +extern int cur_cdlen; +extern enum wm_cd_modes cur_cdmode; +extern int cur_ntracks; +extern int cur_lasttrack; +extern int cur_firsttrack; +extern int cur_listno; +extern int cur_stopmode; + +void wipe_cdinfo( void ); +void play_next_entry( int forward ); +void make_playlist( int playmode, int starttrack ); +int get_autoplay( void ); +int get_playmode( void ); +void pl_find_track( int track ); +void play_prev_track( int forward ); +void play_next_track( int forward ); +int tracklen( int num ); +int get_default_volume( int track ); +int split_trackinfo( int pos ); +int remove_trackinfo( int num ); +void freeup( char **x ); +int get_runtime( void ); +char *trackname( int num ); +void stash_cdinfo( char *artist, char *cdname, int autoplay, int playmode ); +void stash_trkinfo( int track, char *songname, int contd, int avoid ); +int get_avoid( int num ); +int get_contd( int num ); +void default_volume( int track, int vol ); +char *listentry( int num ); + +#endif /* WM_CDINFO_H */ diff --git a/ascd/ascd/workman/wm_cdrom.h b/ascd/ascd/workman/wm_cdrom.h new file mode 100644 index 0000000..7d0dca4 --- /dev/null +++ b/ascd/ascd/workman/wm_cdrom.h @@ -0,0 +1,64 @@ +#ifndef WM_CDROM_H +#define WM_CDROM_H +/* + * $Id: wm_cdrom.h,v 1.6 1999/05/05 16:09:55 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Prototypes from cdrom.c + * + * This is just one more step to a more modular and understandable code. + */ + +#define WM_CDS_NO_DISC 0 +#define WM_CDS_DISC_READY 1 +#define WM_CDS_JUST_INSERTED 2 + +#define WM_STR_GENVENDOR "Generic" +#define WM_STR_GENMODEL "drive" +#define WM_STR_GENREV "type" + +extern int wm_cd_cur_balance; + + +char * wm_drive_vendor( void ); +char * wm_drive_model( void ); +char * wm_drive_revision( void ); +void wm_drive_settype( char *vendor, char *model, char *revision ); + +int wm_cd_status( void ); + +void wm_cd_play( int start, int pos, int end ); +void wm_cd_play_chunk( int start, int end, int realstart ); +void wm_cd_play_from_pos( int pos ); +void wm_cd_pause( void ); +void wm_cd_stop( void ); +int wm_cd_eject( void ); +int wm_cd_closetray( void ); +int wm_cd_read_initial_volume( int max ); + +/* + * Following are the missing to rename. + */ +int find_trkind( int track, int index, int start ); +void cd_volume( int vol, int bal, int max ); + +#endif /* WM_CDROM_H */ diff --git a/ascd/ascd/workman/wm_config.h b/ascd/ascd/workman/wm_config.h new file mode 100644 index 0000000..c04a0e5 --- /dev/null +++ b/ascd/ascd/workman/wm_config.h @@ -0,0 +1,357 @@ +#ifndef WM_CONFIG_H +#define WM_CONFIG_H +/* + * $Id: wm_config.h,v 1.5 1999/05/05 16:34:22 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + ********************************************************************** + * + * This file consists of several parts. First, there's a generic, + * platform independent part. Set needed options there. + * The following parts are platform dependent. You may search for the + * names listed below and then set your OS specific options there. + * Don't be surprised, if there are no options for your OS. They aren't + * needed in any case. + * + * The default values should produce a functional WorkMan on every + * platform. + * + ********************* + * Current platforms: + ********************* + * BSD386 + * FreeBSD + * HP-UX + * Irix (SGI) + * Linux + * News (Sony NewsOS) + * OpenBSD + * OSF1 + * Sun (SunOS/Solaris, Sparc or x86) + * SVR4 + * Ultrix + * AIX + * + * The above order corresponds with the order of the platform specific + * options below. + */ + +/****************************************************************** + * generic options + ****************************************************************** + ** ** *** **** ** **** ** * ** ** ** * ** + * * * *** **** * * *** * ** ** *** * * * * * * ** + * * *** **** ** *** ** ** ** * * ** * * ** + * * * *** **** **** *** * ** ** *** * * * * *** ** + * * * * ** **** * * ** ** **** ** * * *** ** + ******************************************************************/ + +/* + * This option is obvious. But please do not forget the original + * WorkMan version string if you need support. + */ + +#define WORKMAN_NAME "LibWorkMan" +#define WORKMAN_VERSION "1.4.0" + +/* + * If your CD-ROM drive closes its tray if the device is opened, then + * the next define can make WorkMans "Eject" button an "open/close" + * button. If it disturbs you, just comment it out. + * + * ### this is preliminary. It may have no effect for you ### + */ +#define CAN_CLOSE 1 + +/* + * Define the following if you want the balance slider to + * decrease one channel's volume while increasing the other's + */ +/* #define SYMETRIC_BALANCE 1 */ + + +/* + * Define this if you want CDDA support. Supported systems are: + * + * - Solaris (2.4 or newer) + * --> Linux is on the way. Don't define it now. It would lead to errors only. + */ +/*#define BUILD_CDDA 1*/ + +/* + * + * This is for experimental cddb support. + * This activates the UI component. + */ + +#define CDDB_IN_UI 1 + + +/****************************************************************** + * BSD386 + ****************************************************************** + *** **** *** ******* ** **** **** ************ + *** ** ** ****** * ***** ****** ** ** ** *************** + *** ****** **** ** *** ***** ***** *** ************ + *** ** ****** ** * *** ******** ** ** ** ** *********** + *** **** *** *** ****** **** **** ************ + ******************************************************************/ +#ifdef __bsdi__ + +/* + * This lets you use the SoundBlaster Mixer on BSD/386 + */ +#define SOUNDBLASTER 1 + +#endif /* __bsdi__ (BSD/386) */ + +/****************************************************************** + * FreeBSD + ****************************************************************** + *** ** *** ** ** **** *** *********** + *** ****** ** ** ****** ****** ** ** ****** * ********** + *** **** *** **** **** ****** **** ** ********* + *** ****** ** ** ****** ****** ** ****** ** * ********** + *** ****** ** ** ** ** **** *** *********** + ******************************************************************/ +#if defined(__FreeBSD__) || defined(__NetBSD__) + +#define DEFAULT_CD_DEVICE "/dev/rmatcd0c" + +#endif /* freebsd */ + +/****************************************************************** + * HP-UX + ****************************************************************** + *** ** ** ********* ** ** ** *************************** + *** ** ** ** ******** ** *** **************************** + *** ** *** ** ** **** ***************************** + *** ** ** ************ ** *** **************************** + *** ** ** ************* *** ** *************************** + ******************************************************************/ +#if defined(hpux) || defined (__hpux) + +#define DEFAULT_CD_DEVICE "/dev/rscsi" + +#endif /* hpux */ + +/****************************************************************** + * Irix + ****************************************************************** + *** ** *** ** ** ********************************* + ***** **** ** **** ***** ********************************** + ***** **** ***** ****** *********************************** + ***** **** ** **** ***** ********************************** + *** ** ** ** ** ** ********************************* + ******************************************************************/ +#if defined(sgi) + +#define DEFAULT_CD_DEVICE "/dev/scsi/sc0d6l0" + +#endif /* sgi IRIX */ + +/****************************************************************** + * Linux + ****************************************************************** + *** ****** ** *** ** ** ** ** *********************** + *** ******** **** ** ** ** *** ************************ + *** ******** **** * * ** ** **** ************************* + *** ******** **** ** ** ** *** ************************ + *** ** ** *** *** *** ** *********************** + ******************************************************************/ +#ifdef linux + +/* + * Uncomment the following line to have WorkMan send SCSI commands + * directly to the CD-ROM drive. If you have a SCSI drive you + * probably want this, but it will cause WorkMan to not work on IDE + * drives. + */ +#define LINUX_SCSI_PASSTHROUGH 1 + +/* + * Which device should be opened by WorkMan at default? + */ +#define DEFAULT_CD_DEVICE "/dev/sbpcd" + +/* + * Uncomment the following if you use the sbpcd or mcdx device driver. + * It shouldn't hurt if you use it on other devices. It'll be nice to + * hear from non-sbpcd (or mcdx) users if this is right. + */ +/*#define SBPCD_HACK 1*/ + +/* + * Linux Soundcard support + */ +#define OSS_SUPPORT 1 + +/* + * This has nothing to do with the above. + */ + +/* #define CURVED_VOLUME */ + +/* + * Uncomment the following if you want to try out a better responding + * WorkMan, especially with IDE drives. This may work with non-IDE + * drives as well. But it may be possible, that it doesn't work at all. + * If your drive/driver combination cannot handle the faster access, + * the driver will usually hang and you have to reboot your machine. + */ +/* #define FAST_IDE 1 */ + +/* + * There are two alternative ways of checking a device containing a + * mounted filesystem. Define BSD_MOUNTTEST for the test using + * getmntent(). Undefine it for using the SVR4 ustat(). + * I built in the choice, because it's not clear which method should + * be used in Linux. The ustat manpage tells us since 1995, that + * fstat() should be used, but I'm too dumb to do so. + */ + +#define BSD_MOUNTTEST + +#endif /* linux */ + +/****************************************************************** + * Sony NewsOS + ****************************************************************** + *** *** ** ** ***** *** ***************************** + *** ** ** ****** ***** ** ******************************** + *** * * ** **** ** ** **** ****************************** + *** ** ** ****** * * ****** **************************** + *** *** ** ** * *** ***************************** + ******************************************************************/ +#if defined(__sony_news) || defined(sony_news) + +#define DEFAULT_CD_DEVICE "/dev/rsd/b0i6u0p2\0" + +#endif + +/****************************************************************** + * OpenBSD + ****************************************************************** + **** *** *** ** *** ** **** *** ********* + *** ** ** ** ** ****** ** ** ** ** ****** * ******** + *** ** ** *** **** * * ** ****** **** ** ******* + *** ** ** ****** ****** ** ** ** ****** ** * ******** + **** *** ****** ** *** ** **** *** ********* + ******************************************************************/ +#ifdef __OpenBSD__ + +#define DEFAULT_CD_DEVICE "/dev/rcdrom" + +#endif + +/****************************************************************** + * OSF1 + ****************************************************************** + **** **** *** *** *** ******************************* + *** ** ** ****** ****** ** ******************************* + *** ** **** **** *** ***** ******************************* + *** ** ****** ** **** ****** ******************************* + **** **** *** *** ***** ***************************** + ******************************************************************/ +#if defined(__osf__) + +#define DEFAULT_CD_DEVICE "/dev/rcdrom/cd0" + +#endif + +/****************************************************************** + * SunOS/Solaris + ****************************************************************** + **** *** ** ** *** *************************************** + *** ****** ** ** ** *************************************** + ***** **** ** ** * * *************************************** + ******* ** ** ** ** *************************************** + **** **** *** *** *************************************** + ******************************************************************/ +#if defined(sun) || defined(__sun__) + +/* + * Define the following for Solaris 2.x + * If you don't want WorkMan to try to activate the SPARCstation 5 + * internal audio input so you get sound from the workstation, comment + * out the CODEC define. + */ + +#define SYSV 1 +#define CODEC 1 + +/* + * set the following to "SUNW,CS4231" for Sun and to "SUNW,sb16" + * for PC (with SoundBlaster 16) running Solaris x86 + * (only important if you define CODEC above) + */ +/*#define SUN_AUD_DEV "SUNW,CS4231"*/ +#define SUN_AUD_DEV "SUNW,sbpro" + + +#endif + +/****************************************************************** + * SVR4 + ****************************************************************** + **** *** **** ** *** * ******************************** + *** ****** **** ** ** ** * ******************************** + ***** ***** ** *** *** ******************************* + ******* *** ** *** ** ***** ******************************** + **** ***** **** ** ***** ******************************** + ******************************************************************/ +#if defined(SVR4) && !defined(sun) && !defined(__sun__) + +#define DEFAULT_CD_DEVICE "/dev/rcdrom/cd0" + +#endif + +/****************************************************************** + * Ultrix + ****************************************************************** + *** ** ** ***** ** *** ** ** ****************** + *** ** ** ******* **** ** **** ***** ******************* + *** ** ** ******* **** ***** ****** ******************** + *** ** ** ******* **** ** **** ***** ******************* + **** *** *** **** ** ** ** ** ****************** + ******************************************************************/ +#if defined(ultrix) || defined(__ultrix) + +#endif + +/****************************************************************** + * IBM AIX + ****************************************************************** + **** *** ** ** ***************************************** + *** ** **** ***** ****************************************** + *** **** ****** ******************************************* + *** ** **** ***** ****************************************** + *** ** ** ** ** ***************************************** + ******************************************************************/ +#if defined(AIXV3) + +#define DEFAULT_CD_DEVICE "/dev/cd0" + +#endif /* IBM AIX */ + +/******************************************************************/ +#endif /* WM_CONFIG_H */ diff --git a/ascd/ascd/workman/wm_database.h b/ascd/ascd/workman/wm_database.h new file mode 100644 index 0000000..aaea086 --- /dev/null +++ b/ascd/ascd/workman/wm_database.h @@ -0,0 +1,44 @@ +#ifndef WM_DATABASE_H +#define WM_DATABASE_H +/* + * $Id: wm_database.h,v 1.3 1999/02/14 09:50:47 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Prototypes for WorkMan database + * + * This is just one more step to a more modular and understandable code. + */ + + +#define WM_DB_SAVE_ERROR 1 +#define WM_DB_SAVE_DISABLED 2 + +int wm_db_get_playnew( void ); +void split_workmandb( void ); +int save( void ); +void load( void ); +void load_settings( void ); + +extern int wm_db_save_disabled; +extern int cur_playnew; + +#endif /* WM_DATABASE_H */ diff --git a/ascd/ascd/workman/wm_helpers.h b/ascd/ascd/workman/wm_helpers.h new file mode 100644 index 0000000..0a3763b --- /dev/null +++ b/ascd/ascd/workman/wm_helpers.h @@ -0,0 +1,105 @@ +#ifndef WM_HELPERS_H +#define WM_HELPERS_H +/* + * $Id: wm_helpers.h,v 1.6 1999/03/07 08:36:44 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Here to be found: Prototypes. Including variable names to be easier + * to read. + * This is just one more step to a more modular and understandable code. + * + */ + +/* + * LibWorkMan message levels. I'm not sure how to call them all and which + * use they should fulfill. This is not very urgent now, because there + * aren't many messages in LibWorkMan now. + */ +#define WM_MSG_LEVEL_NONE 0 +#define WM_MSG_LEVEL_ERROR 1 +#define WM_MSG_LEVEL_TWO 2 +#define WM_MSG_LEVEL_THREE 3 +#define WM_MSG_LEVEL_FOUR 4 +#define WM_MSG_LEVEL_INFO 5 +#define WM_MSG_LEVEL_SIX 6 +#define WM_MSG_LEVEL_SEVEN 7 +#define WM_MSG_LEVEL_EIGHT 8 +#define WM_MSG_LEVEL_DEBUG 9 + +/* + * Message classes. This is somehow a definition of + * the message's source. + */ + +#define WM_MSG_CLASS_PLATFORM 0x010 +#define WM_MSG_CLASS_SCSI 0x020 +#define WM_MSG_CLASS_CDROM 0x040 +#define WM_MSG_CLASS_DB 0x080 +#define WM_MSG_CLASS_MISC 0x100 + +#define WM_MSG_CLASS_ALL 0xff0 + +extern int wm_lib_verbosity; + +/* + * I did not know any better place... + */ +#ifdef DEBUG +#define CHECKPOINT(t) fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, t ); +#else +#define CHECKPOINT(t) +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifdef linux +#include <signal.h> +/* Linux doesn't have a SIGEMT */ +#if !defined( SIGEMT ) +# define SIGEMT SIGUNUSED +#endif +#endif /* linux */ + +void freeup( char **x ); +void wm_strmcat( char **t, char *s); +void wm_strmcpy( char **t, char *s ); +char * wm_strdup( char *s ); +/* Somebody's version query unsatisfied? */ +int wm_libver_major( void ); /* return major internal version number */ +int wm_libver_minor( void ); /* return minor internal version number */ +int wm_libver_pl( void ); /* return internal patchlevel number */ +char * wm_libver_name( void ); /* return internal name (LibWorkMan) */ +char * wm_libver_number( void ); /* returns string: "<major>.<minor>.<pl>" */ +char * wm_libver_string( void ); /* returns string: "<name> <number>" */ +char * wm_libver_date( void ); /* returns string: date of compilation */ +void wm_lib_set_verbosity( int level ); /* set verbosity level */ +int wm_lib_get_verbosity( void ); /* get verbosity level */ +void wm_lib_message( unsigned int level, char *format, ... ); /* put out a message on stderr */ +int wm_susleep( int usec ); + +#endif /* WM_HELPERS_H */ diff --git a/ascd/ascd/workman/wm_index.h b/ascd/ascd/workman/wm_index.h new file mode 100644 index 0000000..ce14ac3 --- /dev/null +++ b/ascd/ascd/workman/wm_index.h @@ -0,0 +1,36 @@ +#ifndef WM_INDEX_H +#define WM_INDEX_H +/* + * $Id: wm_index.h,v 1.2 1999/02/14 09:50:47 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Prototypes for wm_index.c + * + * This is just one more step to a more modular and understandable code. + */ + +int idx_find_entry( char *file, int ntracks, int *tracks, + int len, int fuzz, unsigned long *pos ); +int idx_delete_entry(char *file, int track, int fuzz, unsigned long pos ); +int idx_write_entry( char *file, int track, unsigned long pos ); + +#endif /* WM_INDEX_H */ diff --git a/ascd/ascd/workman/wm_platform.h b/ascd/ascd/workman/wm_platform.h new file mode 100644 index 0000000..03f957d --- /dev/null +++ b/ascd/ascd/workman/wm_platform.h @@ -0,0 +1,51 @@ +#ifndef WM_PLATFORM_H +#define WM_PLATFORM_H +/* + * $Id: wm_platform.h,v 1.3 1999/02/14 09:50:47 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * The platform interface + * + * This is just one more step to a more modular and understandable code. + */ + +#include "wm_struct.h" + +int wmcd_open( struct wm_drive *d ); +int wmcd_reopen( struct wm_drive *d ); + +/* + * void keep_cd_open( void ); + */ +int wm_scsi( struct wm_drive *d, unsigned char *cdb, int cdblen, + void *retbuf, int retbuflen, int getreply ); + +/**************************************** + * + * The drive prototypes. + * + */ +extern struct wm_drive generic_proto; +extern struct wm_drive sony_proto; +extern struct wm_drive toshiba_proto; + +#endif /* WM_PLATFORM_H */ diff --git a/ascd/ascd/workman/wm_scsi.h b/ascd/ascd/workman/wm_scsi.h new file mode 100644 index 0000000..8763a62 --- /dev/null +++ b/ascd/ascd/workman/wm_scsi.h @@ -0,0 +1,48 @@ +#ifndef WM_SCSI_H +#define WM_SCSI_H +/* + * $Id: wm_scsi.h,v 1.3 1999/02/14 09:50:47 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * SCSI prototypes (scsi.c) + * + * This is just one more step to a more modular and understandable code. + */ + +#include "wm_struct.h" + +#define WM_ERR_SCSI_INQUIRY_FAILED -1 + +int wm_scsi_mode_sense( struct wm_drive *d, unsigned char page, + unsigned char *buf ); +int sendscsi( struct wm_drive *d, void *buf, + unsigned int len, int dir, + unsigned char a0, unsigned char a1, + unsigned char a2, unsigned char a3, + unsigned char a4, unsigned char a5, + unsigned char a6, unsigned char a7, + unsigned char a8, unsigned char a9, + unsigned char a10, unsigned char a11 ); + int wm_scsi_get_drive_type( struct wm_drive *d, char *vendor, + char *model, char *rev ); + +#endif /* WM_SCSI_H */ diff --git a/ascd/ascd/workman/wm_struct.h b/ascd/ascd/workman/wm_struct.h new file mode 100644 index 0000000..ae1b72a --- /dev/null +++ b/ascd/ascd/workman/wm_struct.h @@ -0,0 +1,181 @@ +#ifndef WM_STRUCT_H +#define WM_STRUCT_H +/* + * $Id: wm_struct.h,v 1.7 1999/05/28 03:35:58 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + + +/* + * Structure for a single track. This is pretty much self-explanatory -- + * one of these exists for each track on the current CD. + */ +struct wm_trackinfo +{ + char *songname; /* Name of song, dynamically allocated */ + char *otherdb; /* Unrecognized info for this track */ + char *otherrc; + int length; /* Length of track in seconds or Kbytes */ + int start; /* Starting position (f+s*75+m*60*75) */ + int volume; /* Per-track volume (1-32, 0 to disable) */ + int track; /* Physical track number */ + int section; /* Section number (0 if track not split) */ + int contd; /* Flag: continuation of previous track */ + int avoid; /* Flag: don't play this track. */ + int data; /* Flag: data track */ +}; + +/* + * Structure for internal playlist management. The internal playlist is + * simply the list of track ranges that are being played currently. This + * is built whenever the CD starts playing; it's used in normal and shuffle + * modes as well as playlist mode. + * + * The "starttime" element represents how much time has elapsed by the time + * we get to this entry. For instance, if the list begins with a 5-minute + * track and a 3-minute track, the third entry would have a starttime of 8 + * minutes. This is used so that the elapsed play time can be displayed + * even in shuffle or playlist modes. + * + * The last member of the list has a start track of 0, and its starttime is + * the total playing time of the playlist (which will usually be overestimated, + * since we don't play leadouts in some cases.) + */ +struct wm_play +{ + int start; /* Start track, or 0 if end of list */ + int end; /* last track plus 1 */ + int starttime; /* Number of seconds elapsed previously */ +}; + +/* + * Structure for playlists (as seen by the user.) This is simply a name + * followed by a zero-terminated list of track numbers to play. The list + * is terminated by a NULL name. + */ +struct wm_playlist +{ + char *name; /* Name of this playlist */ + int *list; /* List of tracks */ +}; + +struct wm_cdinfo +{ + char artist[84]; /* Artist's name */ + char cdname[84]; /* Disc's name */ + int ntracks; /* Number of tracks on the disc */ + int length; /* Total running time in seconds */ + int autoplay; /* Start playing CD immediately */ + int playmode; /* How to play the CD */ + int volume; /* Default volume (1-32, 0 for none) */ + struct wm_trackinfo *trk; /* struct wm_trackinfo[ntracks] */ + struct wm_playlist *lists; /* User-specified playlists */ + char *whichdb; /* Which database is this entry from? */ + char *otherdb; /* Unrecognized lines from this entry */ + char *otherrc; + char *user; /* Name of originating user */ + unsigned int cddbid; /* CDDB-ID of the current disc */ + struct cdinfo *next; /* For browsers, etc. */ +}; + +/* The global variable "cd" points to the struct for the CD that's playing. */ +extern struct wm_cdinfo *cd; + +struct wm_playlist *new_list(); + +enum wm_cd_modes +{ + WM_CDM_UNKNOWN = -1, + WM_CDM_BACK = 0, WM_CDM_TRACK_DONE = 0, + WM_CDM_PLAYING = 1, + WM_CDM_FORWARD = 2, + WM_CDM_PAUSED = 3, + WM_CDM_STOPPED = 4, + WM_CDM_EJECTED = 5 +}; + +/* + * Drive descriptor structure. Used for access to low-level routines. + */ +struct wm_drive +{ + int fd; /* File descriptor, if used by platform */ + char vendor[32]; /* Vendor name */ + char model[32]; /* Drive model */ + char revision[32]; /* Revision of the drive */ + void *aux; /* Pointer to optional platform-specific info */ + void *daux; /* Pointer to optional drive-specific info */ + + int (*init)(); + int (*get_trackcount)(); + int (*get_cdlen)(); + int (*get_trackinfo)(); + int (*get_drive_status)(); + int (*get_volume)(); + int (*set_volume)(); + int (*pause)(); + int (*resume)(); + int (*stop)(); + int (*play)(); + int (*eject)(); + int (*closetray)(); +}; + +/* + * Structure for information of the usage of cddb. + */ +struct wm_cddb { + int protocol; /* 0-off, 1-cddbp, 2-http, 3-htproxy */ + char cddb_server[84]; /* host.domain.name:port */ + char mail_adress[84]; /* [email protected] */ + char path_to_cgi[84]; /* (/)path/to/cddb.cgi */ + char proxy_server[84]; /* host.domain.name:port */ +}; +extern struct wm_cddb cddb; + + +/* + * Each platform has to define generic functions, so may as well declare + * them all here to save space. + * These functions should never be seen outside libworkman. So I don't care + * about the wm_ naming convention here. + */ +int gen_init(), + gen_get_trackcount(), + gen_get_cdlen(), + gen_get_trackinfo(), + gen_get_drive_status(), + gen_get_volume(), + gen_set_volume(), + gen_pause(), + gen_resume(), + gen_stop(), + gen_play(), + gen_eject(), + gen_closetray(); + +struct wm_drive *find_drive_struct(); + + +#endif /* WM_STRUCT_H */ diff --git a/ascd/ascd/workman/wm_version.h b/ascd/ascd/workman/wm_version.h new file mode 100644 index 0000000..b18a2e2 --- /dev/null +++ b/ascd/ascd/workman/wm_version.h @@ -0,0 +1,35 @@ +#ifndef WM_VERSION_H +#define WM_VERSION_H +/* + * $Id: wm_version.h,v 1.2 1999/05/28 03:35:58 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Version information + * + */ + +#define WM_LIBVER_MAJOR 1 +#define WM_LIBVER_MINOR 4 +#define WM_LIBVER_PL 2 +#define WM_LIBVER_NAME "LibWorkMan" + +#endif /* WM_VERSION_H */ diff --git a/ascd/ascd/workman/workman.h b/ascd/ascd/workman/workman.h new file mode 100644 index 0000000..2172b9f --- /dev/null +++ b/ascd/ascd/workman/workman.h @@ -0,0 +1,51 @@ +#ifndef WORKMAN_H +#define WORKMAN_H +/* + * $Id: workman.h,v 1.5 1999/05/05 16:34:22 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * all-in-one libworkman include file. + * + */ + + +/* + * wm_config should always be included first + */ +#include "wm_config.h" + +#include "workman_defs.h" +#ifdef BUILD_CDDA +#include "wm_cdda.h" +#endif +#include "wm_cddb.h" +#include "wm_cdinfo.h" +#include "wm_cdrom.h" +#include "wm_database.h" +#include "wm_helpers.h" +#include "wm_index.h" +#include "wm_platform.h" +#include "wm_scsi.h" +#include "wm_struct.h" + +#endif /* WORKMAN_H */ + diff --git a/ascd/ascd/workman/workman_defs.h b/ascd/ascd/workman/workman_defs.h new file mode 100644 index 0000000..712a414 --- /dev/null +++ b/ascd/ascd/workman/workman_defs.h @@ -0,0 +1,30 @@ +#ifndef WORKMAN_DEFS_H +#define WORKMAN_DEFS_H +/* + * $Id: workman_defs.h,v 1.4 1999/02/14 09:50:47 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player program + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * 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. + * + * #defined CONSTANTS + * + * Too bad this file seems to be so empty... + * + */ + +#include "wm_version.h" + +#endif /* WORKMAN_DEFS_H */ diff --git a/ascd/configure b/ascd/configure new file mode 100755 index 0000000..d533465 --- /dev/null +++ b/ascd/configure @@ -0,0 +1,9 @@ +#!/bin/sh + +cd ascd +rm -f workman +ln -s ../libworkman/include workman +./configure +cd .. +xmkmf -a + diff --git a/ascd/default b/ascd/default new file mode 100755 index 0000000..246ca52 --- /dev/null +++ b/ascd/default @@ -0,0 +1,9 @@ +#!/bin/sh + +cd ascd +rm -f workman +ln -s ../libworkman/include workman +./misc/default +cd .. +xmkmf -a + diff --git a/ascd/libworkman/COPYING.LIB b/ascd/libworkman/COPYING.LIB new file mode 100644 index 0000000..92b8903 --- /dev/null +++ b/ascd/libworkman/COPYING.LIB @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/ascd/libworkman/Config b/ascd/libworkman/Config new file mode 100644 index 0000000..7db9048 --- /dev/null +++ b/ascd/libworkman/Config @@ -0,0 +1,48 @@ +XCOMM ------------------------------------------------------------------- +XCOMM libworkman installation configuration +XCOMM ------------------------------------------------------------------- +XCOMM +XCOMM This file is part of LibWorkMan, the civilized CD player library +XCOMM (c) 1991-1997 by Steven Grimm (original author) +XCOMM (c) by Dirk Försterling (current 'author' = maintainer) +XCOMM The maintainer can be contacted by his e-mail address: +XCOMM [email protected] +XCOMM +XCOMM This library is free software; you can redistribute it and/or +XCOMM modify it under the terms of the GNU Library General Public +XCOMM License as published by the Free Software Foundation; either +XCOMM version 2 of the License, or (at your option) any later version. +XCOMM +XCOMM This library is distributed in the hope that it will be useful, +XCOMM but WITHOUT ANY WARRANTY; without even the implied warranty of +XCOMM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +XCOMM Library General Public License for more details. +XCOMM +XCOMM You should have received a copy of the GNU Library General Public +XCOMM License along with this library; if not, write to the Free +XCOMM Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +XCOMM +XCOMM ------------------------------------------------------------------- +XCOMM +XCOMM This configures the compilation and installation +XCOMM processes only. To configure libworkman to your +XCOMM needs, please review the file include/wm_config.h +XCOMM +XCOMM + +XCOMM ------------------------------------------------------------------- +XCOMM Directory Prefix +XCOMM ------------------------------------------------------------------- +DESTDIR=/usr/local/ + +XCOMM ------------------------------------------------------------------- +XCOMM LibWorkMan include file target relative to DSTDIR +XCOMM ------------------------------------------------------------------- +WMINCDIR=include/workman + + + +XCOMM ------------------------------------------------------------------- +#define SHARED_LIBWORKMAN +LIBDIR=lib + diff --git a/ascd/libworkman/Imakefile b/ascd/libworkman/Imakefile new file mode 100644 index 0000000..c0f26ac --- /dev/null +++ b/ascd/libworkman/Imakefile @@ -0,0 +1,74 @@ +XCOMM ------------------------------------------------------------------- +XCOMM libworkman Imakefile +XCOMM ------------------------------------------------------------------- +XCOMM +XCOMM This file is part of LibWorkMan, the civilized CD player library +XCOMM (c) 1991-1997 by Steven Grimm (original author) +XCOMM (c) by Dirk Försterling (current 'author' = maintainer) +XCOMM The maintainer can be contacted by his e-mail address: +XCOMM [email protected] +XCOMM +XCOMM This library is free software; you can redistribute it and/or +XCOMM modify it under the terms of the GNU Library General Public +XCOMM License as published by the Free Software Foundation; either +XCOMM version 2 of the License, or (at your option) any later version. +XCOMM +XCOMM This library is distributed in the hope that it will be useful, +XCOMM but WITHOUT ANY WARRANTY; without even the implied warranty of +XCOMM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +XCOMM Library General Public License for more details. +XCOMM +XCOMM You should have received a copy of the GNU Library General Public +XCOMM License along with this library; if not, write to the Free +XCOMM Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +XCOMM +XCOMM ------------------------------------------------------------------- +XCOMM +XCOMM Please edit the file "Config" for paths. +XCOMM + +#include <Config> + +#include "include/wm_version.h" + +WM_LIBVER=WM_LIBVER_MAJOR.WM_LIBVER_MINOR.WM_LIBVER_PL + +SRCS = cddb.c cdrom.c cdinfo.c plat_freebsd.c plat_sun.c plat_hpux.c \ + plat_ultrix.c plat_news.c plat_bsd386.c plat_osf1.c plat_linux.c \ + plat_aix.c plat_openbsd.c plat_irix.c plat_svr4.c \ + drv_sony.c drv_toshiba.c scsi.c database.c index.c \ + wm_helpers.c + +OBJS = cddb.o cdrom.o cdinfo.o plat_freebsd.o plat_sun.o plat_hpux.o \ + plat_ultrix.o plat_news.o plat_bsd386.o plat_osf1.o plat_linux.o \ + plat_aix.o plat_openbsd.o plat_irix.o plat_svr4.o \ + drv_sony.o drv_toshiba.o scsi.o database.o index.o \ + wm_helpers.o + +HEADERS= \ + include/wm_cdda.h \ + include/wm_cdinfo.h \ + include/wm_cdrom.h \ + include/wm_config.h \ + include/wm_database.h \ + include/wm_helpers.h \ + include/wm_index.h \ + include/wm_platform.h \ + include/wm_scsi.h \ + include/wm_struct.h \ + include/wm_version.h \ + include/workman.h \ + include/workman_defs.h + + +#include <Library.tmpl> + +#ifdef SHARED_LIBWORKMAN +SharedLibraryTarget(workman,$(WM_LIBVER),$(OBJS),.,.) +InstallSharedLibrary(workman,$(WM_LIBVER),$(LIBDIR)) +#else +NormalLibraryTarget(workman, $(OBJS)) +#endif + +InstallMultiple($(HEADERS),$(WMINCDIR)) + diff --git a/ascd/libworkman/README b/ascd/libworkman/README new file mode 100644 index 0000000..87854ac --- /dev/null +++ b/ascd/libworkman/README @@ -0,0 +1,6 @@ +$Id: README,v 1.2 1999/02/14 09:53:54 dirk Exp $ + +This directory contains the WorkMan library. + +libworkman is a multi-plaform CD-Player library for creating various +CD-Player-UIs. diff --git a/ascd/libworkman/buildindex.c b/ascd/libworkman/buildindex.c new file mode 100644 index 0000000..05a042c --- /dev/null +++ b/ascd/libworkman/buildindex.c @@ -0,0 +1,223 @@ +/* + * $Id: buildindex.c,v 1.2 1999/02/14 09:50:42 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Build a WorkMan database index file from a flat text file. Requires + * 4.4BSD libdb library. + */ + +static char buildindex_id[] = "$Id: buildindex.c,v 1.2 1999/02/14 09:50:42 dirk Exp $"; + +#include <stdio.h> +#include <db.h> +#include <fcntl.h> +#include <errno.h> +#include <netinet/in.h> /* for htonl() */ +#include <sys/types.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <unistd.h> + +static char buildindex_id[]="$Id: buildindex.c,v 1.2 1999/02/14 09:50:42 dirk Exp $"; + +char *strrchr(); + +main(argc, argv) + int argc; + char **argv; +{ + DB *db; + DBT key, data; + FILE *fp; + int lock = 1, i = 0, locked, frame; + char buf[1000], indname[MAXPATHLEN + 100], framebuf[8], *c; + unsigned long pos; + BTREEINFO bt; + struct stat st; + + if (argc > 2 && !strcmp(argv[1], "-n")) + { + lock = 0; + i++; + } + + if (argc < i + 2) + { + fprintf(stderr, "Usage: %s [-n] dbfile [dbfile ...]\n", + argv[0]); + exit(1); + } + + data.data = &pos; + data.size = sizeof(pos); + key.data = framebuf; + key.size = 7; /* %07d */ + + while (++i < argc) + { + fprintf(stderr, "Building index for %s\n", argv[i]); + + if ((fp = fopen(argv[i], "r")) == NULL) + { + perror(argv[i]); + continue; + } + + /* + * Figure out the file's mode, uid, gid, so we can set the + * permissions on the index file to the same thing. + */ + if (fstat(fileno(fp), &st)) + { + sprintf(indname, "%s: fstat", argv[i]); + perror(indname); + fclose(fp); + continue; + } + + if (lock && lockit(fileno(fp), F_WRLCK)) + { + sprintf(indname, "%s: Warning: Couldn't lock", argv[i]); + perror(indname); + locked = 0; + } + else + locked = 1; + + /* + * Create a database file. + */ + bt.flags = R_DUP; /* allow duplicate keys */ + bt.cachesize = 0; + bt.psize = 0; + bt.lorder = 4321; + bt.minkeypage = 0; + bt.compare = NULL; /* use lexical comparison */ + bt.prefix = NULL; /* no prefix comparisons */ + + /* Index files have ".ind" extensions */ + sprintf(indname, "%s.ind", argv[i]); + if ((db = dbopen(indname, O_CREAT | O_RDWR | O_TRUNC, + st.st_mode, DB_BTREE, &bt)) == NULL) + { + perror(indname); + if (locked) + lockit(fileno(fp), F_UNLCK); + fclose(fp); + continue; + } + + /* + * Now loop through the text file, inserting a record into + * the index file for each "tracks" line. + */ + while (! feof(fp)) + { + pos = ftell(fp); + buf[0] = '\0'; + if (fgets(buf, sizeof(buf), fp) == NULL || ! buf[0]) + { + /* End of file? */ + if (feof(fp)) + break; + + /* Nope. A read error. Unlink the database. */ + perror(argv[i]); + (void) unlink(indname); + break; + } + + if (strncmp(buf, "tracks ", 7)) + continue; + + /* + * Found the start of a record. Figure out the start + * time of the last track and put an entry in the + * index file with that as the key. + */ + c = strrchr(buf, ' '); /* this will always succeed */ + *c = '\0'; + c = strrchr(buf, ' '); /* this should too, but... */ + if (c == NULL) + { + fprintf(stderr, + "%s: Malformed tracks line at %lu\n", + argv[i], pos); + continue; + } + sscanf(c+1, "%d", &frame); + sprintf(framebuf, "%07d", frame); + pos = htonl(pos); + + if ((db->put)(db, &key, &data, 0)) + { + perror(indname); + unlink(indname); + break; + } + } + + /* + * Clean up. + */ + (void) (db->close)(db); + if (locked) + lockit(fileno(fp), F_UNLCK); + } +} + +/* + * Lock a file. Time out after a little while if we can't get a lock; + * this usually means the locking system is broken. + * + * Unfortunately, if there are lots of people contending for a lock, + * this can result in the file not getting locked when it probably should. + */ +int +lockit(fd, type) + int fd; + int type; +{ + struct flock fl; + int result, timer = 0; + + fl.l_type = type; + fl.l_whence = 0; + fl.l_start = 0; + fl.l_len = 0; + + while ((result = fcntl(fd, F_SETLK, &fl)) < 0) + { + if (errno != EACCES || errno != EAGAIN) + break; + if (timer++ == 30) + { + errno = ETIMEDOUT; + break; + } + + sleep(1); + } + + return (result); +} diff --git a/ascd/libworkman/cddaslave.c b/ascd/libworkman/cddaslave.c new file mode 100644 index 0000000..bd85084 --- /dev/null +++ b/ascd/libworkman/cddaslave.c @@ -0,0 +1,565 @@ +/* + * $id$ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + ****************************************************************** + * + * Digital audio manipulator for WorkMan. + * + * The CDDA architecture looks like this: + * + * WorkMan (or another UI!) + * ^^^ + * ||| (separate processes connected by pipe) + * vvv + * +------------- cddaslave -------------+ + * | | | + * command module CDDA reader audio output + * (portable) (per platform) (per platform) + * + * This source file has the command module and some of the scaffolding + * to hold cddaslave together, plus some non-system-dependent audio + * processing code. Look in plat_*_cdda.c for system-specific stuff. + * + */ + +#include "config.h" + +#ifdef BUILD_CDDA + +static char cddaslave_id[] = "$Id: cddaslave.c,v 1.3 1999/02/14 22:10:24 dirk Exp $"; + +#include <stdio.h> +#include <sys/types.h> +#include <sys/time.h> +#include "include/wm_cdda.h" +#include "include/wm_platform.h" + + +int playing = 0; /* Should the CD be playing now? */ + +/* + * Loudness setting, plus the floating volume multiplier and decaying-average + * volume level. + */ +int loudness = 0; +unsigned int volume = 32768; +unsigned int level; + +/* + * Playback speed (0 = slow) + */ +int speed = 128; + +/* + * This is non-null if we're saving audio to a file. + */ +FILE *output = NULL; + +/* + * Audio file header format. + */ +typedef unsigned long u_32; +struct auheader { + u_32 magic; + u_32 hdr_size; + u_32 data_size; + u_32 encoding; + u_32 sample_rate; + u_32 channels; +}; + +/* had to change #ifdef to #if -> see wm_cdda.h */ +#if WM_BIG_ENDIAN +# ifndef htonl +# define htonl(x) (x) +# endif +#else +extern unsigned long htonl(x); +#endif + +void *malloc(); +long cdda_transform(); + +/* + * Send status information upstream. + */ +void +send_status(blk) + struct cdda_block *blk; +{ + write(1, blk, sizeof(*blk)); +} + +/* + * Accept a command from our master. + * + * The protocol is byte-oriented: + * PmsfMSFxyz Play from msf to MSF (MSF can be 0,0,0 to play to end) + * xyz is the msf of the start of this chunk, i.e., the + * ending boundary for reverse play. + * S Stop. + * E Eject. This means we just close the CD device and + * open it again later. + * Q Quit. + * Vn Set volume level (0-255). + * Bn Set balance level (0-255). + * EnL Set an equalizer level (n = 0 for bass, 255 for treble) + * G Get current status. + * sn Set speed multiplier to n. + * dn Set direction to forward (n = 0) or reverse. + * Fllllx... Start saving to a file (length = l, followed by name) + * F0000 Stop saving. + * Ln Set loudness level (0-255). + */ +int +command(cd_fd, blk) + int cd_fd; + struct cdda_block *blk; +{ + unsigned char inbuf[10]; + char *filename; + int namelen; + struct auheader hdr; + + if (read(0, inbuf, 1) <= 0) /* Parent died. */ + { + wmcdda_close(); + wmaudio_close(); + exit(0); + } + + switch (inbuf[0]) { + case 'E': + playing = 0; + wmaudio_stop(); + wmcdda_close(cd_fd); + cd_fd = -1; + blk->status = WMCDDA_ACK; + send_status(blk); + break; + case 'P': + read(0, inbuf, 9); + playing = 1; + + wmaudio_stop(); + + wmcdda_setup(inbuf[0] * 60 * 75 + inbuf[1] * 75 + inbuf[2], + inbuf[3] * 60 * 75 + inbuf[4] * 75 + inbuf[5], + inbuf[6] * 60 * 75 + inbuf[7] * 75 + inbuf[8]); + + wmaudio_ready(); + + level = 2500; + volume = 1 << 15; + + blk->status = WMCDDA_ACK; + send_status(blk); + break; + + case 'S': + playing = 0; + wmaudio_stop(); + blk->status = WMCDDA_ACK; + send_status(blk); + blk->status = WMCDDA_STOPPED; + send_status(blk); + break; + + case 'B': + read(0, inbuf, 1); + wmaudio_balance(inbuf[0]); + blk->status = WMCDDA_ACK; + send_status(blk); + break; + + case 'V': + read(0, inbuf, 1); + wmaudio_volume(inbuf[0]); + blk->status = WMCDDA_ACK; + send_status(blk); + break; + + case 'G': + blk->status = WMCDDA_ACK; + send_status(blk); + + if (playing) + blk->status = WMCDDA_PLAYED; + else + blk->status = WMCDDA_STOPPED; + wmaudio_state(blk); + send_status(blk); + break; + + case 'Q': + blk->status = WMCDDA_ACK; + send_status(blk); + wmcdda_close(); + wmaudio_close(); + exit(0); + + case 's': + read(0, inbuf, 1); + speed = inbuf[0]; + wmcdda_speed(speed); + blk->status = WMCDDA_ACK; + send_status(blk); + break; + + case 'd': + read(0, inbuf, 1); + wmcdda_direction(inbuf[0]); + blk->status = WMCDDA_ACK; + send_status(blk); + break; + + case 'L': + read(0, inbuf, 1); + loudness = inbuf[0]; + blk->status = WMCDDA_ACK; + send_status(blk); + break; + + case 'F': + read(0, &namelen, sizeof(namelen)); + if (output != NULL) + { + fclose(output); + output = NULL; + } + if (namelen) + { + filename = malloc(namelen + 1); + if (filename == NULL) + { + perror("cddaslave"); + wmcdda_close(); + wmaudio_close(); + exit(1); + } + + read(0, filename, namelen); + filename[namelen] = '\0'; + output = fopen(filename, "w"); + if (output == NULL) + perror(filename); + else + { + /* Write an .au file header. */ + hdr.magic = htonl(0x2e736e64); + hdr.hdr_size = htonl(sizeof(hdr) + 28); + hdr.data_size = htonl(~0); + hdr.encoding = htonl(3); /* linear-16 */ + hdr.sample_rate = htonl(44100); + hdr.channels = htonl(2); + + fwrite(&hdr, sizeof(hdr), 1, output); + fwrite("Recorded from CD by WorkMan", 28, 1, + output); + } + + free(filename); + } + + blk->status = WMCDDA_ACK; + send_status(blk); + } + return(cd_fd); +} + +/* + * Transform some CDDA data. + */ +long +wmcdda_transform(unsigned char *rawbuf, long buflen, struct cdda_block *block) +{ + long i; + long *buf32 = (long *)rawbuf; + register short *buf16 = (short *)rawbuf; + register int aval; + + /* + * Loudness transformation. Basically this is a self-adjusting + * volume control; our goal is to keep the average output level + * around a certain value (2500 seems to be pleasing.) We do this + * by maintaining a decaying average of the recent output levels + * (where "recent" is some fraction of a second.) All output levels + * are multiplied by the inverse of the decaying average; this has + * the volume-leveling effect we desire, and isn't too CPU-intensive. + * + * This is done by modifying the digital data, rather than adjusting + * the system volume control, because (at least on some systems) + * tweaking the system volume can generate little pops and clicks. + * + * There's probably a more elegant way to achieve this effect, but + * what the heck, I never took a DSP class and am making this up as + * I go along, with a little help from some friends. + * + * This is all done with fixed-point math, oriented around powers + * of two, which with luck will keep the CPU usage to a minimum. + * More could probably be done, for example using lookup tables to + * replace multiplies and divides; whether the memory hit (128K + * for each table) is worthwhile is unclear. + */ + if (loudness) + { + /* We aren't really going backwards, but i > 0 is fast */ + for (i = buflen / 2; i > 0; i--) + { + /* + * Adjust this sample to the current level. + */ + aval = (*buf16 = (((long)*buf16) * volume) >> 15); + buf16++; + + /* + * Don't adjust the decaying average for each sample; + * that just spends CPU time for very little benefit. + */ + if (i & 127) + continue; + + /* + * We want to use absolute values to compute the + * decaying average; otherwise it'd sit around 0. + */ + if (aval < 0) + aval = -aval; + + /* + * Adjust more quickly when we start hitting peaks, + * or we'll get clipping when there's a sudden loud + * section after lots of quiet. + */ + if (aval & ~8191) + aval <<= 3; + + /* + * Adjust the decaying average. + */ + level = ((level << 11) - level + aval) >> 11; + + /* + * Let *really* quiet sounds play softly, or we'll + * amplify background hiss to full volume and blast + * the user's speakers when real sound starts up. + */ + if (! (level & ~511)) + level = 512; + + /* + * And adjust the volume setting using the inverse + * of the decaying average. + */ + volume = (2500 << 15) / level; + } + } + + if (speed == 128) + return (buflen); + + /* + * Half-speed play. Stretch things out. + */ + if (speed == 0) + { + buflen /= 2; /* Since we're moving 32 bits at a time */ + + for (i = buflen - 1; i > 0; i--) + { + buf32[i] = buf32[i / 2]; + } + + buflen *= 4; /* 2 for doubling the buffer, 2 from above */ + } + + /* + * Slow play; can't optimize it as well as half-speed. + */ + if (speed && speed < 128) + { + int multiplier = ((speed + 128) * 128) / 256; + int newlen; + int tally = 0, pos; + + buflen /= 4; /* Get the number of 32-bit values */ + + /* + * Buffer length doubles when speed is 0, stays the same + * when speed is 128. + */ + newlen = (buflen * 128) / multiplier; + + pos = buflen - 1; + for (i = newlen - 1; i > 0; i--) + { + buf32[i] = buf32[pos]; + tally += multiplier; + if (tally & 128) + { + pos--; + tally ^= 128; + } + } + + buflen = newlen * 4; + } + + return (buflen); +} + + +main(argc, argv) + char **argv; +{ + int cd_fd = 3; + fd_set readfd, dummyfd; + struct timeval timeout; + char *cddabuf; + long cddabuflen; + struct cdda_block blockinfo; + long result; + int nfds; + char *devname; + + /* + * Device name should be the command-line argument. + */ + if (argc < 2) + devname = ""; + else + devname = argv[1]; + + /* + * If we're running setuid root, bump up our priority then lose + * superuser access. + */ + nice(-14); + setuid(getuid()); + + FD_ZERO(&dummyfd); + FD_ZERO(&readfd); + + timerclear(&timeout); + + cd_fd = wmcdda_init(&cddabuf, &cddabuflen, cd_fd, devname); + if (cd_fd < 0) + exit(1); + wmaudio_init(); + + blockinfo.status = WMCDDA_ACK; + send_status(&blockinfo); + blockinfo.status = WMCDDA_STOPPED; + + /* + * Accept commands as they come in, and play some sound if we're + * supposed to be doing that. + */ + while (1) + { + FD_SET(0, &readfd); + + /* + * If we're playing, we don't want select to block. Otherwise, + * wait a little while for the next command. + */ + if (playing) + timeout.tv_usec = 0; + else + timeout.tv_usec = 500000; + + nfds = select(1, &readfd, &dummyfd, &dummyfd, &timeout); + + if (nfds < 0) /* Broken pipe; our GUI exited. */ + { + wmcdda_close(cd_fd); + wmaudio_close(); + exit(0); + } + + if (FD_ISSET(0, &readfd)) + { + /* If this doesn't work, just hope for the best */ + if(cd_fd == -1) + cd_fd = wmcdda_open(devname); + cd_fd = command(cd_fd, &blockinfo); + /* + * Process all commands in rapid succession, rather + * than possibly waiting for a CDDA read. + */ + continue; + } + + if (playing) + { + result = wmcdda_read(cd_fd, cddabuf, cddabuflen, + &blockinfo); + if (result <= 0) + { + /* Let the output queue drain. */ + if (blockinfo.status == WMCDDA_DONE) + { + wmaudio_mark_last(); + if (wmaudio_send_status()) + { + /* queue drained, stop polling*/ + playing = 0; + } + } + else + { + playing = 0; + send_status(&blockinfo); + } + } + else + { + result = wmcdda_normalize(cddabuf, result, + &blockinfo); + result = wmcdda_transform(cddabuf, result, + &blockinfo); + if (output) + fwrite(cddabuf, result, 1, output); + result = wmaudio_convert(cddabuf, result, + &blockinfo); + if (wmaudio_play(cddabuf, result, &blockinfo)) + { + playing = 0; + wmaudio_stop(); + send_status(&blockinfo); + } + } + } + else + send_status(&blockinfo); + } +} + +#else /* BUILD_CDDA */ + +main() +{ + exit(0); +} + +#endif /* BUILD_CDDA */ diff --git a/ascd/libworkman/cddb.c b/ascd/libworkman/cddb.c new file mode 100644 index 0000000..75e891b --- /dev/null +++ b/ascd/libworkman/cddb.c @@ -0,0 +1,541 @@ +/* + * $Id: cddb.c,v 1.2 1999/02/14 09:50:42 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * establish connection to cddb server and get data + * socket stuff gotten from gnu port of the finger command + * + * provided by Sven Oliver Moll + * + */ + +static char cddb_id[] = "$Id: cddb.c,v 1.2 1999/02/14 09:50:42 dirk Exp $"; + +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/signal.h> +#include <ctype.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include "include/wm_config.h" +#include "include/wm_struct.h" +#include "include/wm_cdinfo.h" +#include "include/wm_helpers.h" + +/* + * This is for identifying WorkMan at CDDB servers + */ +#define PROGRAM WORKMAN_NAME +#define VERSION WORKMAN_VERSION + +struct wm_cddb cddb; + +int cur_cddb_protocol; +char *cur_cddb_server; +char *cur_cddb_mail_adress; +char *cur_cddb_path_to_cgi; +char *cur_cddb_proxy_server; + +int Socket; +FILE *Connection; + +/* + * + */ +void +cddb_cur2struct(void) +{ + cddb.protocol = cur_cddb_protocol; + strcpy(cddb.cddb_server, cur_cddb_server); + strcpy(cddb.mail_adress, cur_cddb_mail_adress); + strcpy(cddb.path_to_cgi, cur_cddb_path_to_cgi); + strcpy(cddb.proxy_server, cur_cddb_proxy_server); +} /* cddb_cur2struct() */ + +/* + * + */ +void +cddb_struct2cur(void) +{ + cur_cddb_protocol = cddb.protocol; + cur_cddb_server = (cddb.cddb_server); + cur_cddb_mail_adress = (cddb.mail_adress); + cur_cddb_path_to_cgi = (cddb.path_to_cgi); + cur_cddb_proxy_server = (cddb.proxy_server); +} /* cddb_struct2cur() */ + + +/* + * Subroutine from cddb_discid + */ +int +cddb_sum(int n) +{ + char buf[12], + *p; + int ret = 0; + + /* For backward compatibility this algorithm must not change */ + sprintf(buf, "%lu", (unsigned long)n); + for (p = buf; *p != '\0'; p++) + ret += (*p - '0'); + + return (ret); +} /* cddb_sum() */ + + +/* + * Calculate the discid of a CD according to cddb + */ +unsigned long +cddb_discid(void) +{ + int i, + t, + n = 0; + + /* For backward compatibility this algorithm must not change */ + for (i = 0; i < thiscd.ntracks; i++) { + + n += cddb_sum(thiscd.trk[i].start / 75); + /* + * Just for demonstration (See below) + * + * t += (thiscd.trk[i+1].start / 75) - + * (thiscd.trk[i ].start / 75); + */ + } + + /* + * Mathematics can be fun. Example: How to reduce a full loop to + * a simple statement. The discid algorhythm is so half-hearted + * developed that it doesn't even use the full 32bit range. + * But it seems to be always this way: The bad standards will be + * accepted, the good ones turned down. + */ + + t = (thiscd.trk[thiscd.ntracks].start-thiscd.trk[0].start)/75; + return ((n % 0xff) << 24 | t << 8 | thiscd.ntracks); +} /* cddb_discid() */ + +/* + * Split a string into two components according to the first occurance of + * the delimiter. + */ +char * +string_split(char *line, char delim) +{ + char *p1; + + for (p1=line;*p1;p1++) + { + if(*p1 == delim) + { + *p1 = 0; + return ++p1; + } + } + return (NULL); +} /* string_split() */ + +/* + * Generate the hello string according to the cddb protocol + * delimiter is either ' ' (cddbp) or '+' (http) + */ +void +string_makehello(char *line,char delim) +{ + char mail[84],*host; + + strcpy(mail,cddb.mail_adress); + host=string_split(mail,'@'); + + sprintf(line,"%shello%c%s%c%s%c%s%c%s", + delim == ' ' ? "cddb " : "&", + delim == ' ' ? ' ' : '=', + mail,delim, + host,delim, + PROGRAM,delim, + VERSION); +} /* string_makehello() */ + +/* + * Open the TCP connection to the cddb/proxy server + */ +int +connect_open(void) +{ + char *host; + struct hostent *hp; + struct sockaddr_in soc_in; + int port; + + if(cddb.protocol == 3) /* http proxy */ + host = strdup(cddb.proxy_server); + else + host = strdup(cddb.cddb_server); + /* + * t=string_split(host,':'); + */ + port=atoi(string_split(host,':')); + if(!port) + port=8880; + + printf("%s:%d\n",host,port); + hp =gethostbyname(host); + + if (hp == NULL) + { + static struct hostent def; + static struct in_addr defaddr; + static char *alist[1]; + static char namebuf[128]; + int inet_addr(); + + defaddr.s_addr = inet_addr(host); + if (defaddr.s_addr == -1) + { + printf("unknown host: %s\n", host); + return (-1); + } + strcpy(namebuf, host); + def.h_name = namebuf; + def.h_addr_list = alist, def.h_addr = (char *)&defaddr; + def.h_length = sizeof (struct in_addr); + def.h_addrtype = AF_INET; + def.h_aliases = 0; + hp = &def; + } + soc_in.sin_family = hp->h_addrtype; + bcopy(hp->h_addr, (char *)&soc_in.sin_addr, hp->h_length); + soc_in.sin_port = htons(port); + Socket = socket(hp->h_addrtype, SOCK_STREAM, 0); + if (Socket < 0) + { + perror("socket"); + return (-1); + } + fflush(stdout); + if (connect(Socket, (struct sockaddr *)&soc_in, sizeof (soc_in)) < 0) + { + perror("connect"); + close(Socket); + return (-1); + } + + Connection = fdopen(Socket, "r"); + return (0); +} /* connect_open() */ + + +/* + * Close the connection + */ +void +connect_close(void) +{ + (void)fclose(Connection); + close(Socket); +} /* connect_close() */ + +/* + * Get a line from the connection with CR and LF stripped + */ +void +connect_getline(char *line) +{ + char c; + + while ((c = getc(Connection)) != '\n') + { + *line = c; + if ((c != '\r') && (c != (char)0xff)) + line++; + } + *line=0; +} /* connect_getline() */ + +/* + * Read the CD data from the server and place them into the cd struct + */ +void +connect_read_entry(void) +{ + char type; + int trknr; + + char *t,*t2,tempbuf[2000]; + + while(strcmp(tempbuf,".")) + { + connect_getline(tempbuf); + + t=string_split(tempbuf,'='); + if(t != NULL) + { + type=tempbuf[0]; + + if(strncmp("TITLE",tempbuf+1,5)) + continue; + + if('D' == type) + { + /* + * Annahme: "Interpret / Titel" ist falsch. + * Daher: NULL-String erwarten. + */ + t2=string_split(t,'/'); + if(t2 == NULL) + t2 = t; + if(*t2 == ' ') + t2++; + strcpy(cd->cdname,t2); + + for(t2=t;*t2;t2++) + { + if((*t2 == ' ') && (*(t2+1) == 0)) + *t2=0; + } + strcpy(cd->artist,t); + } + + if('T' == type) + { + trknr=atoi(tempbuf+6); + /* + * printf("Track %d:%s\n",trknr,t); + */ + wm_strmcpy(&cd->trk[trknr].songname,t); + } + /* + * fprintf(stderr, "%s %s\n",tempbuf,t); + */ + } + } +} /* connect_read_entry() */ + +/* + * Send a command to the server using cddbp + */ +void +cddbp_send(char *line) +{ + write(Socket, line, strlen(line)); + write(Socket, "\n", 1); +} /* cddbp_send() */ + +/* + * Send the "read from cddb" command to the server using cddbp + */ +void +cddbp_read(char *category, unsigned int id) +{ + char tempbuf[84]; + sprintf(tempbuf, "cddb read %s %08x", category, id); + cddbp_send(tempbuf); +} /* cddbp_read() */ + +/* + * Send a command to the server using http + */ +void +http_send(char* line) +{ + char tempbuf[2000]; + + write(Socket, "GET ", 4); + printf("GET "); + if(cddb.protocol == 3) + { + write(Socket, "http://", 7); + write(Socket, cddb.cddb_server, strlen(cddb.cddb_server)); + printf("http://%s",cddb.cddb_server); + } + write(Socket, cddb.path_to_cgi, strlen(cddb.path_to_cgi)); + write(Socket, "?cmd=" ,5); + write(Socket, line, strlen(line)); + printf("%s?cmd=%s",cddb.path_to_cgi,line); + string_makehello(tempbuf,'+'); + write(Socket, tempbuf, strlen(tempbuf)); + printf("%s",tempbuf); + write(Socket, "&proto=1 HTTP/1.0\n\n", 19); + printf("&proto=1 HTTP/1.0\n"); + do + connect_getline(tempbuf); + while(strcmp(tempbuf,"")); +} /* http_send() */ + +/* + * Send the "read from cddb" command to the server using http + */ +void +http_read(char *category, unsigned int id) +{ + char tempbuf[84]; + sprintf(tempbuf, "cddb+read+%s+%08x", category, id); + http_send(tempbuf); +} /* http_read() */ + +/* + * The main routine called from the ui + */ +void +cddb_request(void) +{ + int i; + char tempbuf[2000]; + extern int cur_ntracks; + + int status; + char category[20]; + unsigned int id; + + strcpy(cddb.cddb_server,"localhost:888"); + strcpy(cddb.mail_adress,"[email protected]"); + /* + * cddb.protocol = 1; + */ + wipe_cdinfo(); + + switch(cddb.protocol) + { + case 1: /* cddbp */ + printf("USING CDDBP\n"); + printf("open\n"); + connect_open(); + connect_getline(tempbuf); + printf("[%s]\n",tempbuf); + /* + * if(atoi(tempbuf) == 201) return; + */ + + /* + * strcpy(tempbuf,"cddb hello svolli bigfoot.com Eierkratzer eins"); + */ + string_makehello(tempbuf,' '); + fprintf(stderr, "%s\n", tempbuf); + cddbp_send(tempbuf); + connect_getline(tempbuf); + printf("[%s]\n",tempbuf); + + printf("query\n"); + sprintf(tempbuf, "cddb query %08x %d",thiscd.cddbid,thiscd.ntracks); + for (i = 0; i < cur_ntracks; i++) + if (thiscd.trk[i].section < 2) + sprintf(tempbuf + strlen(tempbuf), " %d", + thiscd.trk[i].start); + sprintf(tempbuf + strlen(tempbuf), " %d\n", thiscd.length); + printf(">%s<\n",tempbuf); + cddbp_send(tempbuf); + connect_getline(tempbuf); + printf("[%s]\n",tempbuf); + + status=atoi(tempbuf); + /* + * fprintf(stderr, "status:%d\n",status); + * fprintf(stderr,"category:%s\n",category); + * fprintf(stderr,"id:%s\n",id); + */ + if(status == 200) /* Exact match */ + { + sscanf(tempbuf,"%d %s %08x",&status,category,&id); + cddbp_read(category,id); + connect_read_entry(); + } + + if(status == 211) /* Unexact match, multiple possible + * Hack: always use first. */ + { + connect_getline(tempbuf); + sscanf(tempbuf,"%s %08x",category,&id); + while(strcmp(tempbuf,".")) + connect_getline(tempbuf); + cddbp_read(category,id); + connect_read_entry(); + } + + cddbp_send("quit"); + connect_close(); + printf("close\n"); + break; + case 2: /* http */ + case 3: /* http proxy */ + printf("USING HTTP%s\n", + (cddb.protocol == 3) ? " WITH PROXY" : ""); + printf("query\n"); + sprintf(tempbuf, "cddb+query+%08x+%d",thiscd.cddbid,thiscd.ntracks); + for (i = 0; i < cur_ntracks; i++) + if (thiscd.trk[i].section < 2) + sprintf(tempbuf + strlen(tempbuf), "+%d", + thiscd.trk[i].start); + sprintf(tempbuf + strlen(tempbuf), "+%d", thiscd.length); + printf(">%s<\n",tempbuf); + connect_open(); + http_send(tempbuf); + connect_getline(tempbuf); + printf("[%s]\n",tempbuf); + + status=atoi(tempbuf); + /* + * fprintf(stderr, "status:%d\n",status); + * fprintf(stderr, "category:%s\n",category); + * fprintf(stderr, "id:%s\n",id); + */ + + if(status == 200) /* Exact match */ + { + connect_close(); + connect_open(); + sscanf(tempbuf,"%d %s %08x",&status,category,&id); + http_read(category,id); + connect_read_entry(); + } + + if(status == 211) /* Unexact match, multiple possible + * Hack: always use first. */ + { + connect_getline(tempbuf); + sscanf(tempbuf,"%s %08x",category,&id); + while(strcmp(tempbuf,".")) + connect_getline(tempbuf); + connect_close(); + connect_open(); + http_read(category,id); + connect_read_entry(); + } + /* moved close above break */ + connect_close(); + break; + default: /* off */ + break; + } +} /* cddb_request() */ + diff --git a/ascd/libworkman/cdinfo.c b/ascd/libworkman/cdinfo.c new file mode 100644 index 0000000..525e227 --- /dev/null +++ b/ascd/libworkman/cdinfo.c @@ -0,0 +1,887 @@ +/* + * $Id: cdinfo.c,v 1.6 1999/02/14 16:47:40 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Get information about a CD. + */ + +static char cdinfo_id[] = "$Id: cdinfo.c,v 1.6 1999/02/14 16:47:40 dirk Exp $"; + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "include/wm_config.h" + +#include "include/wm_struct.h" +#include "include/wm_cdrom.h" +#include "include/wm_cdinfo.h" +#include "include/wm_database.h" +#include "include/wm_helpers.h" + +struct wm_play *playlist = NULL; +struct wm_cdinfo thiscd, *cd = &thiscd; + +int cur_track = -1; /* Current track number, starting at 1 */ +int cur_index = 0; /* Current index mark */ +int cur_lasttrack = 999; /* Last track to play in current chunk */ +int cur_firsttrack = 0; /* First track of current chunk */ +int cur_pos_abs; /* Current absolute position in seconds */ +int cur_frame; /* Current frame number */ +int cur_pos_rel; /* Current track-relative position in seconds */ +int cur_tracklen; /* Length in seconds of current track */ +int cur_cdlen; /* Length in seconds of entire CD */ +int cur_ntracks; /* Number of tracks on CD (= tracks + sections) */ +int cur_nsections; /* Number of sections currently defined */ +enum wm_cd_modes cur_cdmode = WM_CDM_EJECTED; +int cur_listno; /* Current index into the play list, if playing */ +char * cur_artist; /* Name of current CD's artist */ +char * cur_cdname; /* Album name */ +char * cur_trackname; /* Take a guess */ +char cur_contd; /* Continued flag */ +char cur_avoid; /* Avoid flag */ + +int exit_on_eject = 0; + +int cur_stopmode = -1; +extern int info_modified; + +/* + * insert_trackinfo() + * + * Add a new track to the CD info structure. Pass the position of the new + * entry in the track list -- 0 will make this the first track, 1 the second, + * etc. The new entry will be zeroed out. + */ +void +insert_trackinfo(num) + int num; +{ + struct wm_trackinfo *newtrk; + + /* Easy case: the list is empty */ + if (cd->trk == NULL) { + if ((cd->trk = (struct wm_trackinfo *) calloc(1, + sizeof(*newtrk))) == NULL) + { +nomem: + perror("insert_trackinfo"); + exit(1); + } else { + return; + } /* if() else */ + } /* if() */ + /* Stick the new entry in cd->trk[]. */ + if ((newtrk = (struct wm_trackinfo *) malloc(sizeof(*newtrk) * + (cur_ntracks + 1))) == NULL) + goto nomem; + + if (num) + memcpy(newtrk, cd->trk, sizeof(*newtrk) * num); + memset(&newtrk[num], 0, sizeof(*newtrk)); + if (num < cur_ntracks) + memcpy(&newtrk[num + 1], &cd->trk[num], sizeof(*newtrk) * + (cur_ntracks - num)); + + free(cd->trk); + cd->trk = newtrk; +} + +/* + * split_trackinfo() + * + * Split a track in two at a particular position (absolute, in frames). All + * internal data structures and variables will be adjusted to the new + * numbering scheme. Pass in the track number (>=1) to split, which is also + * the index into cd->trk[] of the new entry. + * + * If pos is within 1 second of the start of another track, the split fails. + * + * Returns 1 on success, 0 if the track couldn't be inserted. + * + * Note: updating user interface elements is up to the caller. + */ +int +split_trackinfo( int pos ) +{ + int i, l, num; + + if (pos < cd->trk[0].start) + return (0); + + /* First find the appropriate track. */ + for (num = 0; num < cur_ntracks; num++) + if (cd->trk[num].start - 75 < pos && + cd->trk[num].start + 75 > pos) + return (0); + else if (cd->trk[num].start > pos) + break; + if (num == 0) + return (0); + + /* Insert the new entry into the track array. */ + insert_trackinfo(num); + + /* Update the easy variables. */ + if (cur_track > num) + cur_track++; + if (cur_firsttrack > num) + cur_firsttrack++; + if (cur_lasttrack > num) + cur_lasttrack++; + + /* Update the user-defined playlists. */ + if (cd->lists != NULL) + for (l = 0; cd->lists[l].name != NULL; l++) + if (cd->lists[l].list != NULL) + for (i = 0; cd->lists[l].list[i]; i++) + if (cd->lists[l].list[i] > num) + cd->lists[l].list[i]++; + + /* Update the internal playlist. */ + if (playlist != NULL) + for (i = 0; playlist[i].start; i++) + { + if (playlist[i].start > num) + playlist[i].start++; + if (playlist[i].end > num) + playlist[i].end++; + } + + /* Now adjust the information in cd->trk[]. */ + cd->trk[num].start = pos; + if (num == cur_ntracks) + cd->trk[num].length = cur_cdlen - pos / 75; + else + cd->trk[num].length = (cd->trk[num + 1].start - pos) / 75; + cd->trk[num - 1].length -= cd->trk[num].length; + if (cur_track == num) + cur_tracklen -= cd->trk[num].length; + cd->trk[num].track = cd->trk[num - 1].track; + cd->trk[num].data = cd->trk[num - 1].data; + cd->trk[num].contd = 1; + cd->trk[num].volume = cd->trk[num - 1].volume; + + if (cd->trk[num - 1].section == 0) + cd->trk[num - 1].section = 1; + cd->trk[num].section = cd->trk[num - 1].section + 1; + + cur_ntracks++; + cur_nsections++; + + for (i = num + 1; i < cur_ntracks; i++) + if (cd->trk[i].track == cd->trk[num].track) + cd->trk[i].section++; + + return (1); +} + +/* + * remove_trackinfo() + * + * Remove a track's internal data. This is similar to split_trackinfo() + * above, but simpler. A track's initial section can't be removed. Track + * numbers start at 0. + * + * Returns 1 on success, 0 on failure. + */ +int +remove_trackinfo( int num ) +{ + int i, l; + + if (num < 1 || num >= cur_ntracks || cd->trk[num].section < 2) + return (0); + + cd->trk[num - 1].length += cd->trk[num].length; + + for (i = num; i < cur_ntracks - 1; i++) + memcpy(&cd->trk[i], &cd->trk[i + 1], sizeof(cd->trk[0])); + + if (cur_track > num) + cur_track--; + if (cur_firsttrack > num) + cur_firsttrack--; + if (cur_lasttrack > num) + cur_lasttrack--; + + /* Update the user-defined playlists. */ + if (cd->lists != NULL) + for (l = 0; cd->lists[l].name != NULL; l++) + if (cd->lists[l].list != NULL) + for (i = 0; cd->lists[l].list[i]; i++) + if (cd->lists[l].list[i] > num) + cd->lists[l].list[i]--; + + /* Update the internal playlist. */ + if (playlist != NULL) + for (i = 0; playlist[i].start; i++) + { + if (playlist[i].start > num) + playlist[i].start--; + if (playlist[i].end > num) + playlist[i].end--; + } + + cur_ntracks--; + cur_nsections--; + + /* + * Update the section numbers for this track. If this is the only + * user-created section in a track, get rid of the section number + * in the track's entry. + */ + if (num == cur_ntracks || cd->trk[num - 1].track != cd->trk[num].track) + { + if (cd->trk[num - 1].section == 1) + cd->trk[num - 1].section = 0; + } + else + for (i = num; i < cur_ntracks; i++) + if (cd->trk[i].track == cd->trk[num - 1].track) + cd->trk[i].section--; + + return (1); +} + +/* + * listentry() + * + * Return a scrolling list entry. + */ +char * +listentry( int num ) +{ + static char buf[600]; + char *name, tracknum[20]; + int digits; + int sdigits; + + if (num >= 0 && num < cur_ntracks) + { + +/* + if (big_spaces) + { + digits = 2; + sdigits = cur_nsections < 9 ? -1 : -2; + } + else + { + digits = cd->trk[num].track < 10 ? 3 : 2; + sdigits = cur_nsections < 9 ? -1 : -3; + } +*/ + + digits = 2; + sdigits = cur_nsections < 9 ? -1 : -2; + + name = cd->trk[num].songname ? cd->trk[num].songname : ""; + + if (cur_nsections) + { + if (cd->trk[num].section > 9) + { + sprintf(tracknum, "%*d.%d", digits, + cd->trk[num].track, + cd->trk[num].section); + } else { + if (cd->trk[num].section) + { + sprintf(tracknum, "%*d.%*d", digits, + cd->trk[num].track, sdigits, + cd->trk[num].section); + } else { + sprintf(tracknum, "%*d%*s", digits, + cd->trk[num].track, + 2 - sdigits, " "); +/* 2 - sdigits - big_spaces, " ");*/ + } + } + } else { + sprintf(tracknum, "%*d", digits, cd->trk[num].track); + } + + if (cd->trk[num].data) + { + sprintf(buf, "%s) %3dMB %s", tracknum, + cd->trk[num].length / 1024, name); + } else { + sprintf(buf, "%s) %02d:%02d %s", tracknum, + cd->trk[num].length / 60, + cd->trk[num].length % 60, name); + } + + return (buf); + } else { + return (NULL); + } +} /* listentry() */ + +/* + * trackname() + * + * Return a track's name. + */ +char * +trackname( int num ) +{ + if (num >= 0 && num < cur_ntracks) + { + if (cd->trk[num].songname) + { + return (cd->trk[num].songname); + } else { + return (""); + } + } else { + return (NULL); + } +} /* trackname() */ + +/* + * tracklen() + * + * Return a track's length in seconds. + */ +int +tracklen( int num ) +{ + if (cd != NULL && num >= 0 && num < cur_ntracks) + return (cd->trk[num].length); + else + return (0); +} + +/* + * get_default_volume() + * + * Return the default volume (0-32, 0=none) for the CD or a track. + */ +int +get_default_volume( int track ) +{ + if (! track) + return (cd->volume); + else if (track <= cur_ntracks) + return (cd->trk[track - 1].volume); + else + return (0); +} + +/* + * get_contd() + * + * Return the contd value for a track. + */ +int +get_contd( int num ) +{ + if (num >= 0 && num < cur_ntracks) + return (cd->trk[num].contd); + else + return (0); +} + +/* + * get_avoid() + * + * Return the avoid value for a track. + */ +int +get_avoid( int num ) +{ + if (num >= 0 && num < cur_ntracks) + return (cd->trk[num].avoid); + else + return (0); +} + +/* + * get_autoplay() + * + * Is autoplay set on this disc? + */ +int +get_autoplay( void ) +{ + return ( cd->autoplay ); +} + +/* + * get_playmode() + * + * Return the default playmode for the CD. + */ +int +get_playmode( void ) +{ + return ( cd->playmode ); +} + +/* + * get_runtime() + * + * Return the total running time for the current playlist in seconds. + */ +int +get_runtime( void ) +{ + int i; + + if (playlist == NULL || playlist[0].start == 0 || cur_firsttrack == -1) + return (cd == NULL ? 0 : cd->length); + + for (i = 0; playlist[i].start; i++) + ; + + return (playlist[i].starttime); +} + +/* + * default_volume() + * + * Set the default volume for the CD or a track. + */ +void +default_volume( int track, int vol ) +{ + if (track == 0) + cd->volume = vol; + else if (track <= cur_ntracks) + cd->trk[track - 1].volume = vol; +} + +/* + * Play the next thing on the playlist, if any. + */ +void +play_next_entry( int forward ) +{ + if (cd == NULL) + return; + if (playlist != NULL && playlist[cur_listno].start) + { + wm_cd_play(playlist[cur_listno].start, 0, + playlist[cur_listno].end); + cur_listno++; + } + else + wm_cd_stop(); +} + +/* + * Play the next track, following playlists as necessary. + */ +void +play_next_track( int forward ) +{ + if (cd == NULL) + return; + + /* Is the current playlist entry done? Move on, if so. */ + if (playlist == NULL || cur_track + 1 == playlist[cur_listno - 1].end) + play_next_entry( forward ); + else + wm_cd_play(cur_track + 1, 0, playlist[cur_listno - 1].end); +} + +/* + * Play the previous track, hopping around the playlist as necessary. + */ +void +play_prev_track( int forward ) +{ + if (cd == NULL) + return; + + if (playlist == NULL) + return; + + /* If we're in the middle of this playlist entry, go back one track */ + if (cur_track > playlist[cur_listno - 1].start) + wm_cd_play(cur_track - 1, 0, playlist[cur_listno - 1].end); + else + if (cur_listno > 1) + { + cur_listno--; + wm_cd_play(playlist[cur_listno - 1].end - 1, 0, + playlist[cur_listno - 1].end); + } + else + wm_cd_play(playlist[0].start, 0, playlist[0].end); +} + +/* + * stash_cdinfo(artist, cdname) + */ +void +stash_cdinfo(char *artist, char *cdname, int autoplay, int playmode ) +{ + if (cd != NULL) + { + if (strcmp(cd->artist, artist)) + info_modified = 1; + strcpy(cd->artist, artist); + + if (strcmp(cd->cdname, cdname)) + info_modified = 1; + strcpy(cd->cdname, cdname); + + if (!!cd->autoplay != !!autoplay) + info_modified = 1; + cd->autoplay = autoplay; + + if (!!cd->playmode != !!playmode) + info_modified = 1; + cd->playmode = playmode; + } +} /* stash_cdinfo() */ + +/* + * wipe_cdinfo() + * + * Clear out all a CD's soft information (presumably in preparation for + * reloading from the database.) + */ +void +wipe_cdinfo( void ) +{ + struct wm_playlist *l; + int i; + + if (cd != NULL) + { + cd->artist[0] = cd->cdname[0] = '\0'; + cd->autoplay = cd->playmode = cd->volume = 0; + cd->whichdb = NULL; + freeup(&cd->otherrc); + freeup(&cd->otherdb); + + if (thiscd.lists != NULL) + { + for (l = thiscd.lists; l->name != NULL; l++) + { + free(l->name); + free(l->list); + } + freeup( (char **)&thiscd.lists ); + } + + for (i = 0; i < cur_ntracks; i++) + { + freeup(&cd->trk[i].songname); + freeup(&cd->trk[i].otherrc); + freeup(&cd->trk[i].otherdb); + cd->trk[i].avoid = cd->trk[i].contd = 0; + cd->trk[i].volume = 0; + if (cd->trk[i].section > 1) + remove_trackinfo(i--); + } + } +} + +/* + * stash_trkinfo(track, songname, contd, avoid) + * + * Update information about a track on the current CD. + */ +void +stash_trkinfo( int track, char *songname, int contd, int avoid ) +{ + if (cd != NULL) + { + track--; + if (!!cd->trk[track].contd != !!contd) + info_modified = 1; + cd->trk[track].contd = track ? contd : 0; + + if (!!cd->trk[track].avoid != !!avoid) + info_modified = 1; + cd->trk[track].avoid = avoid; + + if ((cd->trk[track].songname == NULL && songname[0]) || + (cd->trk[track].songname != NULL && + strcmp(cd->trk[track].songname, songname))) + { + info_modified = 1; + wm_strmcpy(&cd->trk[track].songname, songname); + } + } +} + +/* + * new_list() + * + * Add a playlist to a CD. + */ +struct wm_playlist * +new_list(cd, listname) + struct wm_cdinfo *cd; + char *listname; +{ + int nlists = 0; + struct wm_playlist *l; + + if (cd->lists != NULL) + { + for (nlists = 0; cd->lists[nlists].name != NULL; nlists++) + ; + l = (struct wm_playlist *)realloc(cd->lists, (nlists + 2) * + sizeof (struct wm_playlist)); + } + else + l = (struct wm_playlist *)malloc(2 * sizeof (struct wm_playlist)); + + if (l == NULL) + return (NULL); + + l[nlists + 1].name = NULL; + l[nlists].name = NULL; /* so wm_strmcpy doesn't free() it */ + wm_strmcpy(&l[nlists].name, listname); + l[nlists].list = NULL; + cd->lists = l; + + return (&l[nlists]); +} + +/* + * make_playlist() + * + * Construct a playlist for the current CD. If we're in shuffle mode, play + * each non-avoided track once, keeping continued tracks in the right order. + * + * If playmode is 2, use playlist number (playmode-2). XXX should do + * bounds checking on this, probably. + * + * If consecutive tracks are being played, only make one playlist entry for + * them, so the CD player won't pause between tracks while we wake up. + */ +void +make_playlist( int playmode, int starttrack ) +{ + int i, avoiding = 1, entry = 0, count, track, + *thislist; + + cur_listno = 0; + if (playlist != NULL) + free(playlist); + playlist = malloc(sizeof (*playlist) * (cur_ntracks + 1)); + if (playlist == NULL) + { + perror("playlist"); + exit(1); + } + + /* If this is a data-only CD, we can't play it. */ + if ((starttrack && cd->trk[starttrack - 1].data) || + (cur_ntracks == 1 && cd->trk[0].data)) + { + playlist[0].start = 0; + playlist[0].end = 0; + playlist[1].start = 0; + return; + } + + if (playmode == 1) + { + char *done = malloc(cur_ntracks); + + if (done == NULL) + { + perror("randomizer"); + exit(1); + } + + count = cur_ntracks; + if (starttrack && cd->trk[starttrack - 1].avoid) + count++; + for (i = 0; i < cur_ntracks; i++) + if (cd->trk[i].contd || cd->trk[i].avoid || + cd->trk[i].data) + { + done[i] = 1; + count--; + } + else + done[i] = 0; + + for (i = 0; i < count; i++) + { + int end; /* for readability */ + if (starttrack) + { + track = starttrack - 1; + starttrack = 0; + } + else + while (done[track = rand() % cur_ntracks]) + ; + + playlist[i].start = track + 1; + + /* play all subsequent continuation tracks too */ + for (end = track + 1; end < cur_ntracks + 1; end++) + if (! cd->trk[end].contd || + cd->trk[end].avoid || + cd->trk[end].data) + break; + playlist[i].end = end + 1; + + done[track]++; + } + playlist[i].start = 0; + + free(done); + } + else if (playmode >= 2 && cd->lists && cd->lists[playmode - 2].name) + { + count = 2; /* one terminating entry, and one for start */ + thislist = cd->lists[playmode - 2].list; + + for (i = 0; thislist[i]; i++) + if (thislist[i + 1] != thislist[i] + 1) + count++; + + if (playlist != NULL) + free(playlist); + playlist = malloc(sizeof (*playlist) * count); + if (playlist == NULL) + { + perror("playlist"); + exit(1); + } + + count = 0; + if (starttrack) + { + playlist[0].start = starttrack; + for (track = 0; thislist[track]; track++) + if (starttrack == thislist[track]) + break; + if (! thislist[track]) + { + playlist[0].end = starttrack + 1; + playlist[1].start = thislist[0]; + count = 1; + track = 0; + } + } + else + { + playlist[0].start = thislist[0]; + track = 0; + } + + for (i = track; thislist[i]; i++) + if (thislist[i + 1] != thislist[i] + 1) + { + playlist[count].end = thislist[i] + 1; + count++; + playlist[count].start = thislist[i + 1]; + } + } + else + { + for (i = starttrack ? starttrack - 1 : 0; i < cur_ntracks; i++) + if (avoiding && ! (cd->trk[i].avoid || cd->trk[i].data)) + { + playlist[entry].start = i + 1; + avoiding = 0; + } + else if (! avoiding && (cd->trk[i].avoid || + cd->trk[i].data)) + { + playlist[entry].end = i + 1; + avoiding = 1; + entry++; + } + if (! avoiding) + playlist[entry].end = i + 1; + playlist[entry + !avoiding].start = 0; + } + + /* + * Now go through the list, whatever its source, and figure out + * cumulative starting times for each entry. + */ + entry = count = 0; + do { + playlist[entry].starttime = count; + + if (playlist[entry].start) + for (i = playlist[entry].start; i < + playlist[entry].end; i++) + count += cd->trk[i - 1].length; + } while (playlist[entry++].start); +} + +/* + * Find a particular track's location in the current playlist. Sets the + * appropriate variables (cur_listno, cur_firsttrack, cur_lasttrack). + */ +void +pl_find_track( int track ) +{ + int i; + + if (playlist == NULL) + { + fprintf(stderr, "Null playlist! Huh?\n"); + return; + } + + for (i = 0; playlist[i].start; i++) + if (track >= playlist[i].start && track < playlist[i].end) + { + cur_listno = i + 1; + cur_firsttrack = playlist[i].start; + cur_lasttrack = playlist[i].end - 1; + return; + } + + /* + * Couldn't find the track in question. Make a special entry with + * just that track. + */ + if (! playlist[i].start) + { + playlist = realloc(playlist, (i + 2) * sizeof(*playlist)); + if (playlist == NULL) + { + perror("playlist realloc"); + exit(1); + } + + playlist[i + 1].start = playlist[i + 1].end = 0; + playlist[i + 1].starttime = playlist[i].starttime + + cd->trk[track - 1].length; + playlist[i].start = track; + playlist[i].end = track + 1; + cur_listno = i + 1; + cur_firsttrack = track; + cur_lasttrack = track; + } +} diff --git a/ascd/libworkman/cdrom.c b/ascd/libworkman/cdrom.c new file mode 100644 index 0000000..bbc0181 --- /dev/null +++ b/ascd/libworkman/cdrom.c @@ -0,0 +1,793 @@ +/* + * $Id: cdrom.c,v 1.10 1999/05/05 16:34:19 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Interface between most of WorkMan and the low-level CD-ROM library + * routines defined in plat_*.c and drv_*.c. The goal is to have no + * platform- or drive-dependent code here. + */ + +static char cdrom_id[] = "$Id: cdrom.c,v 1.10 1999/05/05 16:34:19 dirk Exp $"; + +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +/* #include <sys/time.h> */ + +#include "include/wm_config.h" +#include "include/wm_struct.h" +#include "include/wm_cddb.h" +#include "include/wm_cdrom.h" +#include "include/wm_database.h" +#include "include/wm_platform.h" +#include "include/wm_helpers.h" +#include "include/wm_cdinfo.h" + +#ifdef CAN_CLOSE +#include <fcntl.h> +#endif + +#define WM_MSG_CLASS WM_MSG_CLASS_CDROM + +/* extern struct wm_drive generic_proto, toshiba_proto, sony_proto; */ +/* toshiba33_proto; <=== Somehow, this got lost */ + +/* + * The supported drive types are listed here. NULL means match anything. + * The first match in the list is used, and substring matches are done (so + * put long names before their shorter prefixes.) + */ +struct drivelist { + char *ven; + char *mod; + char *rev; + struct wm_drive *proto; +} drives[] = { +{ "TOSHIBA", "XM-3501", NULL, &toshiba_proto }, +{ "TOSHIBA", "XM-3401", NULL, &toshiba_proto }, +{ "TOSHIBA", "XM-3301", NULL, &toshiba_proto }, +{ "SONY", "CDU-8012", NULL, &sony_proto }, +{ "SONY", "CDU 561", NULL, &sony_proto }, +{ WM_STR_GENVENDOR, WM_STR_GENMODEL, WM_STR_GENREV, &generic_proto }, +{ NULL, NULL, NULL, &generic_proto } +}; + +/* + * Solaris 2.2 will remove the device out from under us. Getting an ENOENT + * is therefore sometimes not a problem. + */ +int intermittent_dev = 0; + +/* + * Do we want to keep the CD device open after quitting by default? + * + int keep_open = 0; + */ + +#if defined DEFAULT_CD_DEVICE +char *cd_device = DEFAULT_CD_DEVICE; +#else +char *cd_device = NULL; +#endif + + +int wm_cd_cur_balance = 10; + +struct wm_drive drive = { -1, "", "", "", NULL, NULL }; + +char _wm_drive_vendor[32] = "Generic"; +char _wm_drive_model[32] = "drive type"; +char _wm_drive_revision[32] = ""; + +/* + * Give information about the drive we found during wmcd_open() + */ + +char *wm_drive_vendor( void ) +{ + char *s = NULL; + + wm_strmcpy( &s, _wm_drive_vendor ); + return s; +} + +char *wm_drive_model( void ) +{ + char *s = NULL; + + wm_strmcpy( &s, _wm_drive_model ); + return s; +} + +char *wm_drive_revision( void ) +{ + char *s = NULL; + + wm_strmcpy( &s, _wm_drive_revision ); + return s; +} + +void wm_drive_settype( char *vendor, char *model, char *revision ) +{ + sprintf( _wm_drive_vendor, "%s", vendor ); + sprintf( _wm_drive_model, "%s", model ); + sprintf( _wm_drive_revision, "%s", revision ); +} + +/* + * Figure out which prototype drive structure we should be using based + * on the vendor, model, and revision of the current drive. + */ +struct wm_drive * +find_drive_struct(char *vendor, char *model, char *rev) +{ + struct drivelist *d; + + for (d = drives; d; d++) + { + if( ( (d->ven != NULL) && strncmp(d->ven, vendor, strlen(d->ven)) ) || + ( (d->mod != NULL) && strncmp(d->mod, model, strlen(d->mod)) ) || + ( (d->rev != NULL) && strncmp(d->rev, rev, strlen(d->rev)) ) ) + continue; + + if (d->proto->vendor[0] == '\0') + strcpy(d->proto->vendor, vendor); + if (d->proto->model[0] == '\0') + strcpy(d->proto->model, model); + + return (d->proto); + } + + return (NULL); /* this means the list is badly terminated. */ +} /* find_drive_struct() */ + +/* + * read_toc() + * + * Read the table of contents from the CD. Return a pointer to a wm_cdinfo + * struct containing the relevant information (minus artist/cdname/etc.) + * This is a static struct. Returns NULL if there was an error. + * + * XXX allocates one trackinfo too many. + */ +struct wm_cdinfo * +read_toc() +{ + struct wm_playlist *l; + int i, pos; + + if ((drive.get_trackcount)(&drive, &thiscd.ntracks) < 0) + { + perror("trackcount"); + return (NULL); + } + + thiscd.artist[0] = thiscd.cdname[0] = '\0'; + thiscd.whichdb = thiscd.otherrc = thiscd.otherdb = thiscd.user = NULL; + thiscd.length = 0; + thiscd.autoplay = thiscd.playmode = thiscd.volume = 0; + + /* Free up any left-over playlists. */ + if (thiscd.lists != NULL) + { + for (l = thiscd.lists; l->name != NULL; l++) + { + free(l->name); + free(l->list); + } + free(thiscd.lists); + thiscd.lists = NULL; + } + + if (thiscd.trk != NULL) + free(thiscd.trk); + + thiscd.trk = malloc((thiscd.ntracks + 1) * sizeof(struct wm_trackinfo)); + if (thiscd.trk == NULL) + { + perror("malloc"); + return (NULL); + } + + for (i = 0; i < thiscd.ntracks; i++) + { + if ((drive.get_trackinfo)(&drive, i + 1, &thiscd.trk[i].data, + &thiscd.trk[i].start) < 0) + { + perror("CD track info read"); + return (NULL); + } + + thiscd.trk[i].avoid = thiscd.trk[i].data; + thiscd.trk[i].length = thiscd.trk[i].start / 75; + + thiscd.trk[i].songname = thiscd.trk[i].otherrc = + thiscd.trk[i].otherdb = NULL; + thiscd.trk[i].contd = 0; + thiscd.trk[i].volume = 0; + thiscd.trk[i].track = i + 1; + thiscd.trk[i].section = 0; + } + + if ((drive.get_cdlen)(&drive, &thiscd.trk[i].start) < 0) + { + perror("CD length read"); + return (NULL); + } + thiscd.trk[i].length = thiscd.trk[i].start / 75; + +/* Now compute actual track lengths. */ + pos = thiscd.trk[0].length; + + for (i = 0; i < thiscd.ntracks; i++) + { + thiscd.trk[i].length = thiscd.trk[i+1].length - pos; + pos = thiscd.trk[i+1].length; + if (thiscd.trk[i].data) + thiscd.trk[i].length = (thiscd.trk[i + 1].start - + thiscd.trk[i].start) * 2; + if (thiscd.trk[i].avoid) + wm_strmcpy(&thiscd.trk[i].songname, "DATA TRACK"); + } + + thiscd.length = thiscd.trk[thiscd.ntracks].length; + thiscd.cddbid = cddb_discid(drive); + + return (&thiscd); +} + +/* + * wm_cd_status() + * + * Return values: + * + * 0 No CD in drive. + * 1 CD in drive. + * 2 CD has just been inserted (TOC has been read) + * + * Updates cur_track, cur_pos_rel, cur_pos_abs and other variables. + */ +int +wm_cd_status( void ) +{ + static enum wm_cd_modes oldmode = WM_CDM_UNKNOWN; + enum wm_cd_modes mode; + int status, trackno = cur_track; + int ret = WM_CDS_DISC_READY; + + /* Open the drive. This returns 1 if the device isn't ready. */ + + status = wmcd_open(&drive); + + if (status < 0) + return (status); + if (status > 0) + return (WM_CDS_NO_DISC); + + /* If the user hit the stop button, don't pass PLAYING as oldmode. + * Likewise, if we've just started playing, don't remember that + * we were stopped before (or the state machine in get_drive_status + * can get confused.) + */ + if( (cur_cdmode == WM_CDM_STOPPED) || (cur_cdmode == WM_CDM_PLAYING) ) + oldmode = cur_cdmode; + + if( (drive.get_drive_status)(&drive, oldmode, &mode, &cur_frame, + &trackno, &cur_index) < 0) + { + perror("CD get drive status"); + return (-1); + } + oldmode = mode; + + if (mode == WM_CDM_EJECTED || mode == WM_CDM_UNKNOWN) + { + cur_cdmode = WM_CDM_EJECTED; + cur_track = -1; + cur_cdlen = cur_tracklen = 1; + cur_pos_abs = cur_pos_rel = cur_frame = 0; + + if (exit_on_eject) + exit(0); + + return (WM_CDS_NO_DISC); + } + + /* If there wasn't a CD before and there is now, learn about it. */ + if (cur_cdmode == WM_CDM_EJECTED) + { + cur_pos_rel = cur_pos_abs = 0; + + + status = wmcd_reopen( &drive ); + + if ((cd = read_toc()) == NULL) + { + if (exit_on_eject) + { + exit(-1); + } else { + return (-1); + } + } + + cur_nsections = 0; + cur_ntracks = cd->ntracks; + cur_cdlen = cd->length; + load(); + cur_artist = cd->artist; + cur_cdname = cd->cdname; + cur_cdmode = WM_CDM_STOPPED; + ret = WM_CDS_JUST_INSERTED; + } + + switch (mode) { + case WM_CDM_PLAYING: + case WM_CDM_PAUSED: + cur_pos_abs = cur_frame / 75; + + /* Only look up the current track number if necessary. */ + if (cur_track < 1 || cur_frame < cd->trk[cur_track-1].start || + cur_frame >= (cur_track >= cur_ntracks ? + (cur_cdlen + 1) * 75 : + cd->trk[cur_track].start)) + { + cur_track = 0; + while (cur_track < cur_ntracks && cur_frame >= + cd->trk[cur_track].start) + cur_track++; + } + if (cur_track >= 1 && trackno > cd->trk[cur_track-1].track) + cur_track++; + /* Fall through */ + + case WM_CDM_UNKNOWN: + if (mode == WM_CDM_UNKNOWN) + { + mode = WM_CDM_STOPPED; + cur_lasttrack = cur_firsttrack = -1; + } + /* Fall through */ + + case WM_CDM_STOPPED: + if (cur_track >= 1 && cur_track <= cur_ntracks) + { + cur_trackname = cd->trk[cur_track-1].songname; + cur_avoid = cd->trk[cur_track-1].avoid; + cur_contd = cd->trk[cur_track-1].contd; + cur_pos_rel = (cur_frame - + cd->trk[cur_track-1].start) / 75; + if (cur_pos_rel < 0) + cur_pos_rel = -cur_pos_rel; + } + + if( (playlist != NULL) && playlist[0].start & (cur_listno > 0)) + { + cur_pos_abs -= cd->trk[playlist[cur_listno-1]. + start - 1].start / 75; + cur_pos_abs += playlist[cur_listno-1].starttime; + } + if (cur_pos_abs < 0) + cur_pos_abs = cur_frame = 0; + + if (cur_track < 1) + cur_tracklen = cd->length; + else + cur_tracklen = cd->trk[cur_track-1].length; + /* Fall through */ + + case WM_CDM_TRACK_DONE: + cur_cdmode = mode; + break; + case WM_CDM_FORWARD: + case WM_CDM_EJECTED: + break; + } + + return (ret); +} + +#undef CLIF_VOL +#ifdef CLIF_VOL +/* + * cd_volume(vol, bal, max) + * + * Set the volume levels. "vol" and "bal" are the volume and balance knob + * settings, respectively. "max" is the maximum value of the volume knob + * (the balance knob is assumed to always go from 0 to 20.) + */ +void +cd_volume(vol, bal, max) + int vol, bal, max; +{ + int left, right, scale; + +/* + * Set "left" and "right" to volume-slider values accounting for the + * balance setting. + */ +/* printf("Vol = %d, Bal = %d, Max = %d\n", vol, bal, max); +*/ + + vol = (vol * 100 + max - 16) / max; + scale = (vol + 5) / 10; + + if (bal < 9) + { + right = vol - scale * (10 - bal); +#ifdef SYMETRIC_BALANCE + left = vol + scale * (10 - bal); +#else + left = vol; +#endif + } + else if (bal > 11) + { +#ifdef SYMETRIC_BALANCE + right = vol + scale * (bal - 10); +#else + right = vol; +#endif + left = vol - scale * (bal - 10); + } + else + left = right = vol; + +/* + * some plat_*.c is missing the limitation + */ + left = left < 0 ? 0 : left > 100 ? 100 : left; + right = right < 0 ? 0 : right > 100 ? 100 : right; +/* printf("Left = %d, Right = %d\n", left, right); +*/ + (void) (drive.set_volume)(&drive, left, right); +} /* cd_volume() */ + +#else + +/* + * cd_volume(vol, bal, max) + * + * Set the volume levels. "vol" and "bal" are the volume and balance knob + * settings, respectively. "max" is the maximum value of the volume knob + * (the balance knob is assumed to always go from 0 to 20.) + */ +void +cd_volume( int vol, int bal, int max ) +{ + int left, right; + +/* + * Set "left" and "right" to volume-slider values accounting for the + * balance setting. + * + * XXX - the maximum volume setting is assumed to be in the 20-30 range. + */ + if (bal < 9) + right = vol - (9 - bal) * 2; + else + right = vol; + if (bal > 11) + left = vol - (bal - 11) * 2; + else + left = vol; + + left = (left * 100 + max - 1) / max; + right = (right * 100 + max - 1) / max; + if (left > 100) + left = 100; + if (right > 100) + right = 100; + + (void) (drive.set_volume)(&drive, left, right); +} /* cd_volume() */ + +#endif /* CLIF_VOL */ + + +/* + * wm_cd_pause() + * + * Pause the CD, if it's in play mode. If it's already paused, go back to + * play mode. + */ +void +wm_cd_pause( void ) +{ + static int paused_pos; + + if (cur_cdmode == WM_CDM_EJECTED) /* do nothing if there's no CD! */ + return; + + switch (cur_cdmode) { + case WM_CDM_PLAYING: /* playing */ + cur_cdmode = WM_CDM_PAUSED; + (drive.pause)(&drive); + paused_pos = cur_pos_rel; + break; + + case WM_CDM_PAUSED: /* paused */ + cur_cdmode = WM_CDM_PLAYING; +/* (drive.resume)(&drive); */ + if ((drive.resume)(&drive)) + wm_cd_play(cur_track, paused_pos, + playlist[cur_listno-1].end); + default: /* */ + break; + } +} /* wm_cd_pause() */ + +/* + * wm_cd_stop() + * + * Stop the CD if it's not already stopped. + */ +void +wm_cd_stop( void ) +{ + if (cur_cdmode == WM_CDM_EJECTED) + return; + + if (cur_cdmode != WM_CDM_STOPPED) + { + cur_lasttrack = cur_firsttrack = -1; + cur_cdmode = WM_CDM_STOPPED; + (drive.stop)(&drive); + cur_track = 1; + } +} /* wm_cd_stop() */ + +/* + * wm_cd_play_chunk(start, end) + * + * Play the CD from one position to another (both in frames.) + */ +void +wm_cd_play_chunk( int start, int end, int realstart ) +{ + if (cur_cdmode == WM_CDM_EJECTED || cd == NULL) + return; + + end--; + if (start >= end) + start = end-1; + + (drive.play)(&drive, start, end, realstart); +} + +/* + * wm_cd_play(starttrack, pos, endtrack) + * + * Start playing the CD or jump to a new position. "pos" is in seconds, + * relative to start of track. + */ +void +wm_cd_play( int start, int pos, int end ) +{ + if (cur_cdmode == WM_CDM_EJECTED || cd == NULL) + return; + + cur_firsttrack = start; + start--; + end--; + cur_lasttrack = end; + + wm_cd_play_chunk(cd->trk[start].start + pos * 75, end >= cur_ntracks ? + cur_cdlen * 75 : cd->trk[end].start - 1, + cd->trk[start].start); + + /* So we don't update the display with the old frame number */ + wm_cd_status(); + cur_frame = cd->trk[start].start + pos * 75; + cur_track = cur_firsttrack; + cur_cdmode = WM_CDM_PLAYING; +} + +/* + * Set the offset into the current track and play. -1 means end of track + * (i.e., go to next track.) + */ +void +wm_cd_play_from_pos( int pos ) +{ + if (pos == -1) + if (cd) + pos = cd->trk[cur_track - 1].length - 1; + if (cur_cdmode == WM_CDM_PLAYING) + wm_cd_play(cur_track, pos, playlist[cur_listno-1].end); +} /* wm_cd_play_from_pos() */ + +/* + * Eject the current CD, if there is one, and set the mode to 5. + * + * Returns 0 on success, 1 if the CD couldn't be ejected, or 2 if the + * CD contains a mounted filesystem. + */ +int +wm_cd_eject( void ) +{ + int status; + + status = (drive.eject)(&drive); + if (status < 0) + { + if (status == -3) + { + return (2); + } else { + return (1); + } + } + + if (exit_on_eject) + exit(0); + + cur_track = -1; + cur_cdlen = cur_tracklen = 1; + cur_pos_abs = cur_pos_rel = cur_frame = 0; + cur_cdmode = WM_CDM_EJECTED; + + return (0); +} + +int wm_cd_closetray(void) +{ + return((drive.closetray)(&drive) ? 0 : wm_cd_status()==2 ? 1 : 0); +} /* wm_cd_closetray() */ + +/* + * find_trkind(track, index, start) + * + * Start playing at a particular track and index, optionally using a particular + * frame as a starting position. Returns a frame number near the start of the + * index mark if successful, 0 if the track/index didn't exist. + * + * This is made significantly more tedious (though probably easier to port) + * by the fact that CDROMPLAYTRKIND doesn't work as advertised. The routine + * does a binary search of the track, terminating when the interval gets to + * around 10 frames or when the next track is encountered, at which point + * it's a fair bet the index in question doesn't exist. + */ +int +find_trkind( int track, int index, int start ) +{ + int top = 0, bottom, current, interval, ret = 0, i; + + if( cur_cdmode == WM_CDM_EJECTED || cd == NULL ) + return ( 0 ); /* WARNING: was nothing */ + + for (i = 0; i < cur_ntracks; i++) + if (cd->trk[i].track == track) + break; + bottom = cd->trk[i].start; + + for (; i < cur_ntracks; i++) + if (cd->trk[i].track > track) + break; + + top = i == cur_ntracks ? (cd->length - 1) * 75 : cd->trk[i].start; + + if (start > bottom && start < top) + bottom = start; + + current = (top + bottom) / 2; + interval = (top - bottom) / 4; + + do { + wm_cd_play_chunk(current, current + 75, current); + + if (wm_cd_status() != 1) + return (0); + while (cur_frame < current) + if (wm_cd_status() != 1 || cur_cdmode != WM_CDM_PLAYING) + return (0); + else + wm_susleep(1); + + if (cd->trk[cur_track - 1].track > track) + break; + + if (cur_index >= index) + { + ret = current; + current -= interval; + } + else + current += interval; + interval /= 2; + } while (interval > 2); + + return (ret); +} /* find_trkind() */ + +/* + * Read the initial volume from the drive, if available. Set cur_balance to + * the balance level (0-20, 10=centered) and return the proper setting for + * the volume knob. + * + * "max" is the maximum value of the volume knob. + */ +int +wm_cd_read_initial_volume( int max ) +{ + int left, right; + + if ((drive.get_volume)(&drive, &left, &right) < 0 || left == -1) + return (max); + + left = (left * max + 99) / 100; + right = (right * max + 99) / 100; + + if (left < right) + { + wm_cd_cur_balance = (right - left) / 2 + 11; + if (wm_cd_cur_balance > 20) + wm_cd_cur_balance = 20; + + return (right); + } + else if (left == right) + { + wm_cd_cur_balance = 10; + return (left); + } + else + { + wm_cd_cur_balance = (right - left) / 2 + 9; + if (wm_cd_cur_balance < 0) + wm_cd_cur_balance = 0; + + return (left); + } +} /* wm_cd_read_initial_volume() */ + +/* + * Prototype wm_drive structure, with generic functions. The generic functions + * will be replaced with drive-specific functions as appropriate once the drive + * type has been sensed. + */ +struct wm_drive generic_proto = { + -1, /* fd */ + "Generic\0", /* vendor */ + "drive type\0 ", /* model */ + "\0", /* revision */ + NULL, /* aux */ + NULL, /* daux */ + + gen_init, /* functions... */ + gen_get_trackcount, + gen_get_cdlen, + gen_get_trackinfo, + gen_get_drive_status, + gen_get_volume, + gen_set_volume, + gen_pause, + gen_resume, + gen_stop, + gen_play, + gen_eject, + gen_closetray +}; diff --git a/ascd/libworkman/database.c b/ascd/libworkman/database.c new file mode 100644 index 0000000..6bb50b8 --- /dev/null +++ b/ascd/libworkman/database.c @@ -0,0 +1,1522 @@ +/* + * $Id: database.c,v 1.8 1999/05/28 03:35:54 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Manage the CD database. All these routines assume that the "cd" global + * structure contains current information (as far as the outside world knows; + * obviously it won't contain track titles just after a CD is inserted.) + */ + +static char database_id[] = "$Id: database.c,v 1.8 1999/05/28 03:35:54 dirk Exp $"; + +#define RCFILE "/.workmanrc" +#define DBFILE "/.workmandb" +#define FUZZFRAMES 75 + +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/time.h> +#include <sys/stat.h> +#include "include/wm_config.h" +#include "include/wm_helpers.h" +#include "include/wm_struct.h" +#include "include/wm_cdinfo.h" +#include "include/wm_cddb.h" +#include "include/wm_index.h" +#include "include/wm_database.h" + +#define WM_MSG_CLASS WM_MSG_CLASS_DB + +#define SWALLOW_LINE(fp) { int c; while ((c = getc(fp)) != '\n' && c != EOF); } + +int suppress_locking = 0; /* Turn off locking of datafile (dangerous) */ + +char *rcfile = NULL; /* Personal rcfile */ +char *dbfiles = NULL; /* Colon-separated list of databases */ +char **databases = NULL; /* NULL-terminated list of databases */ + +char *otherrc = NULL; /* Unrecognized cruft from start of rcfile */ + +long rcpos, rclen; /* XXX */ + +int found_in_db, found_in_rc; +long holepos, firstpos; + +int fuzz_frames = FUZZFRAMES; + +int wm_db_save_disabled = FALSE; + +int cur_playnew = -1; + +extern int cur_ntracks, cur_nsections; + +int mark_a = 0; +int mark_b = 0; + + +/* + * + */ +int wm_db_get_playnew( void ) +{ + return 0; +} + +/* + * split_workmandb() + * + * Split the WORKMANDB environment variable, if any, into a list of database + * files in the global "databases". If WORKMANDB is not available, make + * a single entry with $HOME/DBFILE. + * + * Also, fill the "rcfile" global with the personal preferences filename. + * + * The environment variables should have already been read and placed in the + * "rcfile" and "dbfiles" globals, respectively. + */ +void +split_workmandb( void ) +{ + int ndbs, i; + char *home, *wmdb; + int no_rc = 0, no_db = 0; + + if (rcfile == NULL) + { + if ((home = getenv("HOME")) != NULL) + { + rcfile = malloc(strlen(home) + sizeof(RCFILE)); + if (rcfile == NULL) + { + +nomem: + perror("split_workmandb()"); + exit(1); + } + + strcpy(rcfile, home); + strcat(rcfile, RCFILE); + } + else + no_rc = 1; + + } + + if ((wmdb = dbfiles) == NULL) + { + if ((home = getenv("HOME")) != NULL) + { + wmdb = malloc(strlen(home) + sizeof(DBFILE)); + if (wmdb == NULL) + goto nomem; + + databases = malloc(2 * sizeof (databases[0])); + if (databases == NULL) + goto nomem; + + strcpy(wmdb, home); + strcat(wmdb, DBFILE); + databases[0] = wmdb; + databases[1] = NULL; + } + else + { + static char *emptydb = NULL; + + databases = &emptydb; + no_db = 1; + } + } + else + { + ndbs = 1; + for (home = wmdb; *home; home++) + if (*home == ':') + { + *home = '\0'; + ndbs++; + } + + databases = malloc((ndbs + 1) * sizeof(databases[0])); + if (databases == NULL) + goto nomem; + + for (i = 0; i < ndbs; i++) + { + databases[i] = wmdb; + wmdb += strlen(wmdb) + 1; + } + + databases[i] = NULL; + } + + if (no_db || no_rc) + { + fprintf(stderr, +"WorkMan was run without a home directory, probably by a system daemon.\n"); + fprintf(stderr, "It doesn't know where to find "); + if (no_rc) + { + fprintf(stderr, "your personal preferences file "); + if (no_db) + fprintf(stderr, "or the\ndatabase of CD descriptions"); + } + else + fprintf(stderr, "the database of CD descriptions"); + + fprintf(stderr, +".\nYou can use the X resources \"workman.db.shared\" and \"workman.db.personal\"\nto tell WorkMan where to look.\n"); + + wm_db_save_disabled = TRUE; + } +} + +/* + * print_cdinfo(cd, prefs) + * + * cd A pointer to a cdinfo struct. + * prefs Flag: write personal preferences? + * + * Print a CD's information (in more or less readable form) to a buffer. + * Returns a pointer to the buffer. + * + * XXX - could be more efficient about calling wm_strmcat() and strlen(). + */ +char * +print_cdinfo(struct wm_cdinfo *cd, int prefs) +{ + int i; + char tempbuf[2000]; /* XXX - is this always big enough? */ + static char *cdibuf = NULL; + struct wm_playlist *l; + + sprintf(tempbuf, "\ntracks %d", cd->ntracks); + for (i = 0; i < cur_ntracks; i++) + if (cd->trk[i].section < 2) + sprintf(tempbuf + strlen(tempbuf), " %d", + cd->trk[i].start); + sprintf(tempbuf + strlen(tempbuf), " %d\n", cd->length); + + wm_strmcpy(&cdibuf, tempbuf); + + if (cur_nsections) + { + sprintf(tempbuf, "sections %d", cur_nsections); + /* fixed a bug here */ + for (i = 0; i < cur_ntracks; i++) + if (cd->trk[i].section > 1) + sprintf(tempbuf + strlen(tempbuf), " %d", + cd->trk[i].start); + sprintf(tempbuf + strlen(tempbuf), "\n"); + + wm_strmcat(&cdibuf, tempbuf); + } + + if (prefs) + { + if (cd->autoplay) + wm_strmcat(&cdibuf, "autoplay\n"); + for (l = cd->lists; l != NULL && l->name != NULL; l++) + { + wm_strmcat(&cdibuf, "playlist "); + + i = strlen(cdibuf) - 1; + wm_strmcat(&cdibuf, l->name); + while (cdibuf[++i]) + if (cdibuf[i] == ' ' || cdibuf[i] == '\t') + cdibuf[i] = '_'; + + if (l->list != NULL) + { + for (i = 0; l->list[i]; i++) + ; + sprintf(tempbuf, " %d", i); + wm_strmcat(&cdibuf, tempbuf); + for (i = 0; l->list[i]; i++) + { + sprintf(tempbuf, " %d", l->list[i]); + wm_strmcat(&cdibuf, tempbuf); + } + wm_strmcat(&cdibuf, "\n"); + } + else + wm_strmcat(&cdibuf, " 0\n"); + } + + if (cd->volume) + { + /* + * Have to maintain compatibility with old versions, + * where volume was 0-32. + */ + sprintf(tempbuf, "cdvolume %d\n", (cd->volume * 32) / 100); + wm_strmcat(&cdibuf, tempbuf); + } + + if (cd->playmode) + { + sprintf(tempbuf, "playmode %d\n", cd->playmode); + wm_strmcat(&cdibuf, tempbuf); + } + + if (mark_a) + { + sprintf(tempbuf, "mark %d START\n", mark_a); + wm_strmcat(&cdibuf, tempbuf); + } + if (mark_b) + { + sprintf(tempbuf, "mark %d END\n", mark_b); + wm_strmcat(&cdibuf, tempbuf); + } + + if (cd->otherrc) + wm_strmcat(&cdibuf, cd->otherrc); + + for (i = 0; i < cur_ntracks; i++) + { + if (cd->trk[i].avoid) + { + sprintf(tempbuf, "dontplay %d\n", i + 1); + wm_strmcat(&cdibuf, tempbuf); + } + if (cd->trk[i].volume) + { + sprintf(tempbuf, "volume %d %d\n", i + 1, + (cd->trk[i].volume * 32) / 100); + wm_strmcat(&cdibuf, tempbuf); + } + if (cd->trk[i].otherrc) + wm_strmcat(&cdibuf, cd->trk[i].otherrc); + } + } + else + { + if (cd->cdname[0]) + { + wm_strmcat(&cdibuf, "cdname "); + wm_strmcat(&cdibuf, cd->cdname); + wm_strmcat(&cdibuf, "\n"); + } + + if (cd->artist[0]) + { + wm_strmcat(&cdibuf, "artist "); + wm_strmcat(&cdibuf, cd->artist); + wm_strmcat(&cdibuf, "\n"); + } + + if (cd->otherdb) + wm_strmcat(&cdibuf, cd->otherdb); + + for (i = 0; i < cur_ntracks; i++) + { + if (cd->trk[i].section > 1) + wm_strmcat(&cdibuf, "s-"); + wm_strmcat(&cdibuf, "track "); + if (cd->trk[i].songname != NULL) + wm_strmcat(&cdibuf, cd->trk[i].songname); + wm_strmcat(&cdibuf, "\n"); + if (cd->trk[i].contd) + { + if (cd->trk[i].section > 1) + wm_strmcat(&cdibuf, "s-"); + wm_strmcat(&cdibuf, "continue\n"); + } + if (cd->trk[i].otherdb) + wm_strmcat(&cdibuf, cd->trk[i].otherdb); + } + } + + return (cdibuf); +} /* print_cdinfo() */ + +/* + * Open the rcfile for reading or writing. + * + * name Filename + * mode "r" or "w" + */ +FILE * +open_rcfile(char *name, char *mode) +{ + FILE *fp; + struct stat st; + + fp = fopen(name, mode); + if (fp == NULL) + { + if (errno != ENOENT || mode[0] == 'w') + perror(name); + } + else + { + /* Don't let people open directories or devices */ + if (fstat(fileno(fp), &st) < 0) + { + perror(name); + fclose(fp); + return (NULL); + } + +#ifdef S_ISREG + if (! S_ISREG(st.st_mode)) +#else + if ((st.st_mode & S_IFMT) != S_IFREG) +#endif + { + errno = EISDIR; + perror(name); + fclose(fp); + return (NULL); + } + + if (mode[0] == 'w') /* create -- put data in so locks work */ + { + fputs("# WorkMan database file\n", fp); + fclose(fp); + fp = fopen(name, "r+"); + if (fp == NULL) + if (errno != ENOENT) + perror(name); + } + } + + return (fp); +} + +/* + * + * allocate and clear "trackmap". + * + */ +int *reset_tracks(void) +{ + int i, j; + int *trackmap; + /* + * Since we access track numbers indirectly (to handle sections + * with at least a little elegance), the track mapping needs to be + * set up before we read anything. Initially it must assume that + * no sections will appear in this datafile. + */ + trackmap = malloc(sizeof(int) * cur_ntracks); + if (trackmap == NULL) + { + perror("trackmap"); + exit(1); + } + j = 0; + for (i = 0; i < cd->ntracks; i++) + { + trackmap[i] = j; + while (cd->trk[++j].section > 1) + ; + } + return trackmap; +} /* reset_tracks() */ + +/* + * Load a new-format database file, searching for a match with the currently + * inserted CD. Modify the in-core copy of the CD info based on what's found + * in the database. + * + * Returns 1 if there was a match or 0 if not. + * + * fp FILE* of database or rcfile. + * prefs 1 if we're searching .workmanrc, 0 for .workmandb, + * 2 if just reading settings + * scan Scan for "tracks" location and entry size only + * holesize_wanted How big a hole we're looking for, if any. + * + * If a hole was found along the way, update the global "holepos" with its + * starting offset in the file. A hole is defined as a bunch of blank lines + * preceding a "tracks" line. Holepos will contain the best match. + * + * In addition, "firstpos" will be filled with the position of the first + * "tracks" keyword, so we know how much room is available for global + * settings at the rcfile's start. + */ +int +search_db( FILE *fp, int prefs, int scan, int holesize_wanted ) + +{ + char keyword[64], listname[64], *c; + int b, i, j, track = 0, listsize, ntracks, scratch, searching = 1; + int *trackmap = 0, gotsections = 0; + int fudge, maxfudge, sizediff, bestfudge = 0; + long pos = 0, thisholepos = -1, holesize = 99991239; + struct wm_playlist *l; + + rclen = 0; + + /* We may not find any holes at all! */ + if (holesize_wanted) + holepos = -1; + + if( prefs != 2 ) + trackmap = reset_tracks(); + + if (prefs) + freeup(&otherrc); + firstpos = -1; + while (! feof(fp)) + { + pos = ftell(fp); + keyword[0] = '\0'; + do + b = getc(fp); + while (b != EOF && b != '\n' && isspace(b)); + + if (b == EOF || feof(fp)) + break; + + if (b != '\n') + { + keyword[0] = b; + fscanf(fp, "%s", &keyword[1]); + } + if (keyword[0] == '\0') /* Blank line. */ + { + if (thisholepos < 0) + thisholepos = pos; + continue; + } + + /* Strip off "s-" if we've seen a "sections" keyword */ + if (gotsections && keyword[0] == 's' && keyword[1] == '-') + for (c = &keyword[2]; (c[-2] = *c) != '\0'; c++) + ; + + /* If this is the start of a CD entry, see if it matches. */ + if (! strcmp(keyword, "tracks")) + { + if (prefs == 2) + break; + + /* Is this the end of a hole? */ + if (holesize_wanted && (thisholepos >= 0)) + { + /* Yep. Is it better than the last one? */ + if (pos - thisholepos < holesize && pos - + thisholepos >= holesize_wanted) + { + holepos = thisholepos; + holesize = pos - thisholepos; + } + thisholepos = -1; + } + + /* Is it the start of the CD entries? */ + if (firstpos == -1) + firstpos = pos; + + /* Is this the end of the entry we really wanted? */ + if (! searching) + { + rclen = pos - rcpos; + break; + } + + /* If we have a near match, indicate that we + should stop reading tracks, etc now */ + if (searching == 2) + { + searching = 3; + continue; + } + + fscanf(fp, "%d", &ntracks); + + if (ntracks != cd->ntracks) + { +chomp: + SWALLOW_LINE(fp); + continue; + } + + fudge = 0; + maxfudge = (ntracks * fuzz_frames) >> 1; + track = 0; + for (i = 0; i < ntracks; i++) + { + fscanf(fp, "%d", &scratch); + if (scratch != cd->trk[track].start) + { + sizediff = abs(scratch - cd->trk[track].start); + if (sizediff > fuzz_frames || + (sizediff && scan)) + break; + fudge += sizediff; + } + while (cd->trk[++track].section > 1) + ; + } + if (i != ntracks) + goto chomp; + + if (fudge > 0) /* best near match? */ + { + if (fudge > maxfudge) + goto chomp; + if (bestfudge == 0 || fudge < bestfudge) + bestfudge = fudge; + else + goto chomp; + rcpos = pos; + track = 0; + searching = 2; + } + else /* probably exact match */ + { + fscanf(fp, "%d", &scratch); + + if (scratch != -1 && scratch != cd->length) + goto chomp; + + /* Found it! */ + rcpos = pos; + track = 0; + searching = 0; + } + + SWALLOW_LINE(fp); /* Get rid of newline */ + } + + /* Global mode stuff goes here */ + else if (! strcmp(keyword, "cddbprotocol")) + { + getc(fp); + i = getc(fp); /* only first letter is used */ + cddb.protocol = i == 'c' ? 1 : + i == 'h' ? 2 : 3 ; + do + i = getc(fp); + while (i != '\n' && i != EOF); + } + + else if (! strcmp(keyword, "cddbserver")) + { + getc(fp); /* lose the space */ + if (cddb.cddb_server[0]) + do + i = getc(fp); + while (i != '\n' && i != EOF); + else + { + fgets(cddb.cddb_server, + sizeof(cddb.cddb_server), fp); + if ((i = strlen(cddb.cddb_server))) + cddb.cddb_server[i - 1] = '\0'; + } + } + + else if (! strcmp(keyword, "cddbmailadress")) + { + getc(fp); /* lose the space */ + if (cddb.mail_adress[0]) + do + i = getc(fp); + while (i != '\n' && i != EOF); + else + { + fgets(cddb.mail_adress, + sizeof(cddb.mail_adress), fp); + if ((i = strlen(cddb.mail_adress))) + cddb.mail_adress[i - 1] = '\0'; + } + } + + else if (! strcmp(keyword, "cddbpathtocgi")) + { + getc(fp); /* lose the space */ + if (cddb.path_to_cgi[0]) + do + i = getc(fp); + while (i != '\n' && i != EOF); + else + { + fgets(cddb.path_to_cgi, + sizeof(cddb.path_to_cgi), fp); + if ((i = strlen(cddb.path_to_cgi))) + cddb.path_to_cgi[i - 1] = '\0'; + } + } + + else if (! strcmp(keyword, "cddbproxy")) + { + getc(fp); /* lose the space */ + if (cddb.proxy_server[0]) + do + i = getc(fp); + while (i != '\n' && i != EOF); + else + { + fgets(cddb.proxy_server, + sizeof(cddb.proxy_server), fp); + if ((i = strlen(cddb.proxy_server))) + cddb.proxy_server[i - 1] = '\0'; + } + } + + else if (! strcmp(keyword, "whendone")) + { + getc(fp); + i = getc(fp); /* only first letter is used */ + if (cur_stopmode == -1) /* user's setting preferred */ + cur_stopmode = i == 's' ? 0 : i == 'r' ? 1 : 2; + do + i = getc(fp); + while (i != '\n' && i != EOF); + } + + else if (! strcmp(keyword, "playnew")) + { + if (cur_playnew == -1) + cur_playnew = 1; + SWALLOW_LINE(fp); + } + + /* If we're searching, skip to the next "tracks" line. */ + else if (((searching & 1)|| scan) + && !(prefs && firstpos == -1)) + SWALLOW_LINE(fp) + + else if (! strcmp(keyword, "sections")) + { + gotsections = 1; + fscanf(fp, "%d", &ntracks); + + free(trackmap); + trackmap = (int *) malloc(sizeof(int) * + (cur_ntracks + ntracks)); + if (trackmap == NULL) + { + perror("section mapping"); + exit(1); + } + + /* + * If sections are already defined, use these as a + * reference, mapping this CD entry's section numbers + * to the ones in core. + * + * Otherwise, split the CD up according to the sections + * listed here. + */ + if (cur_nsections) + { + track = 0; + i = 0; + while (ntracks) + { + ntracks--; + fscanf(fp, "%d", &scratch); + while (scratch > cd->trk[track].start) + { + if (cd->trk[track].section < 2) + trackmap[i++] = track; + ++track; + + if (track == cur_ntracks) + break; + } + + /* rc has later sections than db... */ + if (track == cur_ntracks) + break; + + /* Matches can be approximate */ + if (scratch+75 > cd->trk[track].start && + scratch-75 < cd->trk[track].start) + trackmap[i++] = track++; + else + trackmap[i++] = -1; + + if (track == cur_ntracks) + break; + } + + /* This only happens if track == cur_ntracks */ + while (ntracks--) + trackmap[i++] = -1; + + while (track < cur_ntracks) + { + if (cd->trk[track].section < 2) + trackmap[i++] = track; + track++; + } + + track = 0; + SWALLOW_LINE(fp); + } + else + { + while (ntracks--) + { + fscanf(fp, "%d", &scratch); + split_trackinfo(scratch); + } + + for (i = 0; i < cur_ntracks; i++) + { + trackmap[i] = i; + /* split_trackinfo() sets this */ + cd->trk[i].contd = 0; + } + + SWALLOW_LINE(fp); + } + } + + else if (! strcmp(keyword, "track")) + { + char buf[502]; + + getc(fp); /* lose the space */ + /* don't overwrite existing track names. */ + /* However, overwrite them if there was a "bad" fuzzy match before */ + if ((trackmap[track] == -1 || track > (cd->ntracks + cur_nsections)) && (searching == 2)) + SWALLOW_LINE(fp) + else if (cd->trk[trackmap[track]].songname && + cd->trk[trackmap[track]].songname[0]) + do + i = getc(fp); + while (i != '\n' && i != EOF); + else + { + fgets(buf, sizeof(buf), fp); + if( (i = strlen(buf)) ) + buf[i - 1] = '\0'; + wm_strmcpy(&cd->trk[trackmap[track]].songname, + buf); + } + track++; + } + + else if (! strcmp(keyword, "playmode")) + fscanf(fp, "%d", &cd->playmode); + + else if (! strcmp(keyword, "autoplay")) + cd->autoplay = 1; + + else if (! strcmp(keyword, "cdname")) + { + /* because of fuzzy matching that may change + the disk contents, we reset everything when + we find the name, in hopes that we will recover + most, if not all, of the information from the + file. */ +/* + * nasty bug was here. Was it? BUGBUGBUG + * + * wipe_cdinfo(); + */ trackmap = reset_tracks(); + + getc(fp); /* lose the space */ + /* don't overwrite existing cd name. */ + if (cd->cdname[0] && (searching == 2)) + do + i = getc(fp); + while (i != '\n' && i != EOF); + else + { + if (searching > 1) + { + strcpy(cd->cdname, "Probably://"); + fgets(cd->cdname + strlen(cd->cdname), sizeof(cd->cdname), fp); + } + else + { + fgets(cd->cdname, sizeof(cd->cdname), fp); + } + if ( (i = strlen(cd->cdname)) ) + cd->cdname[i - 1] = '\0'; + } + } + + else if (! strcmp(keyword, "artist")) + { + getc(fp); /* lose the space */ + /* don't overwrite existing artist names. */ + if (cd->artist[0]) + do + i = getc(fp); + while (i != '\n' && i != EOF); + else + { + fgets(cd->artist, sizeof(cd->artist), fp); + if( (i = strlen(cd->artist)) ) + cd->artist[i - 1] = '\0'; + } + } + + else if (! strcmp(keyword, "cdvolume")) + { + fscanf(fp, "%d", &cd->volume); + cd->volume = (cd->volume * 100) / 32; + } + + else if (! strcmp(keyword, "dontplay")) + { + fscanf(fp, "%d", &i); + if (trackmap[i - 1] != -1) + cd->trk[trackmap[i - 1]].avoid = 1; + } + + else if (! strcmp(keyword, "continue")) + { + if (trackmap[track - 1] != -1) + cd->trk[trackmap[track - 1]].contd = 1; + } + + else if (! strcmp(keyword, "volume")) + { + fscanf(fp, "%d", &i); + if (trackmap[i - 1] == -1) + SWALLOW_LINE(fp) + else + { + i = trackmap[i - 1]; + fscanf(fp, "%d", &cd->trk[i].volume); + cd->trk[i].volume = (cd->trk[i].volume*100)/32; + if (cd->trk[i].volume > 32) + cd->trk[i].volume = 0; + } + } + + else if (! strcmp(keyword, "playlist")) + { + getc(fp); + fscanf(fp, "%s", listname); + +/* XXX take this out at some point */ + if (! strcmp(listname, "Default")) + strcpy(listname, "List A"); + + for (i = 0; listname[i]; i++) + if (listname[i] == '_') + listname[i] = ' '; + + l = new_list(cd, listname); + if (l == NULL) + { +plnomem: + perror("playlist read"); + exit(1); + } + + fscanf(fp, "%d", &listsize); + + l->list = malloc(sizeof(int) * (listsize + 1)); + if (l->list == NULL) + goto plnomem; + + /* Leave out tracks that weren't in .workmandb. */ + j = 0; + for (i = 0; i < listsize; i++) + { + fscanf(fp, "%d", &scratch); + scratch = trackmap[scratch - 1]; + if (scratch != -1) + l->list[j++] = scratch + 1; + } + + l->list[j] = 0; + } + + else if (! strcmp(keyword, "mark")) + { + int mark_val = -1, mark_namelen; + char mark_name[32]; + + fscanf(fp, "%d", &mark_val); + if (mark_val == -1) + goto chomp; + + if (getc(fp) != ' ') + continue; + + fgets(mark_name, sizeof(mark_name), fp); + if( ( mark_namelen = strlen(mark_name)) ) + mark_name[mark_namelen - 1] = '\0'; + + if (! strcmp(mark_name, "START")) + set_abtimer(0, mark_val); + else if (! strcmp(mark_name, "END")) + set_abtimer(1, mark_val); + } + + /* Unrecognized keyword. Put it in the right place. */ + else + { + char **buf, input[BUFSIZ]; + + if (track && trackmap[track - 1] == -1) + { + SWALLOW_LINE(fp); + continue; + } + + i = track ? trackmap[track - 1] : 0; + buf = prefs ? i ? &cd->trk[i].otherrc : &cd->otherrc : + i ? &cd->trk[i].otherdb : &cd->otherdb; + if (firstpos == -1) { + if (prefs) { + buf = &otherrc; + } else { + goto chomp; + } /* if() else */ + } /* if() */ + wm_strmcat(buf, keyword); + do { + input[sizeof(input) - 1] = 'x'; + fgets(input, sizeof(input), fp); + wm_strmcat(buf, input); + } while (input[sizeof(input) - 1] != 'x'); + } + } + + if (rclen == 0 && !searching) + rclen = pos - rcpos; + + if (searching > 1) /* A near match has been found. Good enough. */ + searching = 0; + + cddb_struct2cur(); + return (! searching); + +} /* search_db() */ + +/* + * Delay some amount of time without using interval timers. + */ +void +spinwheels(int secs) { + struct timeval tv; + + tv.tv_usec = 0; + tv.tv_sec = secs; + select(0, NULL, NULL, NULL, &tv); +} /* spinwheels() */ + +/* + * lockit(fd, type) + * + * fd file descriptor + * type lock type + * + * Lock a file. Time out after a little while if we can't get a lock; + * this usually means the locking system is broken. + * + * Unfortunately, if there are lots of people contending for a lock, + * this can result in the file not getting locked when it probably should. + */ +int +lockit(int fd, int type) +{ + struct flock fl; + int result, timer = 0; + + if (suppress_locking) + return (0); + + fl.l_type = type; + fl.l_whence = 0; + fl.l_start = 0; + fl.l_len = 0; + + while ((result = fcntl(fd, F_SETLK, &fl)) < 0) + { + if (errno != EACCES || errno != EAGAIN) + break; + if (timer++ == 30) + { + errno = ETIMEDOUT; + break; + } + + spinwheels(1); + } + + return (result); +} /* lockit */ + +/* + * Search all the database files and our personal preference file for + * more information about the current CD. + */ +void +load( void ) +{ + FILE *fp; + char **dbfile; + int locked = 0; + int dbfound = 0, *trklist, i; + unsigned long dbpos; + +/* This is some kind of profiling code. I don't change it + to wm_lib_message() for now... */ +#ifdef DEBUG + long t1, t2; + if( getenv( "WORKMAN_DEBUG" ) != NULL ) + { + time(&t1); + printf("%s (%d): search start = %ld\n", __FILE__, __LINE__, t1); + fflush(stdout); + } +#endif + + dbfile = databases; + + found_in_db = 0; + + /* Turn the cd->trk array into a simple array of ints. */ + trklist = (int *)malloc(sizeof(int) * cd->ntracks); + for (i = 0; i < cd->ntracks; i++) + trklist[i] = cd->trk[i].start; + + do { + if (*dbfile && idx_find_entry(*dbfile, cd->ntracks, trklist, + cd->length * 75, 0, &dbpos) == 0) + dbfound = 1; + + fp = *dbfile ? open_rcfile(*dbfile, "r") : NULL; + if (fp != NULL) + { + if (lockit(fileno(fp), F_RDLCK)) + perror("Couldn't get read (db) lock"); + else + locked = 1; + + if (dbfound) + fseek(fp, dbpos, 0); + + if (search_db(fp, 0, 0, 0)) + { + found_in_db = 1; + cd->whichdb = *dbfile; + } + + if (locked && lockit(fileno(fp), F_UNLCK)) + perror("Couldn't relinquish (db) lock"); + + fclose(fp); + } + } while (*++dbfile != NULL && cd->whichdb == NULL); + +#ifdef DEBUG + if( getenv( "WORKMAN_DEBUG" ) != NULL ) + { + time(&t2); + printf("%s (%d): db search end = %ld, elapsed = %ld\n", __FILE__, __LINE__, t2, t2 - t1); + fflush(stdout); + } +#endif + + fp = rcfile ? open_rcfile(rcfile, "r") : NULL; + if (fp != NULL) + { + locked = 0; + if (lockit(fileno(fp), F_RDLCK)) + perror("Couldn't get read (rc) lock"); + else + locked = 1; + + rcpos = 0; + found_in_rc = search_db(fp, 1, 0, 0); + if (! found_in_rc) + cd->autoplay = wm_db_get_playnew(); + + if (locked && lockit(fileno(fp), F_UNLCK)) + perror("Couldn't relinquish (rc) lock"); + + fclose(fp); + } + + free(trklist); + + if (cur_playnew == -1) + cur_playnew = 0; + +#ifdef DEBUG + if( getenv( "WORKMAN_DEBUG" ) != NULL ) + { + time(&t2); + printf("%s (%d): search end = %ld, elapsed = %ld\n", __FILE__, __LINE__, t2, t2 - t1); + fflush(stdout); + } +#endif +} /* load() */ + +/* + * Load program settings from the rcfile. + */ +void +load_settings( void ) +{ + FILE *fp; + int locked; + + fp = rcfile ? open_rcfile(rcfile, "r") : NULL; + if (fp != NULL) + { + locked = 0; + if (lockit(fileno(fp), F_RDLCK)) + perror("Couldn't get read (rc) lock"); + else + locked = 1; + + rcpos = 0; + found_in_rc = search_db(fp, 2, 0, 0); + if (! found_in_rc) + cd->autoplay = wm_db_get_playnew(); + + if (locked && lockit(fileno(fp), F_UNLCK)) + perror("Couldn't relinquish (rc) lock"); + + fclose(fp); + } +} /* load_settings() */ + +/* + * save_globals() + * + * Save the global preferences, scooting CD entries to the end if needed. + * The assumption here is that the rcfile is locked, and that firstpos has + * been set by a previous scan. + */ +void +save_globals(FILE *fp) +{ + char *globes = NULL, *cdentry = NULL, temp[100]; + long curpos; + int globesize, hit_cdent = 0, c = 0; + + if (otherrc) + wm_strmcpy(&globes, otherrc); + + if (cddb.protocol) + { + sprintf(temp, "cddbprotocol "); + switch(cddb.protocol) + { + case 1: /* cddbp */ + sprintf(temp + strlen(temp), "cddbp\n"); + break; + case 2: /* http */ + sprintf(temp + strlen(temp), "http\n"); + break; + case 3: /* proxy */ + sprintf(temp + strlen(temp), "proxy\n"); + break; + default: + break; + } + wm_strmcat(&globes, temp); + + if(cddb.mail_adress[0]) + { + sprintf(temp,"cddbmailadress %s\n", + cddb.mail_adress); + wm_strmcat(&globes, temp); + } + + if(cddb.cddb_server[0]) + { + sprintf(temp,"cddbserver %s\n", + cddb.cddb_server); + wm_strmcat(&globes, temp); + } + + if(cddb.path_to_cgi[0]) + { + sprintf(temp,"cddbpathtocgi %s\n", + cddb.mail_adress); + wm_strmcat(&globes, temp); + } + + if(cddb.proxy_server[0]) + { + sprintf(temp,"cddbproxy %s\n", + cddb.mail_adress); + wm_strmcat(&globes, temp); + } + } + + if (cur_stopmode == 1 || cur_stopmode == 2) + { + sprintf(temp, "whendone %s\n", cur_stopmode == 1 ? "repeat" : + "eject"); + wm_strmcat(&globes, temp); + } + + if (cur_playnew == 1) + wm_strmcat(&globes, "playnew\n"); + + curpos = firstpos; + if (curpos < 0) + curpos = 0; + + fseek(fp, curpos, SEEK_SET); + + if (firstpos < (globesize = globes != NULL ? strlen(globes) : 0)) + { + while (1) + { + temp[sizeof(temp)-1] = 'x'; + + if (fgets(temp, sizeof(temp), fp) == NULL) + { + fseek(fp, 0, SEEK_SET); + if (globes != NULL) + { + fwrite(globes, globesize, 1, fp); + free(globes); + } + if (cdentry != NULL) + { + fwrite(cdentry, strlen(cdentry), 1, fp); + free(cdentry); + } + return; + } + + if (! strncmp(temp, "tracks ", 7)) + { + hit_cdent = 1; + if (curpos >= globesize) + break; + } + + if (! hit_cdent) + { + curpos += strlen(temp); + if (temp[sizeof(temp)-1] == '\0') + while ((c = getc(fp)) != '\n' && + c != EOF) + curpos++; + if (c == '\n') + curpos++; + + continue; + } + + wm_strmcat(&cdentry, temp); + curpos += strlen(temp); + while (temp[sizeof(temp)-1] == '\0') + { + temp[sizeof(temp)-1] = 'x'; + if (fgets(temp, sizeof(temp), fp) == NULL) + break; + wm_strmcat(&cdentry, temp); + curpos += strlen(temp); + } + } + + if (cdentry != NULL) + { + fseek(fp, 0, SEEK_END); + fwrite(cdentry, strlen(cdentry), 1, fp); + free(cdentry); + } + } + + if (globes != NULL) + { + fseek(fp, 0, SEEK_SET); + fwrite(globes, globesize, 1, fp); + free(globes); + } + + while (globesize++ < curpos) + putc('\n', fp); +} /* save_globals() */ + +/* + * save_entry() + * + * Save the CD information to one database. + * + * filename Database to save to. + * pref 0 for hard data, 1 for preferences. + * + * If an entry for this CD exists already, overwrite it with the new entry + * if the new entry is the same size or smaller, or with newlines if the new + * entry is larger (in which case the new entry is appended to the file.) + * + * Also, if the preference information is being updated, save it to the + * file while we've got it locked. Scoot stuff from the beginning of + * the file to the end as needed to facilitate this. + * + * XXX Preference-saving should probably be done elsewhere, like in an + * Apply button on the Goodies popup, and in any case needs to go to a + * different file (.Xdefaults?) + * + * Returns 0 on success. + */ +int +save_entry(char *filename, int pref) +{ + FILE *fp; + char *buf; + int len, i, locked = 0; + + + if( filename == NULL ) + return (-1); + + fp = open_rcfile(filename, "r+"); + if (fp == NULL) + { + if (errno == ENOENT) /* doesn't exist already */ + fp = open_rcfile(filename, "w"); + if (fp == NULL) + return (-1); + } + + if (lockit(fileno(fp), F_WRLCK)) + perror("Warning: Couldn't get write lock"); + else + locked = 1; + + buf = print_cdinfo(cd, pref); + len = strlen(buf); /* doesn't return if there's an error */ + + rcpos = -1; + search_db(fp, pref, 1, len); + if (rcpos != -1) /* XXX */ + { + /* + * Jump to the entry's position in the database file, if + * it was found. + */ + fseek(fp, rcpos, SEEK_SET); + + if (rclen >= len && holepos == -1) + { + /* + * If the new entry will fit in the space occupied by + * the old one, overwrite the old one and make a hole + * of the appropriate size at its end. + * + * No need to update the index file in this case, as + * the entry's position hasn't changed. + */ + fputs(buf, fp); + for (i = len; i < rclen; i++) + fputc('\n', fp); + } + else + { + /* + * Overwrite the old entry with a hole and delete + * its pointer in the index file. + */ + for (i = 0; i < rclen; i++) + fputc('\n', fp); + idx_delete_entry(filename, cd->trk[cd->ntracks-1].start, + 0, rcpos); + + rcpos = -1; + } + } + + /* + * Old entry wasn't found, or its new version wouldn't fit where + * the old one was. + */ + if (rcpos == -1) + { + /* + * Write the new entry in a hole, if there is one, + * or at the end of the file. + */ + if (holepos >= 0) + { + fseek(fp, holepos, SEEK_SET); + if (holepos < firstpos) + firstpos = holepos; + } + else + { + fseek(fp, 0, SEEK_END); + holepos = ftell(fp); + } + fputs(buf, fp); + + /* + * Write a new index entry for this CD. + */ + idx_write_entry(filename, cd->trk[cd->ntracks - 1].start, + holepos); + } + + if (pref) + save_globals(fp); + + fflush(fp); + + if (locked && lockit(fileno(fp), F_UNLCK)) + perror("Warning: Couldn't relinquish write lock"); + + fclose(fp); + + return (0); +} /* save_entry() */ + +/* + * save() + * + * Save CD information to the appropriate datafile (the first file in the + * list, unless the entry came from another database file) and to the + * personal prefs file. + */ +int +save( void ) +{ + + if( wm_db_save_disabled == FALSE ) + { + if (save_entry(rcfile, 1)) + return (0); + + if (cd->whichdb == NULL || access(cd->whichdb, W_OK)) + cd->whichdb = databases[0]; + + if (save_entry(cd->whichdb, 0)) + return (0); + + return( WM_DB_SAVE_ERROR ); + } else { + return( WM_DB_SAVE_DISABLED ); + } +} /* save() */ diff --git a/ascd/libworkman/drv_sony.c b/ascd/libworkman/drv_sony.c new file mode 100644 index 0000000..338ecc5 --- /dev/null +++ b/ascd/libworkman/drv_sony.c @@ -0,0 +1,173 @@ +/* + * $Id: drv_sony.c,v 1.3 1999/02/14 09:50:42 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Vendor-specific drive control routines for Sony CDU-8012 series. + */ + +static char drv_sony_id[] = "$Id: drv_sony.c,v 1.3 1999/02/14 09:50:42 dirk Exp $"; + +#include <stdio.h> +#include <errno.h> +#include "include/wm_config.h" +#include "include/wm_struct.h" +#include "include/wm_scsi.h" + +#define PAGE_AUDIO 0x0e + +/* local prototypes */ +static int sony_init( struct wm_drive *d ); +static int sony_set_volume(struct wm_drive *d, int left, int right ); +static int sony_get_volume( struct wm_drive *d, int *left, int *right ); + + +extern int min_volume, max_volume; + +struct wm_drive sony_proto = { + -1, /* fd */ + "Sony", /* vendor */ + "CDU-8012", /* model */ + "", /* revision */ + NULL, /* aux */ + NULL, /* daux */ + + sony_init, /* functions... */ + gen_get_trackcount, + gen_get_cdlen, + gen_get_trackinfo, + gen_get_drive_status, + sony_get_volume, + sony_set_volume, + gen_pause, + gen_resume, + gen_stop, + gen_play, + gen_eject, + gen_closetray +}; + +/* + * Initialize the driver. + */ +static int sony_init( struct wm_drive *d ) { + +/* Sun use Sony drives as well */ +#if defined(SYSV) && defined(CODEC) + codec_init(); +#endif + min_volume = 128; + max_volume = 255; + return( 0 ); +} /* sony_init() */ + +/* + * On the Sony CDU-8012 drive, the amount of sound coming out the jack + * increases much faster toward the top end of the volume scale than it + * does at the bottom. To make up for this, we make the volume scale look + * sort of logarithmic (actually an upside-down inverse square curve) so + * that the volume value passed to the drive changes less and less as you + * approach the maximum slider setting. Additionally, only the top half + * of the volume scale is valid; the bottom half is all silent. The actual + * formula looks like + * + * max^2 - (max - vol)^2 max + * v = --------------------- + --- + * max * 2 2 + * + * Where "max" is the maximum value of the volume scale, usually 100. + */ +static int +scale_volume(vol, max) + int vol, max; +{ + vol = (max*max - (max - vol) * (max - vol)) / max; + return ((vol + max) / 2); +} + +/* + * Given a value between min_volume and max_volume, return the standard-scale + * volume value needed to achieve that hardware value. + * + * Rather than perform floating-point calculations to reverse the above + * formula, we simply do a binary search of scale_volume()'s return values. + */ +static int +unscale_volume(cd_vol, max) + int cd_vol, max; +{ + int vol = 0, top = max, bot = 0, scaled = 0; + + cd_vol = (cd_vol * 100 + (max_volume - 1)) / max_volume; + + while (bot <= top) + { + vol = (top + bot) / 2; + scaled = scale_volume(vol, max); + if (cd_vol <= scaled) + top = vol - 1; + else + bot = vol + 1; + } + + /* Might have looked down too far for repeated scaled values */ + if (cd_vol < scaled) + vol++; + + if (vol < 0) + vol = 0; + else if (vol > max) + vol = max; + + return (vol); +} + +/* + * Get the volume. Sun's CD-ROM driver doesn't support this operation, even + * though their drive does. Dumb. + */ +static int +sony_get_volume( struct wm_drive *d, int *left, int *right ) +{ + unsigned char mode[16]; + + /* Get the current audio parameters first. */ + if (wm_scsi_mode_sense(d, PAGE_AUDIO, mode)) + return (-1); + + *left = unscale_volume(mode[9], 100); + *right = unscale_volume(mode[11], 100); + + return (0); +} + +/* + * Set the volume using the wacky scale outlined above. The Sony drive + * responds to the standard set-volume command. + */ +static int +sony_set_volume(struct wm_drive *d, int left, int right ) +{ + left = scale_volume(left, 100); + right = scale_volume(right, 100); + return (gen_set_volume(d, left, right)); +} diff --git a/ascd/libworkman/drv_toshiba.c b/ascd/libworkman/drv_toshiba.c new file mode 100644 index 0000000..3412182 --- /dev/null +++ b/ascd/libworkman/drv_toshiba.c @@ -0,0 +1,154 @@ +/* + * $Id: drv_toshiba.c,v 1.3 1999/02/14 09:50:42 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Vendor-specific drive control routines for Toshiba XM-3401 series. + */ + +static char drv_toshiba_id[] = "$Id: drv_toshiba.c,v 1.3 1999/02/14 09:50:42 dirk Exp $"; + +#include <stdio.h> +#include <errno.h> +#include "include/wm_config.h" +#include "include/wm_struct.h" +#include "include/wm_scsi.h" + +#define SCMD_TOSH_EJECT 0xc4 + +/* local prototypes */ +static int tosh_init( struct wm_drive *d ); +static int tosh_eject( struct wm_drive *d ); +static int tosh_set_volume( struct wm_drive *d, int left, int right ); +static int tosh_get_volume( struct wm_drive *d, int *left, int *right ); + +struct wm_drive toshiba_proto = { + -1, /* fd */ + "Toshiba", /* vendor */ + "", /* model */ + "", /* revision */ + NULL, /* aux */ + NULL, /* daux */ + + tosh_init, /* functions... */ + gen_get_trackcount, + gen_get_cdlen, + gen_get_trackinfo, + gen_get_drive_status, + tosh_get_volume, + tosh_set_volume, + gen_pause, + gen_resume, + gen_stop, + gen_play, + tosh_eject, + gen_closetray +}; + +/* + * Initialize the driver. + */ +static int +tosh_init( struct wm_drive *d ) +{ + extern int min_volume; + +/* Sun use Toshiba drives as well */ +#if defined(SYSV) && defined(CODEC) + codec_init(); +#endif + min_volume = 0; + return ( 0 ); +} + +/* + * Send the Toshiba code to eject the CD. + */ +static int +tosh_eject( struct wm_drive *d ) +{ + return (sendscsi(d, NULL, 0, 0, SCMD_TOSH_EJECT, 1, 0,0,0,0,0,0,0,0,0,0)); +} + +/* + * Set the volume. The low end of the scale is more sensitive than the high + * end, so make up for that by transforming the volume parameters to a square + * curve. + */ +static int +tosh_set_volume( struct wm_drive *d, int left, int right ) +{ + left = (left * left * left) / 10000; + right = (right * right * right) / 10000; + return (gen_set_volume(d, left, right)); +} + +/* + * Undo the transformation above using a binary search (so no floating-point + * math is required.) + */ +static int +unscale_volume(cd_vol, max) + int cd_vol, max; +{ + int vol = 0, top = max, bot = 0, scaled = 0; + + /*cd_vol = (cd_vol * 100 + (max_volume - 1)) / max_volume;*/ + + while (bot <= top) + { + vol = (top + bot) / 2; + scaled = (vol * vol) / max; + if (cd_vol <= scaled) + top = vol - 1; + else + bot = vol + 1; + } + + /* Might have looked down too far for repeated scaled values */ + if (cd_vol < scaled) + vol++; + + if (vol < 0) + vol = 0; + else if (vol > max) + vol = max; + + return (vol); +} + +/* + * Get the volume. + */ +static int +tosh_get_volume( struct wm_drive *d, int *left, int *right ) +{ + int status; + + status = gen_get_volume(d, left, right); + if (status < 0) + return (status); + *left = unscale_volume(*left, 100); + *right = unscale_volume(*right, 100); + + return (0); +} diff --git a/ascd/libworkman/include/wm_cdda.h b/ascd/libworkman/include/wm_cdda.h new file mode 100644 index 0000000..831b19e --- /dev/null +++ b/ascd/libworkman/include/wm_cdda.h @@ -0,0 +1,117 @@ +#ifndef WM_CDDA_H +#define WM_CDDA_H +/* + * $Id: wm_cdda.h,v 1.2 1999/02/14 09:50:46 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * Information about a particular block of CDDA data. + */ +struct cdda_block { + unsigned char status; /* see below */ + unsigned char track; + unsigned char index; + unsigned char minute; + unsigned char second; + unsigned char frame; + + /* Average volume levels, for level meters */ + unsigned char lev_chan0; + unsigned char lev_chan1; + + /* Current volume setting (0-255) */ + unsigned char volume; + + /* Current balance setting (0-255, 128 = balanced) */ + unsigned char balance; +}; + +/* + * cdda_block status codes. + */ +#define WMCDDA_ERROR 0 /* Couldn't read CDDA from disc */ +#define WMCDDA_OK 1 /* Read this block successfully (raw data) */ +#define WMCDDA_PLAYED 2 /* Just played the block in question */ +#define WMCDDA_STOPPED 3 /* Block data invalid; we've just stopped */ +#define WMCDDA_ACK 4 /* Acknowledgement of command from parent */ +#define WMCDDA_DONE 5 /* Chunk of data is done playing */ +#define WMCDDA_EJECTED 6 /* Disc ejected or offline */ + +/* + * Enable or disable CDDA building depending on platform capabilities, and + * determine endianness based on architecture. (Gross!) + * + * For header-comfort, the macros LITTLE_ENDIAN and BIG_ENDIAN had to be + * renamed. At least Linux does have bytesex.h and endian.h for easy + * byte-order examination. + */ + +#ifdef sun +# ifdef SYSV +# include <sys/types.h> +# include <sys/cdio.h> +# ifndef CDROMCDDA +# undef BUILD_CDDA +# endif +# ifdef i386 +# define WM_LITTLE_ENDIAN 1 +# define WM_BIG_ENDIAN 0 +# else +# define WM_BIG_ENDIAN 1 +# define WM_LITTLE_ENDIAN 0 +# endif +# else +# undef BUILD_CDDA +# endif +#endif + +/* Linux only allows definition of endianness, because there's no + * standard interface for CDROM CDDA functions that aren't available + * if there is no support. + */ +#ifdef linux +# include <bytesex.h> +# include <endian.h> +/* + * XXX could this be a problem? The results are only 0 and 1 because + * of the ! operator. How about other linux compilers than gcc ? + */ +# define WM_LITTLE_ENDIAN !(__BYTE_ORDER - __LITTLE_ENDIAN) +# define WM_BIG_ENDIAN !(__BYTE_ORDER - __BIG_ENDIAN) +#endif + +/* + * The following code shouldn't take effect now. + * In 1998, the WorkMan platforms don't support __PDP_ENDIAN + * architectures. + * + */ + +#if !WM_LITTLE_ENDIAN +# if !WM_BIG_ENDIAN +# error yet unsupported architecture + foo bar this is to stop the compiler. +# endif +#endif +#endif /* WM_CDDA_H */ diff --git a/ascd/libworkman/include/wm_cddb.h b/ascd/libworkman/include/wm_cddb.h new file mode 100644 index 0000000..bd5b4f9 --- /dev/null +++ b/ascd/libworkman/include/wm_cddb.h @@ -0,0 +1,42 @@ +#ifndef WM_CDDB_H +#define WM_CDDB_H +/* + * $Id: wm_cddb.h,v 1.2 1999/02/14 09:50:46 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +extern struct wm_cddb cddb; +extern int cur_cddb_protocol; +extern char *cur_cddb_server, *cur_cddb_mail_adress, + *cur_cddb_path_to_cgi, *cur_cddb_proxy_server; + +unsigned int cddb_discid(); +void cddb_struct2cur(); +void cddb_cur2struct(); +void cddb_select(); +void connect_cddb(); +void update_cddbserver(); +void cddb_request(); + +#endif /* WM_CDDB_H */ diff --git a/ascd/libworkman/include/wm_cdinfo.h b/ascd/libworkman/include/wm_cdinfo.h new file mode 100644 index 0000000..f783465 --- /dev/null +++ b/ascd/libworkman/include/wm_cdinfo.h @@ -0,0 +1,78 @@ +#ifndef WM_CDINFO_H +#define WM_CDINFO_H +/* + * $Id: wm_cdinfo.h,v 1.5 1999/05/28 03:35:58 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Prototypes from cdinfo.c + * + * This is just one more step to a more modular and understandable code. + */ + +extern char *cur_trackname; /* Take a guess */ +extern int cur_index; /* Current index mark */ +extern int cur_frame; /* Current frame number */ +extern struct wm_play *playlist; /* = NULL */ +extern struct wm_cdinfo thiscd; +extern struct wm_cdinfo *cd; +extern int cur_track; /* Current track number, starting at 1 */ +extern char *cur_artist; /* Name of current CD's artist */ +extern char cur_avoid; /* Avoid flag */ +extern char cur_contd; /* Continued flag */ +extern char *cur_cdname; /* Album name */ +extern int cur_nsections; /* Number of sections currently defined */ +extern int exit_on_eject; +extern int cur_track; +extern int cur_pos_abs; +extern int cur_pos_rel; +extern int cur_tracklen; +extern int cur_cdlen; +extern enum wm_cd_modes cur_cdmode; +extern int cur_ntracks; +extern int cur_lasttrack; +extern int cur_firsttrack; +extern int cur_listno; +extern int cur_stopmode; + +void wipe_cdinfo( void ); +void play_next_entry( int forward ); +void make_playlist( int playmode, int starttrack ); +int get_autoplay( void ); +int get_playmode( void ); +void pl_find_track( int track ); +void play_prev_track( int forward ); +void play_next_track( int forward ); +int tracklen( int num ); +int get_default_volume( int track ); +int split_trackinfo( int pos ); +int remove_trackinfo( int num ); +void freeup( char **x ); +int get_runtime( void ); +char *trackname( int num ); +void stash_cdinfo( char *artist, char *cdname, int autoplay, int playmode ); +void stash_trkinfo( int track, char *songname, int contd, int avoid ); +int get_avoid( int num ); +int get_contd( int num ); +void default_volume( int track, int vol ); +char *listentry( int num ); + +#endif /* WM_CDINFO_H */ diff --git a/ascd/libworkman/include/wm_cdrom.h b/ascd/libworkman/include/wm_cdrom.h new file mode 100644 index 0000000..7d0dca4 --- /dev/null +++ b/ascd/libworkman/include/wm_cdrom.h @@ -0,0 +1,64 @@ +#ifndef WM_CDROM_H +#define WM_CDROM_H +/* + * $Id: wm_cdrom.h,v 1.6 1999/05/05 16:09:55 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Prototypes from cdrom.c + * + * This is just one more step to a more modular and understandable code. + */ + +#define WM_CDS_NO_DISC 0 +#define WM_CDS_DISC_READY 1 +#define WM_CDS_JUST_INSERTED 2 + +#define WM_STR_GENVENDOR "Generic" +#define WM_STR_GENMODEL "drive" +#define WM_STR_GENREV "type" + +extern int wm_cd_cur_balance; + + +char * wm_drive_vendor( void ); +char * wm_drive_model( void ); +char * wm_drive_revision( void ); +void wm_drive_settype( char *vendor, char *model, char *revision ); + +int wm_cd_status( void ); + +void wm_cd_play( int start, int pos, int end ); +void wm_cd_play_chunk( int start, int end, int realstart ); +void wm_cd_play_from_pos( int pos ); +void wm_cd_pause( void ); +void wm_cd_stop( void ); +int wm_cd_eject( void ); +int wm_cd_closetray( void ); +int wm_cd_read_initial_volume( int max ); + +/* + * Following are the missing to rename. + */ +int find_trkind( int track, int index, int start ); +void cd_volume( int vol, int bal, int max ); + +#endif /* WM_CDROM_H */ diff --git a/ascd/libworkman/include/wm_config.h b/ascd/libworkman/include/wm_config.h new file mode 100644 index 0000000..c04a0e5 --- /dev/null +++ b/ascd/libworkman/include/wm_config.h @@ -0,0 +1,357 @@ +#ifndef WM_CONFIG_H +#define WM_CONFIG_H +/* + * $Id: wm_config.h,v 1.5 1999/05/05 16:34:22 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + ********************************************************************** + * + * This file consists of several parts. First, there's a generic, + * platform independent part. Set needed options there. + * The following parts are platform dependent. You may search for the + * names listed below and then set your OS specific options there. + * Don't be surprised, if there are no options for your OS. They aren't + * needed in any case. + * + * The default values should produce a functional WorkMan on every + * platform. + * + ********************* + * Current platforms: + ********************* + * BSD386 + * FreeBSD + * HP-UX + * Irix (SGI) + * Linux + * News (Sony NewsOS) + * OpenBSD + * OSF1 + * Sun (SunOS/Solaris, Sparc or x86) + * SVR4 + * Ultrix + * AIX + * + * The above order corresponds with the order of the platform specific + * options below. + */ + +/****************************************************************** + * generic options + ****************************************************************** + ** ** *** **** ** **** ** * ** ** ** * ** + * * * *** **** * * *** * ** ** *** * * * * * * ** + * * *** **** ** *** ** ** ** * * ** * * ** + * * * *** **** **** *** * ** ** *** * * * * *** ** + * * * * ** **** * * ** ** **** ** * * *** ** + ******************************************************************/ + +/* + * This option is obvious. But please do not forget the original + * WorkMan version string if you need support. + */ + +#define WORKMAN_NAME "LibWorkMan" +#define WORKMAN_VERSION "1.4.0" + +/* + * If your CD-ROM drive closes its tray if the device is opened, then + * the next define can make WorkMans "Eject" button an "open/close" + * button. If it disturbs you, just comment it out. + * + * ### this is preliminary. It may have no effect for you ### + */ +#define CAN_CLOSE 1 + +/* + * Define the following if you want the balance slider to + * decrease one channel's volume while increasing the other's + */ +/* #define SYMETRIC_BALANCE 1 */ + + +/* + * Define this if you want CDDA support. Supported systems are: + * + * - Solaris (2.4 or newer) + * --> Linux is on the way. Don't define it now. It would lead to errors only. + */ +/*#define BUILD_CDDA 1*/ + +/* + * + * This is for experimental cddb support. + * This activates the UI component. + */ + +#define CDDB_IN_UI 1 + + +/****************************************************************** + * BSD386 + ****************************************************************** + *** **** *** ******* ** **** **** ************ + *** ** ** ****** * ***** ****** ** ** ** *************** + *** ****** **** ** *** ***** ***** *** ************ + *** ** ****** ** * *** ******** ** ** ** ** *********** + *** **** *** *** ****** **** **** ************ + ******************************************************************/ +#ifdef __bsdi__ + +/* + * This lets you use the SoundBlaster Mixer on BSD/386 + */ +#define SOUNDBLASTER 1 + +#endif /* __bsdi__ (BSD/386) */ + +/****************************************************************** + * FreeBSD + ****************************************************************** + *** ** *** ** ** **** *** *********** + *** ****** ** ** ****** ****** ** ** ****** * ********** + *** **** *** **** **** ****** **** ** ********* + *** ****** ** ** ****** ****** ** ****** ** * ********** + *** ****** ** ** ** ** **** *** *********** + ******************************************************************/ +#if defined(__FreeBSD__) || defined(__NetBSD__) + +#define DEFAULT_CD_DEVICE "/dev/rmatcd0c" + +#endif /* freebsd */ + +/****************************************************************** + * HP-UX + ****************************************************************** + *** ** ** ********* ** ** ** *************************** + *** ** ** ** ******** ** *** **************************** + *** ** *** ** ** **** ***************************** + *** ** ** ************ ** *** **************************** + *** ** ** ************* *** ** *************************** + ******************************************************************/ +#if defined(hpux) || defined (__hpux) + +#define DEFAULT_CD_DEVICE "/dev/rscsi" + +#endif /* hpux */ + +/****************************************************************** + * Irix + ****************************************************************** + *** ** *** ** ** ********************************* + ***** **** ** **** ***** ********************************** + ***** **** ***** ****** *********************************** + ***** **** ** **** ***** ********************************** + *** ** ** ** ** ** ********************************* + ******************************************************************/ +#if defined(sgi) + +#define DEFAULT_CD_DEVICE "/dev/scsi/sc0d6l0" + +#endif /* sgi IRIX */ + +/****************************************************************** + * Linux + ****************************************************************** + *** ****** ** *** ** ** ** ** *********************** + *** ******** **** ** ** ** *** ************************ + *** ******** **** * * ** ** **** ************************* + *** ******** **** ** ** ** *** ************************ + *** ** ** *** *** *** ** *********************** + ******************************************************************/ +#ifdef linux + +/* + * Uncomment the following line to have WorkMan send SCSI commands + * directly to the CD-ROM drive. If you have a SCSI drive you + * probably want this, but it will cause WorkMan to not work on IDE + * drives. + */ +#define LINUX_SCSI_PASSTHROUGH 1 + +/* + * Which device should be opened by WorkMan at default? + */ +#define DEFAULT_CD_DEVICE "/dev/sbpcd" + +/* + * Uncomment the following if you use the sbpcd or mcdx device driver. + * It shouldn't hurt if you use it on other devices. It'll be nice to + * hear from non-sbpcd (or mcdx) users if this is right. + */ +/*#define SBPCD_HACK 1*/ + +/* + * Linux Soundcard support + */ +#define OSS_SUPPORT 1 + +/* + * This has nothing to do with the above. + */ + +/* #define CURVED_VOLUME */ + +/* + * Uncomment the following if you want to try out a better responding + * WorkMan, especially with IDE drives. This may work with non-IDE + * drives as well. But it may be possible, that it doesn't work at all. + * If your drive/driver combination cannot handle the faster access, + * the driver will usually hang and you have to reboot your machine. + */ +/* #define FAST_IDE 1 */ + +/* + * There are two alternative ways of checking a device containing a + * mounted filesystem. Define BSD_MOUNTTEST for the test using + * getmntent(). Undefine it for using the SVR4 ustat(). + * I built in the choice, because it's not clear which method should + * be used in Linux. The ustat manpage tells us since 1995, that + * fstat() should be used, but I'm too dumb to do so. + */ + +#define BSD_MOUNTTEST + +#endif /* linux */ + +/****************************************************************** + * Sony NewsOS + ****************************************************************** + *** *** ** ** ***** *** ***************************** + *** ** ** ****** ***** ** ******************************** + *** * * ** **** ** ** **** ****************************** + *** ** ** ****** * * ****** **************************** + *** *** ** ** * *** ***************************** + ******************************************************************/ +#if defined(__sony_news) || defined(sony_news) + +#define DEFAULT_CD_DEVICE "/dev/rsd/b0i6u0p2\0" + +#endif + +/****************************************************************** + * OpenBSD + ****************************************************************** + **** *** *** ** *** ** **** *** ********* + *** ** ** ** ** ****** ** ** ** ** ****** * ******** + *** ** ** *** **** * * ** ****** **** ** ******* + *** ** ** ****** ****** ** ** ** ****** ** * ******** + **** *** ****** ** *** ** **** *** ********* + ******************************************************************/ +#ifdef __OpenBSD__ + +#define DEFAULT_CD_DEVICE "/dev/rcdrom" + +#endif + +/****************************************************************** + * OSF1 + ****************************************************************** + **** **** *** *** *** ******************************* + *** ** ** ****** ****** ** ******************************* + *** ** **** **** *** ***** ******************************* + *** ** ****** ** **** ****** ******************************* + **** **** *** *** ***** ***************************** + ******************************************************************/ +#if defined(__osf__) + +#define DEFAULT_CD_DEVICE "/dev/rcdrom/cd0" + +#endif + +/****************************************************************** + * SunOS/Solaris + ****************************************************************** + **** *** ** ** *** *************************************** + *** ****** ** ** ** *************************************** + ***** **** ** ** * * *************************************** + ******* ** ** ** ** *************************************** + **** **** *** *** *************************************** + ******************************************************************/ +#if defined(sun) || defined(__sun__) + +/* + * Define the following for Solaris 2.x + * If you don't want WorkMan to try to activate the SPARCstation 5 + * internal audio input so you get sound from the workstation, comment + * out the CODEC define. + */ + +#define SYSV 1 +#define CODEC 1 + +/* + * set the following to "SUNW,CS4231" for Sun and to "SUNW,sb16" + * for PC (with SoundBlaster 16) running Solaris x86 + * (only important if you define CODEC above) + */ +/*#define SUN_AUD_DEV "SUNW,CS4231"*/ +#define SUN_AUD_DEV "SUNW,sbpro" + + +#endif + +/****************************************************************** + * SVR4 + ****************************************************************** + **** *** **** ** *** * ******************************** + *** ****** **** ** ** ** * ******************************** + ***** ***** ** *** *** ******************************* + ******* *** ** *** ** ***** ******************************** + **** ***** **** ** ***** ******************************** + ******************************************************************/ +#if defined(SVR4) && !defined(sun) && !defined(__sun__) + +#define DEFAULT_CD_DEVICE "/dev/rcdrom/cd0" + +#endif + +/****************************************************************** + * Ultrix + ****************************************************************** + *** ** ** ***** ** *** ** ** ****************** + *** ** ** ******* **** ** **** ***** ******************* + *** ** ** ******* **** ***** ****** ******************** + *** ** ** ******* **** ** **** ***** ******************* + **** *** *** **** ** ** ** ** ****************** + ******************************************************************/ +#if defined(ultrix) || defined(__ultrix) + +#endif + +/****************************************************************** + * IBM AIX + ****************************************************************** + **** *** ** ** ***************************************** + *** ** **** ***** ****************************************** + *** **** ****** ******************************************* + *** ** **** ***** ****************************************** + *** ** ** ** ** ***************************************** + ******************************************************************/ +#if defined(AIXV3) + +#define DEFAULT_CD_DEVICE "/dev/cd0" + +#endif /* IBM AIX */ + +/******************************************************************/ +#endif /* WM_CONFIG_H */ diff --git a/ascd/libworkman/include/wm_database.h b/ascd/libworkman/include/wm_database.h new file mode 100644 index 0000000..aaea086 --- /dev/null +++ b/ascd/libworkman/include/wm_database.h @@ -0,0 +1,44 @@ +#ifndef WM_DATABASE_H +#define WM_DATABASE_H +/* + * $Id: wm_database.h,v 1.3 1999/02/14 09:50:47 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Prototypes for WorkMan database + * + * This is just one more step to a more modular and understandable code. + */ + + +#define WM_DB_SAVE_ERROR 1 +#define WM_DB_SAVE_DISABLED 2 + +int wm_db_get_playnew( void ); +void split_workmandb( void ); +int save( void ); +void load( void ); +void load_settings( void ); + +extern int wm_db_save_disabled; +extern int cur_playnew; + +#endif /* WM_DATABASE_H */ diff --git a/ascd/libworkman/include/wm_helpers.h b/ascd/libworkman/include/wm_helpers.h new file mode 100644 index 0000000..0a3763b --- /dev/null +++ b/ascd/libworkman/include/wm_helpers.h @@ -0,0 +1,105 @@ +#ifndef WM_HELPERS_H +#define WM_HELPERS_H +/* + * $Id: wm_helpers.h,v 1.6 1999/03/07 08:36:44 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Here to be found: Prototypes. Including variable names to be easier + * to read. + * This is just one more step to a more modular and understandable code. + * + */ + +/* + * LibWorkMan message levels. I'm not sure how to call them all and which + * use they should fulfill. This is not very urgent now, because there + * aren't many messages in LibWorkMan now. + */ +#define WM_MSG_LEVEL_NONE 0 +#define WM_MSG_LEVEL_ERROR 1 +#define WM_MSG_LEVEL_TWO 2 +#define WM_MSG_LEVEL_THREE 3 +#define WM_MSG_LEVEL_FOUR 4 +#define WM_MSG_LEVEL_INFO 5 +#define WM_MSG_LEVEL_SIX 6 +#define WM_MSG_LEVEL_SEVEN 7 +#define WM_MSG_LEVEL_EIGHT 8 +#define WM_MSG_LEVEL_DEBUG 9 + +/* + * Message classes. This is somehow a definition of + * the message's source. + */ + +#define WM_MSG_CLASS_PLATFORM 0x010 +#define WM_MSG_CLASS_SCSI 0x020 +#define WM_MSG_CLASS_CDROM 0x040 +#define WM_MSG_CLASS_DB 0x080 +#define WM_MSG_CLASS_MISC 0x100 + +#define WM_MSG_CLASS_ALL 0xff0 + +extern int wm_lib_verbosity; + +/* + * I did not know any better place... + */ +#ifdef DEBUG +#define CHECKPOINT(t) fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, t ); +#else +#define CHECKPOINT(t) +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifdef linux +#include <signal.h> +/* Linux doesn't have a SIGEMT */ +#if !defined( SIGEMT ) +# define SIGEMT SIGUNUSED +#endif +#endif /* linux */ + +void freeup( char **x ); +void wm_strmcat( char **t, char *s); +void wm_strmcpy( char **t, char *s ); +char * wm_strdup( char *s ); +/* Somebody's version query unsatisfied? */ +int wm_libver_major( void ); /* return major internal version number */ +int wm_libver_minor( void ); /* return minor internal version number */ +int wm_libver_pl( void ); /* return internal patchlevel number */ +char * wm_libver_name( void ); /* return internal name (LibWorkMan) */ +char * wm_libver_number( void ); /* returns string: "<major>.<minor>.<pl>" */ +char * wm_libver_string( void ); /* returns string: "<name> <number>" */ +char * wm_libver_date( void ); /* returns string: date of compilation */ +void wm_lib_set_verbosity( int level ); /* set verbosity level */ +int wm_lib_get_verbosity( void ); /* get verbosity level */ +void wm_lib_message( unsigned int level, char *format, ... ); /* put out a message on stderr */ +int wm_susleep( int usec ); + +#endif /* WM_HELPERS_H */ diff --git a/ascd/libworkman/include/wm_index.h b/ascd/libworkman/include/wm_index.h new file mode 100644 index 0000000..ce14ac3 --- /dev/null +++ b/ascd/libworkman/include/wm_index.h @@ -0,0 +1,36 @@ +#ifndef WM_INDEX_H +#define WM_INDEX_H +/* + * $Id: wm_index.h,v 1.2 1999/02/14 09:50:47 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Prototypes for wm_index.c + * + * This is just one more step to a more modular and understandable code. + */ + +int idx_find_entry( char *file, int ntracks, int *tracks, + int len, int fuzz, unsigned long *pos ); +int idx_delete_entry(char *file, int track, int fuzz, unsigned long pos ); +int idx_write_entry( char *file, int track, unsigned long pos ); + +#endif /* WM_INDEX_H */ diff --git a/ascd/libworkman/include/wm_platform.h b/ascd/libworkman/include/wm_platform.h new file mode 100644 index 0000000..03f957d --- /dev/null +++ b/ascd/libworkman/include/wm_platform.h @@ -0,0 +1,51 @@ +#ifndef WM_PLATFORM_H +#define WM_PLATFORM_H +/* + * $Id: wm_platform.h,v 1.3 1999/02/14 09:50:47 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * The platform interface + * + * This is just one more step to a more modular and understandable code. + */ + +#include "wm_struct.h" + +int wmcd_open( struct wm_drive *d ); +int wmcd_reopen( struct wm_drive *d ); + +/* + * void keep_cd_open( void ); + */ +int wm_scsi( struct wm_drive *d, unsigned char *cdb, int cdblen, + void *retbuf, int retbuflen, int getreply ); + +/**************************************** + * + * The drive prototypes. + * + */ +extern struct wm_drive generic_proto; +extern struct wm_drive sony_proto; +extern struct wm_drive toshiba_proto; + +#endif /* WM_PLATFORM_H */ diff --git a/ascd/libworkman/include/wm_scsi.h b/ascd/libworkman/include/wm_scsi.h new file mode 100644 index 0000000..8763a62 --- /dev/null +++ b/ascd/libworkman/include/wm_scsi.h @@ -0,0 +1,48 @@ +#ifndef WM_SCSI_H +#define WM_SCSI_H +/* + * $Id: wm_scsi.h,v 1.3 1999/02/14 09:50:47 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * SCSI prototypes (scsi.c) + * + * This is just one more step to a more modular and understandable code. + */ + +#include "wm_struct.h" + +#define WM_ERR_SCSI_INQUIRY_FAILED -1 + +int wm_scsi_mode_sense( struct wm_drive *d, unsigned char page, + unsigned char *buf ); +int sendscsi( struct wm_drive *d, void *buf, + unsigned int len, int dir, + unsigned char a0, unsigned char a1, + unsigned char a2, unsigned char a3, + unsigned char a4, unsigned char a5, + unsigned char a6, unsigned char a7, + unsigned char a8, unsigned char a9, + unsigned char a10, unsigned char a11 ); + int wm_scsi_get_drive_type( struct wm_drive *d, char *vendor, + char *model, char *rev ); + +#endif /* WM_SCSI_H */ diff --git a/ascd/libworkman/include/wm_struct.h b/ascd/libworkman/include/wm_struct.h new file mode 100644 index 0000000..ae1b72a --- /dev/null +++ b/ascd/libworkman/include/wm_struct.h @@ -0,0 +1,181 @@ +#ifndef WM_STRUCT_H +#define WM_STRUCT_H +/* + * $Id: wm_struct.h,v 1.7 1999/05/28 03:35:58 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + + +/* + * Structure for a single track. This is pretty much self-explanatory -- + * one of these exists for each track on the current CD. + */ +struct wm_trackinfo +{ + char *songname; /* Name of song, dynamically allocated */ + char *otherdb; /* Unrecognized info for this track */ + char *otherrc; + int length; /* Length of track in seconds or Kbytes */ + int start; /* Starting position (f+s*75+m*60*75) */ + int volume; /* Per-track volume (1-32, 0 to disable) */ + int track; /* Physical track number */ + int section; /* Section number (0 if track not split) */ + int contd; /* Flag: continuation of previous track */ + int avoid; /* Flag: don't play this track. */ + int data; /* Flag: data track */ +}; + +/* + * Structure for internal playlist management. The internal playlist is + * simply the list of track ranges that are being played currently. This + * is built whenever the CD starts playing; it's used in normal and shuffle + * modes as well as playlist mode. + * + * The "starttime" element represents how much time has elapsed by the time + * we get to this entry. For instance, if the list begins with a 5-minute + * track and a 3-minute track, the third entry would have a starttime of 8 + * minutes. This is used so that the elapsed play time can be displayed + * even in shuffle or playlist modes. + * + * The last member of the list has a start track of 0, and its starttime is + * the total playing time of the playlist (which will usually be overestimated, + * since we don't play leadouts in some cases.) + */ +struct wm_play +{ + int start; /* Start track, or 0 if end of list */ + int end; /* last track plus 1 */ + int starttime; /* Number of seconds elapsed previously */ +}; + +/* + * Structure for playlists (as seen by the user.) This is simply a name + * followed by a zero-terminated list of track numbers to play. The list + * is terminated by a NULL name. + */ +struct wm_playlist +{ + char *name; /* Name of this playlist */ + int *list; /* List of tracks */ +}; + +struct wm_cdinfo +{ + char artist[84]; /* Artist's name */ + char cdname[84]; /* Disc's name */ + int ntracks; /* Number of tracks on the disc */ + int length; /* Total running time in seconds */ + int autoplay; /* Start playing CD immediately */ + int playmode; /* How to play the CD */ + int volume; /* Default volume (1-32, 0 for none) */ + struct wm_trackinfo *trk; /* struct wm_trackinfo[ntracks] */ + struct wm_playlist *lists; /* User-specified playlists */ + char *whichdb; /* Which database is this entry from? */ + char *otherdb; /* Unrecognized lines from this entry */ + char *otherrc; + char *user; /* Name of originating user */ + unsigned int cddbid; /* CDDB-ID of the current disc */ + struct cdinfo *next; /* For browsers, etc. */ +}; + +/* The global variable "cd" points to the struct for the CD that's playing. */ +extern struct wm_cdinfo *cd; + +struct wm_playlist *new_list(); + +enum wm_cd_modes +{ + WM_CDM_UNKNOWN = -1, + WM_CDM_BACK = 0, WM_CDM_TRACK_DONE = 0, + WM_CDM_PLAYING = 1, + WM_CDM_FORWARD = 2, + WM_CDM_PAUSED = 3, + WM_CDM_STOPPED = 4, + WM_CDM_EJECTED = 5 +}; + +/* + * Drive descriptor structure. Used for access to low-level routines. + */ +struct wm_drive +{ + int fd; /* File descriptor, if used by platform */ + char vendor[32]; /* Vendor name */ + char model[32]; /* Drive model */ + char revision[32]; /* Revision of the drive */ + void *aux; /* Pointer to optional platform-specific info */ + void *daux; /* Pointer to optional drive-specific info */ + + int (*init)(); + int (*get_trackcount)(); + int (*get_cdlen)(); + int (*get_trackinfo)(); + int (*get_drive_status)(); + int (*get_volume)(); + int (*set_volume)(); + int (*pause)(); + int (*resume)(); + int (*stop)(); + int (*play)(); + int (*eject)(); + int (*closetray)(); +}; + +/* + * Structure for information of the usage of cddb. + */ +struct wm_cddb { + int protocol; /* 0-off, 1-cddbp, 2-http, 3-htproxy */ + char cddb_server[84]; /* host.domain.name:port */ + char mail_adress[84]; /* [email protected] */ + char path_to_cgi[84]; /* (/)path/to/cddb.cgi */ + char proxy_server[84]; /* host.domain.name:port */ +}; +extern struct wm_cddb cddb; + + +/* + * Each platform has to define generic functions, so may as well declare + * them all here to save space. + * These functions should never be seen outside libworkman. So I don't care + * about the wm_ naming convention here. + */ +int gen_init(), + gen_get_trackcount(), + gen_get_cdlen(), + gen_get_trackinfo(), + gen_get_drive_status(), + gen_get_volume(), + gen_set_volume(), + gen_pause(), + gen_resume(), + gen_stop(), + gen_play(), + gen_eject(), + gen_closetray(); + +struct wm_drive *find_drive_struct(); + + +#endif /* WM_STRUCT_H */ diff --git a/ascd/libworkman/include/wm_version.h b/ascd/libworkman/include/wm_version.h new file mode 100644 index 0000000..b18a2e2 --- /dev/null +++ b/ascd/libworkman/include/wm_version.h @@ -0,0 +1,35 @@ +#ifndef WM_VERSION_H +#define WM_VERSION_H +/* + * $Id: wm_version.h,v 1.2 1999/05/28 03:35:58 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Version information + * + */ + +#define WM_LIBVER_MAJOR 1 +#define WM_LIBVER_MINOR 4 +#define WM_LIBVER_PL 2 +#define WM_LIBVER_NAME "LibWorkMan" + +#endif /* WM_VERSION_H */ diff --git a/ascd/libworkman/include/workman.h b/ascd/libworkman/include/workman.h new file mode 100644 index 0000000..2172b9f --- /dev/null +++ b/ascd/libworkman/include/workman.h @@ -0,0 +1,51 @@ +#ifndef WORKMAN_H +#define WORKMAN_H +/* + * $Id: workman.h,v 1.5 1999/05/05 16:34:22 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * all-in-one libworkman include file. + * + */ + + +/* + * wm_config should always be included first + */ +#include "wm_config.h" + +#include "workman_defs.h" +#ifdef BUILD_CDDA +#include "wm_cdda.h" +#endif +#include "wm_cddb.h" +#include "wm_cdinfo.h" +#include "wm_cdrom.h" +#include "wm_database.h" +#include "wm_helpers.h" +#include "wm_index.h" +#include "wm_platform.h" +#include "wm_scsi.h" +#include "wm_struct.h" + +#endif /* WORKMAN_H */ + diff --git a/ascd/libworkman/include/workman_defs.h b/ascd/libworkman/include/workman_defs.h new file mode 100644 index 0000000..712a414 --- /dev/null +++ b/ascd/libworkman/include/workman_defs.h @@ -0,0 +1,30 @@ +#ifndef WORKMAN_DEFS_H +#define WORKMAN_DEFS_H +/* + * $Id: workman_defs.h,v 1.4 1999/02/14 09:50:47 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player program + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * 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. + * + * #defined CONSTANTS + * + * Too bad this file seems to be so empty... + * + */ + +#include "wm_version.h" + +#endif /* WORKMAN_DEFS_H */ diff --git a/ascd/libworkman/index.c b/ascd/libworkman/index.c new file mode 100644 index 0000000..e27cc0f --- /dev/null +++ b/ascd/libworkman/index.c @@ -0,0 +1,385 @@ +/* + * $Id: index.c,v 1.2 1999/02/14 09:50:42 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Maintain an external index file for a WorkMan CD database. + * Uses the Berkeley libdb library, available from ftp.cs.berkeley.edu. + */ + +static char index_id[] = "$Id: index.c,v 1.2 1999/02/14 09:50:42 dirk Exp $"; + +#ifdef LIBDB + +#include <stdio.h> +#include <stdlib.h> +#include <db.h> +#include <fcntl.h> +#include <string.h> +#include <netinet/in.h> /* for htonl() */ +#include "include/wm_config.h" +#include "include/wm_index.h" + +extern int suppress_locking; + +/* + * idx_find_entry() + * + * Find an entry in the index file. + * + * Input: + * file Name of database file (text version). + * ntracks Number of tracks on the CD we're looking for. + * tracks Array of track start times. + * len CD length in frames. + * fuzz Fuzz factor (tolerance value). + * pos Pointer to return value. + * + * Output: + * 1 No matching record found. + * 0 Record found; *pos contains offset into text file. + * -1 Index file out of date or inaccessible, or another error. + */ +int +idx_find_entry( char *file, int ntracks, int *tracks, + int len, int fuzz, unsigned long *pos ) +{ + unsigned long dbpos; + char *indexname = NULL, keyval[8]; + int c; + FILE *text; + DB *index; + DBT key, data; + BTREEINFO bti; + + /* + * First, see if the text file is accessible. Lock it if so. + */ + text = fopen(file, "r"); + if (text == NULL) + return (-1); + if ((c = getc(text)) == EOF) + { + fclose(text); + return (-1); + } + if (! suppress_locking) + if (lockit(fileno(text), F_RDLCK)) + { + fclose(text); + return (-1); + } + + /* + * Open the index file. + */ + indexname = malloc(strlen(file) + sizeof(".ind")); + if (indexname == NULL) + { + fclose(text); + return (-1); + } + strcpy(indexname, file); + strcat(indexname, ".ind"); + bti.flags = 0; + bti.cachesize = 0; + bti.minkeypage = bti.maxkeypage = 0; + bti.psize = bti.lorder = 0; + bti.compare = NULL; + bti.prefix = NULL; + index = dbopen(indexname, O_RDONLY, 0666, DB_BTREE, &bti); + free(indexname); + if (index == NULL) + { + fclose(text); + return (-1); + } + + /* + * Search for the first matching entry. + */ + sprintf(keyval, "%07d", tracks[ntracks - 1] - fuzz); + key.data = keyval; + key.size = 7; + if (c = (index->seq)(index, &key, &data, R_CURSOR)) + { + (index->close)(index); + fclose(text); + return (c); + } + + /* + * Now loop through all the possible matches, collecting them into + * memory. + */ + do { + char tracksline[750], *s; + int i, val; + + /* Hit the end of the valid entries? */ + sscanf(key.data, "%d", &val); + if (val > tracks[ntracks - 1] + fuzz) + break; + + dbpos = ntohl(*((unsigned long *) data.data)); + if (fseek(text, dbpos, 0)) + break; + + fgets(tracksline, sizeof(tracksline), text); + if (strncmp(tracksline, "tracks ", 7)) + break; + (void) strtok(tracksline, " \t"); + + /* Got a valid tracks line. See if it matches the CD. */ + s = strtok(NULL, " \t"); + if (s == NULL) + break; + if (atoi(s) != ntracks) + continue; + + for (i = 0; i < ntracks; i++) + { + s = strtok(NULL, " \t"); + if (s == NULL) + break; + val = atoi(s); + if (val + fuzz < tracks[i] || val - fuzz > tracks[i]) + break; + } + if (i != ntracks) + continue; + + s = strtok(NULL, " \t"); + if (s == NULL) + continue; + val = atoi(s); + if (val + fuzz / 75 < len / 75 || val + fuzz / 75 > len / 75) + continue; + + /* XXX - add to sorted list! */ + *pos = dbpos; + (index->close)(index); + fclose(text); + return (0); + } while ((c = (index->seq)(index, &key, &data, R_NEXT)) == 0); + + if (c == 0) + { + /* An error. */ + (index->close)(index); + fclose(text); + return (-1); + } + + (index->close)(index); + fclose(text); + return (1); +} + +/* + * idx_delete_entry() + * + * Delete an entry from the index file. + * + * Input: + * file Name of database file (text version). + * track Last track's start time (database key). + * fuzz Fuzz factor (tolerance value). + * pos Position of CD in database file. + * + * Output: + * 1 No matching record found. + * 0 Record deleted. + * -1 Index file out of date or inaccessible, or another error. + * + * Note: it is the caller's responsibility to do locking, as it's assumed + * that this operation will accompany a modification of the main database + * file and will need to be atomic with that modification. + */ +int +idx_delete_entry(char *file, int track, int fuzz, unsigned long pos ) +{ + unsigned long dbpos; + char *indexname = NULL, keyval[8]; + int c, status; + DB *index; + DBT key, data; + BTREEINFO bti; + + /* + * Open the index file. + */ + indexname = malloc(strlen(file) + sizeof(".ind")); + if (indexname == NULL) + return (-1); + + strcpy(indexname, file); + strcat(indexname, ".ind"); + + bti.flags = 0; + bti.cachesize = 0; + bti.minkeypage = bti.maxkeypage = 0; + bti.psize = bti.lorder = 0; + bti.compare = NULL; + bti.prefix = NULL; + index = dbopen(indexname, O_RDWR, 0666, DB_BTREE, &bti); + free(indexname); + if (index == NULL) + return (-1); + + /* + * Search for the first matching entry. + */ + sprintf(keyval, "%07d", track - fuzz); + key.data = keyval; + key.size = 7; + if (c = (index->seq)(index, &key, &data, R_CURSOR)) + { + /* + * Nothing matched! + */ + (index->close)(index); + return (c); + } + + /* + * Look for the entry the user wants to delete. + */ + do { + int val; + + /* Hit the end of the valid entries? */ + sscanf(key.data, "%d", &val); + if (val > track + fuzz) + break; + + /* Is this the entry we want? */ + if (pos == ntohl(*((unsigned long *) data.data))) + { + /* + * Yep! Delete it. + */ + status = (index->del)(index, &key, R_CURSOR); + (index->close)(index); + return (status); + } + } while ((c = (index->seq)(index, &key, &data, R_NEXT)) == 0); + + if (c == 0) + { + /* An error. */ + (index->close)(index); + return (-1); + } + + (index->close)(index); + return (1); +} + +/* + * idx_write_entry() + * + * Write out an index file entry. + * + * Input: + * file Name of database file (text version). + * track Start time of last track (database key). + * pos Position of entry in text file. + * + * Output: + * 0 Record written. + * -1 Index file inaccessible, or another error. + * + * Note: it is the caller's responsibility to do locking, as it's assumed + * that this operation will accompany a modification of the main database + * file and will need to be atomic with that modification. + */ +int +idx_write_entry( char *file, int track, unsigned long pos ) +{ + char *indexname, keyval[8]; + int status; + DB *index; + DBT key, data; + BTREEINFO bti; + + /* + * Open the index file. + */ + indexname = malloc(strlen(file) + sizeof(".ind")); + if (indexname == NULL) + return (-1); + + strcpy(indexname, file); + strcat(indexname, ".ind"); + + bti.flags = R_DUP; + bti.cachesize = 0; + bti.minkeypage = 0; + bti.maxkeypage = 0; + bti.psize = 0; + bti.lorder = 4321; /* network byte order */ + bti.compare = NULL; + bti.prefix = NULL; + index = dbopen(indexname, O_RDWR, 0666, DB_BTREE, &bti); + free(indexname); + if (index == NULL) + return (-1); + + /* + * Create a new key and value. + */ + pos = htonl(pos); + data.data = &pos; + data.size = sizeof(pos); + key.data = keyval; + key.size = 7; + + sprintf(keyval, "%07d", track); + + status = (index->put)(index, &key, &data, 0); + + (index->close)(index); + return (status); +} + +#else /* LIBDB */ + +int +idx_find_entry() +{ + return (1); /* no record found; text file will be searched. */ +} + +int +idx_delete_entry() +{ + return (0); +} + +int +idx_write_entry() +{ + return (0); +} + +#endif /* LIBDB */ diff --git a/ascd/libworkman/plat_aix.c b/ascd/libworkman/plat_aix.c new file mode 100644 index 0000000..980bb50 --- /dev/null +++ b/ascd/libworkman/plat_aix.c @@ -0,0 +1,443 @@ +/* + * $Id: plat_aix.c,v 1.7 1999/03/07 08:36:40 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * plat_aix - AIX 4.x IDE and SCSI support 16 Dec 1998 + * + * AIX 4.x Port: Erik O'Shaughnessy + * Original AIX IDE Code: Cloyce Spradling (xmcd libdi_d/aixioc.c ) + * + * Taken from the ascd distribution. + * + */ + + +#ifdef AIXV3 + +static char plat_aix_id[] = "$Id: plat_aix.c,v 1.7 1999/03/07 08:36:40 dirk Exp $"; + +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/cdrom.h> +#include <sys/devinfo.h> +#include <sys/scsi.h> +#include <sys/scdisk.h> + +#include "include/wm_config.h" +#include "include/wm_struct.h" + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +#define LEADOUT 0xaa + +extern char *cd_device; + +int min_volume = 128; +int max_volume = 255; + + + +/* NAME: gen_init + * + * FUNCTION: + * + * RETURNS: + */ + +int gen_init(struct wm_drive *d){ + + return 0; +} + +/* NAME: gen_get_trackcount + * + * FUNCTION: + * + * RETURNS: + */ + +int gen_get_trackcount(struct wm_drive *d,int *tracks){ + struct cd_audio_cmd cmd; + + cmd.audio_cmds = CD_TRK_INFO_AUDIO; + cmd.msf_flag = 0; + + if( ioctl(d->fd,DKAUDIO,&cmd) < 0){ + perror("DKAUDIO"); + return -1; + } + + *tracks = cmd.indexing.track_index.last_track; + + return 0; +} + + +/* NAME: gen_get_cdlen + * + * FUNCTION: + * + * RETURNS: + */ + +int gen_get_cdlen(struct wm_drive *d,int *frames){ + int tmp; + + return gen_get_trackinfo(d,LEADOUT,&tmp,frames); +} + +/* NAME: gen_get_trackinfo + * + * FUNCTION: + * + * RETURNS: + */ + +int gen_get_trackinfo(struct wm_drive *d,int track,int *data,int *startframe){ + struct cd_audio_cmd cmd; + + cmd.audio_cmds = CD_GET_TRK_MSF; + cmd.msf_flag = 1; + + cmd.indexing.track_msf.track = track; + + if( ioctl(d->fd,DKAUDIO,&cmd) < 0) + return -1; + + *startframe = cmd.indexing.track_msf.mins * 60 * 75 + + cmd.indexing.track_msf.secs * 75 + + cmd.indexing.track_msf.frames; + + *data = 0; + + return 0; +} + +/* NAME: gen_get_drive_status + * + * FUNCTION: + * + * RETURNS: + */ + +int gen_get_drive_status(struct wm_drive *d, + enum wm_cd_modes oldmode, + enum wm_cd_modes *mode, + int *pos, + int *track, + int *index){ + struct cd_audio_cmd cmd; + + *mode = WM_CDM_EJECTED; + + if(d->fd < 0) + switch( wmcd_open(d) ){ + case -1: + return -1; + case 1: + return 0; + } + + cmd.audio_cmds = CD_INFO_AUDIO; + if( ioctl(d->fd,DKAUDIO,&cmd) < 0) + return -1; + + switch(cmd.status){ + case CD_PLAY_AUDIO: + *mode = WM_CDM_PLAYING; + *track = cmd.indexing.info_audio.current_track; + *index = cmd.indexing.info_audio.current_index; + *pos = cmd.indexing.info_audio.current_mins * 60 * 75 + + cmd.indexing.info_audio.current_secs * 75 + + cmd.indexing.info_audio.current_frames; + break; + + case CD_PAUSE_AUDIO: + *mode = WM_CDM_PAUSED; + *track = cmd.indexing.info_audio.current_track; + *index = cmd.indexing.info_audio.current_index; + *pos = cmd.indexing.info_audio.current_mins * 60 * 75 + + cmd.indexing.info_audio.current_secs * 75 + + cmd.indexing.info_audio.current_frames; + + break; + case CD_NO_AUDIO: /* no play audio in progress */ + case CD_COMPLETED: /* play operation completed successfully */ + case CD_STATUS_ERROR: /* invalid status or play stopped due to err */ + case CD_NOT_VALID: /* audio status is invalid or not supported */ + *mode = WM_CDM_STOPPED; + break; + default: + *mode = WM_CDM_UNKNOWN; + break; + } + + return 0; +} + + +int scale_volume(int vol,int max){ + return ((vol * (max_volume - min_volume)) / max + min_volume); +} + +int unscale_volume(int vol,int max){ + int n; + n = ( vol - min_volume ) * max_volume / (max - min_volume); + return (n <0)?0:n; +} + +/* NAME: gen_get_volume + * + * FUNCTION: + * + * RETURNS: + */ + +int gen_get_volume(struct wm_drive *d,int *left,int *right){ + struct cd_audio_cmd cmd; + int l,r; + + fprintf(stderr,"gen_get_volume\n"); + + cmd.audio_cmds = CD_INFO_AUDIO; + if( ioctl(d->fd,DKAUDIO,&cmd) < 0) + return -1; + + *left = unscale_volume(cmd.out_port_0_vol,100); + *right = unscale_volume(cmd.out_port_1_vol,100); + + return 0; +} + +/* NAME: gen_set_volume + * + * FUNCTION: + * + * RETURNS: + */ + +int gen_set_volume(struct wm_drive *d,int left,int right){ + struct cd_audio_cmd cmd; + + cmd.audio_cmds = CD_SET_VOLUME; + cmd.volume_type = CD_VOLUME_CHNLS; + + cmd.out_port_0_vol = scale_volume(left,100); + cmd.out_port_1_vol = scale_volume(right,100); + + if( ioctl(d->fd,DKAUDIO,&cmd) < 0){ + perror("CD_SET_VOLUME"); + return -1; + } + + return 0; +} + +/* NAME: gen_pause + * + * FUNCTION: + * + * RETURNS: + */ + +int gen_pause(struct wm_drive *d){ + struct cd_audio_cmd cmd; + + cmd.audio_cmds = CD_PAUSE_AUDIO; + + return ioctl(d->fd,DKAUDIO,&cmd); +} + +/* NAME: gen_resume + * + * FUNCTION: + * + * RETURNS: + */ + +int gen_resume(struct wm_drive *d){ + struct cd_audio_cmd cmd; + + cmd.audio_cmds = CD_RESUME_AUDIO; + return ioctl(d->fd,DKAUDIO,&cmd); +} + +/* NAME: gen_stop + * + * FUNCTION: + * + * RETURNS: + */ + +int gen_stop(struct wm_drive *d){ + struct cd_audio_cmd cmd; + + cmd.audio_cmds = CD_STOP_AUDIO; + return ioctl(d->fd,DKAUDIO,&cmd); +} + +/* NAME: gen_play + * + * FUNCTION: + * + * RETURNS: + */ + +int gen_play(struct wm_drive *d,int start,int end){ + struct cd_audio_cmd cmd; + + cmd.audio_cmds = CD_PLAY_AUDIO; + cmd.msf_flag = 1; + + cmd.indexing.msf.first_mins = start / (60*75); + cmd.indexing.msf.first_secs = (start % (60*75)) / 75; + cmd.indexing.msf.first_frames = start % 75; + + cmd.indexing.msf.last_mins = end / (60*75); + cmd.indexing.msf.last_secs = (end % (60*75)) / 75; + cmd.indexing.msf.last_frames = end % 75; + + if( ioctl(d->fd,DKAUDIO,&cmd) < 0){ + perror("DKAUDIO:CD_PLAY_AUDIO"); + return -1; + } + + return 0; +} + +/* NAME: gen_eject + * + * FUNCTION: + * + * RETURNS: + */ + +int gen_eject(struct wm_drive *d){ + + return ioctl(d->fd,DKEJECT,NULL); +} + + +/*----------------------------------------* + * Close the CD tray + *----------------------------------------*/ +int gen_closetray(struct wm_drive *d) +{ +#ifdef CAN_CLOSE + if(!close(d->fd)) + { + d->fd=-1; + return(wmcd_reopen(d)); + } else { + return(-1); + } +#else + /* Always succeed if the drive can't close */ + return(0); +#endif /* CAN_CLOSE */ +} /* gen_closetray() */ + + +/* NAME: wm_scsi + * + * FUNCTION: + * + * RETURNS: + */ + + + +int wm_scsi(struct wm_drive *d, + uchar_t *cdb, int cdblen,void *retbuf,int retbuflen,int getreply){ + return 0; +} + + +/* NAME: wmcd_open + * + * FUNCTION: + * + * RETURNS: + */ + +int wmcd_open(struct wm_drive *d){ + char vendor[32] = WM_STR_GENVENDOR; + char model[32] = WM_STR_GENMODEL; + char rev[32] = WM_STR_GENREV; + + int fd; + + if( ! d ){ + errno = EFAULT; + return -1; + } + + if(d->fd > -1) /* device already open? */ + return 0; + + if( cd_device == (char *)NULL ) + cd_device = DEFAULT_CD_DEVICE; + + if( (fd=openx(cd_device,O_RDONLY,NULL,SC_SINGLE)) < 0 ){ + perror("openx"); + return 1; + } + + *d = *(find_drive_struct(vendor, model, rev)); + wm_drive_settype(vendor, model, rev); + + d->fd = fd; + d->init(d); + + return 0; +} /* wmcd_open() */ + +/* + * Re-Open the device if it is open. + */ +int +wmcd_reopen( struct wm_drive *d ) +{ + int status; + + do { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "wmcd_reopen "); + if (d->fd >= 0) /* Device really open? */ + { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "closes the device and "); + status = close( d->fd ); /* close it! */ + /* we know, that the file is closed, do we? */ + d->fd = -1; + } + wm_susleep( 1000 ); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "calls wmcd_open()\n"); + status = wmcd_open( d ); /* open it as usual */ + wm_susleep( 1000 ); + } while ( status != 0 ); + return status; +} /* wmcd_reopen() */ + +#endif /* _AIX */ diff --git a/ascd/libworkman/plat_bsd386.c b/ascd/libworkman/plat_bsd386.c new file mode 100644 index 0000000..9a58e84 --- /dev/null +++ b/ascd/libworkman/plat_bsd386.c @@ -0,0 +1,500 @@ +/* + * $Id: plat_bsd386.c,v 1.5 1999/03/07 08:36:40 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * BSD/386-specific drive control routines. + */ + +#ifdef __bsdi__ + +static char plat_bsd386_id[] = "$Id: plat_bsd386.c,v 1.5 1999/03/07 08:36:40 dirk Exp $"; + +#include <errno.h> +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/param.h> +#include <sys/stat.h> + +#include "include/wm_config.h" +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +/* + * The following is included from the Linux module. However, I didn't + * see a check here if the CD to be ejected is mounted... + */ +#if defined(BSD_MOUNTTEST) + #include <mntent.h> +#else + /* + * this is for glibc 2.x which defines ust structure in + * ustat.h not stat.h. + */ + #ifdef __GLIBC__ + #include <sys/ustat.h> + #endif +#endif + + +#include <sys/time.h> +#include <string.h> +#include <sys/cdrom.h> +#ifdef SOUNDBLASTER +# include <i386/isa/sblast.h> +#endif + +#include "include/wm_struct.h" + +/* + * Since we can't sense the drive type with libcdrom anyway, and since the + * library doesn't provide "pause" or "resume" functions, use the daux field + * to point to the frame number at which we paused. + */ +struct pause_info { + int frame; + int endframe; +}; +#define PAUSE_FRAME (((struct pause_info *) d->daux)->frame) +#define END_FRAME (((struct pause_info *) d->daux)->endframe) +#define CUR_CD ((struct cdinfo *) d->aux) + +void *malloc(); + +extern char *cd_device; + +#ifdef SOUNDBLASTER + int min_volume = 0; + int max_volume = 15; + int min_volume_drive = 10; /* Toshiba drive does low values. */ + int max_volume_drive = 255; +#else /* not SOUNDBLASTER, libcdrom only */ + int min_volume = 10; + int max_volume = 255; +#endif + +/*--------------------------------------------------------* + * Initialize the drive. A no-op for the generic driver. + *--------------------------------------------------------*/ +int +gen_init(struct wm_drive *d) +{ + return (0); +} + +/*-------------------------------------* + * Get the number of tracks on the CD. + *-------------------------------------*/ +int +gen_get_trackcount(struct wm_drive *d, int *tracks) +{ + *tracks = CUR_CD->ntracks; + + return (0); +} + +/*---------------------------------------------------------* + * Get the start time and mode (data or audio) of a track. + *---------------------------------------------------------*/ +int +gen_get_trackinfo(struct wm_drive *d, int track, int *data, int *startframe) +{ + *data = (CUR_CD->tracks[track - 1].control & 4) ? 1 : 0; + *startframe = CUR_CD->tracks[track - 1].start_frame; + + return (0); +} + +/*-------------------------------------* + * Get the number of frames on the CD. + *-------------------------------------*/ +int +gen_get_cdlen(struct wm_drive *d, int *frames) +{ + *frames = CUR_CD->total_frames; + + return (0); +} + +/*--------------------------------------------------------------------------* + * Get the current status of the drive: the current play mode, the absolute + * position from start of disc (in frames), and the current track and index + * numbers if the CD is playing or paused. + *--------------------------------------------------------------------------*/ +int +gen_get_drive_status(struct wm_drive *d, enum wm_cd_modes oldmode, + enum wm_cd_modes *mode, int *pos, int *track, int *index) +{ + struct cdstatus status; + extern enum wm_cd_modes cur_cdmode; + + /* If we can't get status, the CD is ejected, so default to that. */ + *mode = WM_CDM_EJECTED; + + /* Is the device open? */ + if (d->aux == NULL) + { + switch (wmcd_open(d)) { + case -1: /* error */ + return (-1); + + case 1: /* retry */ + return (0); + } + } + + if (cdstatus (CUR_CD, &status) < 0) + { + *mode = WM_CDM_TRACK_DONE; /* waiting for next track. */ + return (0); + } + +#define DOPOS \ + *pos = status.abs_frame; \ + *track = status.track_num; \ + *index = status.index_num + + switch (status.state) { + case cdstate_playing: + *mode = WM_CDM_PLAYING; + DOPOS; + break; + + case cdstate_stopped: + /* the MITSUMI drive doesn't have a "paused" state, + so it always comes here and not to the paused section. + The PAUSE_FRAME stuff below (in gen_pause()) + fakes out the paused state. */ + if (oldmode == WM_CDM_PLAYING) { + *mode = WM_CDM_TRACK_DONE; + break; + } else if (cur_cdmode != WM_CDM_PAUSED) { + *mode = WM_CDM_STOPPED; + DOPOS; + break; + } + /* fall through if paused */ + + case cdstate_paused: + /* the SCSI2 code in the cdrom library only pauses with + cdstop(); it never truly stops a disc (until an in-progress + play reaches the end). So it always comes here. */ + if (cur_cdmode == WM_CDM_STOPPED) { + *mode = WM_CDM_STOPPED; + DOPOS; + break; + } + if (oldmode == WM_CDM_PLAYING || oldmode == WM_CDM_PAUSED) { + *mode = WM_CDM_PAUSED; + DOPOS; + } else { + *mode = WM_CDM_STOPPED; + DOPOS; + } + break; + + default: + *mode = WM_CDM_STOPPED; + } + + return (0); +} + +/*------------------------------------------------------------------------* + * Return a volume value suitable for passing to the CD-ROM drive. "vol" + * is a volume slider setting; "max" is the slider's maximum value. + *------------------------------------------------------------------------*/ +static int +scale_volume(int vol, int max) +{ + /* on Toshiba XM-3401B drive, and on soundblaster, this works fine. */ + return ((vol * (max_volume - min_volume)) / max + min_volume); +} + +/*---------------------------------------------------------------------* + * Set the volume level for the left and right channels. Their values + * range from 0 to 100. + *---------------------------------------------------------------------*/ +int +gen_set_volume(struct wm_drive *d, int left, int right) +{ + left = scale_volume(left, 100); + right = scale_volume(right, 100); + +#ifdef SOUNDBLASTER + /* Send a Mixer IOCTL */ + if (d->fd >= 0) { + struct sb_mixer_levels levels; + if (ioctl(d->fd, MIXER_IOCTL_READ_LEVELS, &levels) == 0) { + levels.cd.l = left < 0 ? 0 : left; + levels.cd.r = right < 0 ? 0 : right; + (void) ioctl(d->fd, MIXER_IOCTL_SET_LEVELS, &levels); + } else + perror("SoundBlaster mixer read failed"); + } else +#endif + /* NOTE: the cdvolume2() call is an addition to the cdrom library. + Pick it up from the archives on bsdi.com */ + cdvolume2 (CUR_CD, left < 0 ? 0 : left > 255 ? 255 : left, + right < 0 ? 0 : right > 255 ? 255 : right); + + return (0); +} + +/*--------------------------------------------------------------------* + * Pause the CD. This is a bit of a trick since there's no cdpause() + * function in the library. We fake it by saving the frame number + * and stopping. + *--------------------------------------------------------------------*/ +int +gen_pause(struct wm_drive *d) +{ + struct cdstatus status; + + if (cdstatus(d->aux, &status) < 0) + return (-1); + if (status.state != cdstate_playing) + PAUSE_FRAME = CUR_CD->tracks[0].start_frame; + else + PAUSE_FRAME = status.abs_frame; + if (cdstop(d->aux) < 0) + return (-1); + + return (0); +} + +/*-------------------------------------------------* + * Resume playing the CD (assuming it was paused.) + *-------------------------------------------------*/ +int +gen_resume(struct wm_drive *d) +{ + int status; + + status = (d->play)(d, PAUSE_FRAME, END_FRAME); + PAUSE_FRAME = 0; + return (status); +} + +/*--------------* + * Stop the CD. + *--------------*/ +int +gen_stop(struct wm_drive *d) +{ + return cdstop(d->aux); +} + +/*------------------------------------------------------------* + * Play the CD from one position to another (both in frames.) + *------------------------------------------------------------*/ +int +gen_play(struct wm_drive *d, int start, int end) +{ + END_FRAME = end; + if (cdplay(d->aux, start, end) < 0) + return (-1); + else + return (0); +} + +/*----------------------------------------* + * Eject the current CD, if there is one. + *----------------------------------------*/ +int +gen_eject(struct wm_drive *d) +{ + cdeject(d->aux); + cdclose(d->aux); + d->aux = NULL; + free(d->daux); + d->daux = NULL; + + return (0); +} + +/*----------------------------------------* + * Close the CD tray + *----------------------------------------*/ +int gen_closetray(struct wm_drive *d) +{ +#ifdef CAN_CLOSE + if(!close(d->fd)) + { + d->fd=-1; + return(wmcd_open(d)); + } else { + return(-1); + } +#else + /* Always succeed if the drive can't close */ + return(0); +#endif /* CAN_CLOSE */ +} /* gen_closetray() */ + +/*---------------------------------------------------------------------------* + * unscale_volume(cd_vol, max) + * + * Given a value between min_volume and max_volume, return the volume slider + * value needed to achieve that value. + * + * Rather than perform floating-point calculations to reverse the above + * formula, we simply do a binary search of scale_volume()'s return values. + *--------------------------------------------------------------------------*/ +static int +unscale_volume(int cd_vol, int max) +{ + int vol = 0, top = max, bot = 0, scaled; + + while (bot <= top) + { + vol = (top + bot) / 2; + scaled = scale_volume(vol, max); + if (cd_vol == scaled) + break; + if (cd_vol < scaled) + top = vol - 1; + else + bot = vol + 1; + } + + if (vol < 0) + vol = 0; + else if (vol > max) + vol = max; + + return (vol); +} + +/*---------------------------------------------------------------------* + * Read the initial volume from the drive, if available. Each channel + * ranges from 0 to 100, with -1 indicating data not available. + *---------------------------------------------------------------------*/ +int +gen_get_volume(struct wm_drive *d, int *left, int *right) +{ + /* Most systems can't seem to do this... */ + *left = *right = -1; + +#ifdef SOUNDBLASTER + /* Send a Mixer IOCTL */ + if (d->fd >= 0) { + struct sb_mixer_levels levels; + if (ioctl(d->fd, MIXER_IOCTL_READ_LEVELS, &levels) == 0) { + *left = unscale_volume(levels.cd.l, 100); + *right = unscale_volume(levels.cd.r, 100); + } else + perror("SoundBlaster mixer read failed"); + } +#endif + + return (0); +} + +/*---------------------------------------------* + * Send an arbitrary SCSI command to a device. + *---------------------------------------------*/ +int +wm_scsi(struct wm_drive *d, unsigned char *cdb, int cdblen, + void *retbuf, int retbuflen, int getreply) +{ + /* Don't know how to do SCSI passthrough... */ + return (-1); +} + +#ifdef SOUNDBLASTER +int sb_fd = -3; /* patchable from external programs */ +#endif + +/*-----------------------------------------------------------------------* + * Open the CD device. We can't determine the drive type under BSD/386. + *-----------------------------------------------------------------------*/ +int +wmcd_open(struct wm_drvie *d) +{ + void *aux = NULL, *daux = NULL; + int fd = -1; + + if (d->aux) /* Device already open? */ + return (0); + + if ((aux = cdopen(cd_device)) == NULL) + { + fprintf(stderr, "No cdrom found by libcdrom\n"); + exit(1); + } + + if ((daux = malloc(sizeof(struct pause_info))) == NULL) + return (-1); + +#ifdef SOUNDBLASTER + if (sb_fd != -3 && sb_fd < 0) + fd = open ("/dev/sb_mixer", O_RDWR, 0); + if (fd < 0) { + if (sb_fd != -3) + fprintf (stderr,"SoundBlaster mixer not found/disabled; will use direct CD volume control\n"); + max_volume = max_volume_drive; + min_volume = min_volume_drive; + } +#endif + + /* Now fill in the relevant parts of the wm_drive structure. */ + *d = *(find_drive_struct("", "", "")); + d->aux = aux; + d->daux = daux; + d->fd = fd; + PAUSE_FRAME = 0; + END_FRAME = 0; + + (d->init)(d); + + return (0); +} /* wmcd_open() */ + +/* + * Re-Open the device if it is open. + */ +int +wmcd_reopen( struct wm_drive *d ) +{ + int status; + + do { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "wmcd_reopen "); + if (d->fd >= 0) /* Device really open? */ + { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "closes the device and "); + status = close( d->fd ); /* close it! */ + /* we know, that the file is closed, do we? */ + d->fd = -1; + } + wm_susleep( 1000 ); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "calls wmcd_open()\n"); + status = wmcd_open( d ); /* open it as usual */ + wm_susleep( 1000 ); + } while ( status != 0 ); + return status; +} /* wmcd_reopen() */ + + +#endif /* __bsdi__ */ diff --git a/ascd/libworkman/plat_freebsd.c b/ascd/libworkman/plat_freebsd.c new file mode 100644 index 0000000..945b316 --- /dev/null +++ b/ascd/libworkman/plat_freebsd.c @@ -0,0 +1,565 @@ +/* + * $Id: plat_freebsd.c,v 1.8 1999/03/07 08:36:40 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * plat_freebsd.c + * + * FreeBSD-specific drive control routines. + * + * Todd Pfaff, 3/20/94 + * + */ + +#if defined(__FreeBSD__) || defined(__NetBSD__) + +static char freebsd_id[] = "$Id: plat_freebsd.c,v 1.8 1999/03/07 08:36:40 dirk Exp $"; + +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/mount.h> +#include <sys/stat.h> + +#include "include/wm_config.h" + +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/cdio.h> +#include <sys/scsiio.h> + +#if defined(__NetBSD__) +# define MSF_MINUTES 1 +# define MSF_SECONDS 2 +# define MSF_FRAMES 3 +# include "/sys/scsi/scsi_all.h" +# include "/sys/scsi/scsi_cd.h" +#else +# define LEFT_PORT 0 +# define RIGHT_PORT 1 +# if __FreeBSD_version < 300000 +# include <scsi.h> +# endif +#endif + +#include "include/wm_struct.h" +#include "include/wm_platform.h" +#include "include/wm_cdrom.h" +#include "include/wm_scsi.h" +#include "include/wm_helpers.h" + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +void *malloc(); + +int min_volume = 10; +int max_volume = 255; + +extern char *cd_device; + + +/*--------------------------------------------------------* + * Initialize the drive. A no-op for the generic driver. + *--------------------------------------------------------*/ +int +gen_init(struct wm_drive *d) +{ + return (0); +} + +/*-------------------------------------* + * Get the number of tracks on the CD. + *-------------------------------------*/ +int +gen_get_trackcount(struct wm_drive *d, int *tracks) +{ + struct ioc_toc_header hdr; + + if (ioctl(d->fd, CDIOREADTOCHEADER, &hdr) == -1) + return (-1); + + *tracks = hdr.ending_track - hdr.starting_track + 1; + + return (0); +} + +/*-----------------------------------------------------------------------* + * Get the start time and mode (data or audio) of a track. + * + * XXX - this should get cached, but that means keeping track of ejects. + *-----------------------------------------------------------------------*/ +int +gen_get_trackinfo(struct wm_drive *d, int track, int *data, int *startframe) +{ + struct ioc_read_toc_entry toc; + struct cd_toc_entry toc_buffer; + + bzero((char *)&toc_buffer, sizeof(toc_buffer)); + toc.address_format = CD_MSF_FORMAT; + toc.starting_track = track; + toc.data_len = sizeof(toc_buffer); + toc.data = &toc_buffer; + + if (ioctl(d->fd, CDIOREADTOCENTRYS, &toc)) + return (-1); + + *data = ((toc_buffer.control & 0x4) != 0); + +#ifdef __NetBSD__ + *startframe = toc_buffer.addr[MSF_MINUTES]*60*75 + + toc_buffer.addr[MSF_SECONDS] * 75 + + toc_buffer.addr[MSF_FRAMES]; +#else + *startframe = toc_buffer.addr.msf.minute*60*75 + + toc_buffer.addr.msf.second * 75 + + toc_buffer.addr.msf.frame; +#endif + + return (0); +} + +/*-------------------------------------* + * Get the number of frames on the CD. + *-------------------------------------*/ +int +gen_get_cdlen(struct wm_drive *d, int *frames) +{ + int tmp; + struct ioc_toc_header hdr; + int status; + +#define LEADOUT 0xaa /* see scsi.c. what a hack! */ + return gen_get_trackinfo(d, LEADOUT, &tmp, frames); +} + +/*--------------------------------------------------------------------------* + * Get the current status of the drive: the current play mode, the absolute + * position from start of disc (in frames), and the current track and index + * numbers if the CD is playing or paused. + *--------------------------------------------------------------------------*/ +int +gen_get_drive_status(struct wm_drive *d, enum wm_cd_modes oldmode, + enum wm_cd_modes *mode, int *pos, int *track, int *index) +{ + struct ioc_read_subchannel sc; + struct cd_sub_channel_info scd; + + /* If we can't get status, the CD is ejected, so default to that. */ + *mode = WM_CDM_EJECTED; + + sc.address_format = CD_MSF_FORMAT; + sc.data_format = CD_CURRENT_POSITION; + sc.track = 0; + sc.data_len = sizeof(scd); + sc.data = (struct cd_sub_channel_info *)&scd; + + /* Is the device open? */ + if (d->fd < 0) + { + switch (wmcd_open(d)) { + case -1: /* error */ + return (-1); + + case 1: /* retry */ + return (0); + } + } + + if (ioctl(d->fd, CDIOCREADSUBCHANNEL, &sc)) { +/* + * #ifdef __NetBSD__ + * + * Denis Bourez <[email protected]> told me, that closing the + * device is mandatory for FreeBSD, too. + */ + /* we need to release the device so the kernel will notice + reloaded media */ + (void) close(d->fd); + d->fd = -1; +/* + * #endif + */ + return (0); /* ejected */ + } + + switch (scd.header.audio_status) { + case CD_AS_PLAY_IN_PROGRESS: + *mode = WM_CDM_PLAYING; +dopos: +#ifdef __NetBSD__ + *pos = scd.what.position.absaddr[MSF_MINUTES] * 60 * 75 + + scd.what.position.absaddr[MSF_SECONDS] * 75 + + scd.what.position.absaddr[MSF_FRAMES]; +#else + *pos = scd.what.position.absaddr.msf.minute * 60 * 75 + + scd.what.position.absaddr.msf.second * 75 + + scd.what.position.absaddr.msf.frame; +#endif + *track = scd.what.position.track_number; + *index = scd.what.position.index_number; + break; + + case CD_AS_PLAY_PAUSED: + if (oldmode == WM_CDM_PLAYING || oldmode == WM_CDM_PAUSED) + { + *mode = WM_CDM_PAUSED; + goto dopos; + } + else + *mode = WM_CDM_STOPPED; + break; + + case CD_AS_PLAY_COMPLETED: + *mode = WM_CDM_TRACK_DONE; /* waiting for next track. */ + break; + + case CD_AS_NO_STATUS: + case 0: + *mode = WM_CDM_STOPPED; + break; + } + + return (0); +} + +/*---------------------------------------------------------------------------* + * scale_volume(vol, max) + * + * Return a volume value suitable for passing to the CD-ROM drive. "vol" + * is a volume slider setting; "max" is the slider's maximum value. + * + * On Sun and DEC CD-ROM drives, the amount of sound coming out the jack + * increases much faster toward the top end of the volume scale than it + * does at the bottom. To make up for this, we make the volume scale look + * sort of logarithmic (actually an upside-down inverse square curve) so + * that the volume value passed to the drive changes less and less as you + * approach the maximum slider setting. The actual formula looks like + * + * (max^2 - (max - vol)^2) * (max_volume - min_volume) + * v = --------------------------------------------------- + min_volume + * max^2 + * + * If your system's volume settings aren't broken in this way, something + * like the following should work: + * + * return ((vol * (max_volume - min_volume)) / max + min_volume); + *---------------------------------------------------------------------------*/ +static int +scale_volume(int vol, int max) +{ + return ((vol * (max_volume - min_volume)) / max + min_volume); +} + +/*---------------------------------------------------------------------* + * Set the volume level for the left and right channels. Their values + * range from 0 to 100. + *---------------------------------------------------------------------*/ +int +gen_set_volume(struct wm_drive *d, int left, int right) +{ + struct ioc_vol vol; + + if (left < 0) /* don't laugh, I saw this happen once! */ + left = 0; + if (right < 0) + right = 0; + left = scale_volume(left, 100); + right = scale_volume(right, 100); + + bzero((char *)&vol, sizeof(vol)); + + vol.vol[LEFT_PORT] = left; + vol.vol[RIGHT_PORT] = right; + + if (ioctl(d->fd, CDIOCSETVOL, &vol)) + return (-1); + + return (0); +} + +/*---------------* + * Pause the CD. + *---------------*/ +int +gen_pause( struct wm_drive *d ) +{ + return (ioctl(d->fd, CDIOCPAUSE)); +} + +/*-------------------------------------------------* + * Resume playing the CD (assuming it was paused.) + *-------------------------------------------------*/ +int +gen_resume( struct wm_drive *d ) +{ + return (ioctl(d->fd, CDIOCRESUME)); +} + +/*--------------* + * Stop the CD. + *--------------*/ +int +gen_stop( struct wm_drive *d) +{ + return (ioctl(d->fd, CDIOCSTOP)); +} + +/*------------------------------------------------------------* + * Play the CD from one position to another (both in frames.) + *------------------------------------------------------------*/ +int +gen_play(struct wm_drive *d, int start, int end ) +{ + struct ioc_play_msf msf; + + msf.start_m = start / (60*75); + msf.start_s = (start % (60*75)) / 75; + msf.start_f = start % 75; + msf.end_m = end / (60*75); + msf.end_s = (end % (60*75)) / 75; + msf.end_f = end % 75; + + if (ioctl(d->fd, CDIOCSTART)) + return (-1); + + if (ioctl(d->fd, CDIOCPLAYMSF, &msf)) + return (-2); + + return (0); +} + +/*----------------------------------------* + * Eject the current CD, if there is one. + *----------------------------------------*/ +int +gen_eject( struct wm_drive *d ) +{ + /* On some systems, we can check to see if the CD is mounted. */ + struct stat stbuf; + struct statfs buf; + int rval; + + if (fstat(d->fd, &stbuf) != 0) + return (-2); + + /* Is this a mounted filesystem? */ + if (fstatfs(stbuf.st_rdev, &buf) == 0) + return (-3); + + rval = ioctl(d->fd, CDIOCALLOW); + + if (rval == 0) + rval = ioctl(d->fd, CDIOCEJECT); + + if (rval == 0) + rval = ioctl(d->fd, CDIOCPREVENT); + + (void) close(d->fd); + + return rval; +} + +/*----------------------------------------* + * Close the CD tray + *----------------------------------------*/ + +int gen_closetray(struct wm_drive *d) +{ +#ifdef CAN_CLOSE + if(!close(d->fd)) + { + d->fd=-1; + return(wmcd_reopen(d)); + } else { + return(-1); + } +#else + /* Always succeed if the drive can't close */ + return(0); +#endif /* CAN_CLOSE */ +} /* gen_closetray() */ + + +/*---------------------------------------------------------------------------* + * unscale_volume(cd_vol, max) + * + * Given a value between min_volume and max_volume, return the volume slider + * value needed to achieve that value. + * + * Rather than perform floating-point calculations to reverse the above + * formula, we simply do a binary search of scale_volume()'s return values. + *--------------------------------------------------------------------------*/ +static int +unscale_volume( int cd_vol, int max ) +{ + int vol = 0, top = max, bot = 0, scaled; + + while (bot <= top) + { + vol = (top + bot) / 2; + scaled = scale_volume(vol, max); + if (cd_vol == scaled) + break; + if (cd_vol < scaled) + top = vol - 1; + else + bot = vol + 1; + } + + if (vol < 0) + vol = 0; + else if (vol > max) + vol = max; + + return (vol); +} + +/*---------------------------------------------------------------------* + * Read the initial volume from the drive, if available. Each channel + * ranges from 0 to 100, with -1 indicating data not available. + *---------------------------------------------------------------------*/ +int +gen_get_volume( struct wm_drive *d, int *left, int *right ) +{ + struct ioc_vol vol; + + if (d->fd >= 0) + { + bzero((char *)&vol, sizeof(vol)); + + if (ioctl(d->fd, CDIOCGETVOL, &vol)) + *left = *right = -1; + else + { + *left = unscale_volume(vol.vol[LEFT_PORT], 100); + *right = unscale_volume(vol.vol[RIGHT_PORT], 100); + } + } + else + *left = *right = -1; + + return (0); +} + + +/*---------------------------------------------* + * Send an arbitrary SCSI command to a device. + * + *---------------------------------------------*/ +int +wm_scsi(d, cdb, cdblen, retbuf, retbuflen, getreply) + struct wm_drive *d; + unsigned char *cdb; + int cdblen; + void *retbuf; + int retbuflen; + int getreply; +{ + return (-1); +} + + +/*-------------------------------------------------------------------* + * Open the CD device and figure out what kind of drive is attached. + *-------------------------------------------------------------------*/ +int +wmcd_open( struct wm_drive *d ) +{ + int fd; + static int warned = 0; + char vendor[32] = WM_STR_GENVENDOR; + char model[32] = WM_STR_GENMODEL; + char rev[32] = WM_STR_GENREV; + + if (d->fd >= 0) /* Device already open? */ + return (0); + + if (cd_device == NULL) + cd_device = DEFAULT_CD_DEVICE; + + d->fd = open(cd_device, 0); + if (d->fd < 0) + { + if (errno == EACCES) + { + if (!warned) + { + fprintf(stderr, + "As root, please run\n\nchmod 666 %s\n\n%s\n", cd_device, + "to give yourself permission to access the CD-ROM device."); + warned++; + } + } + + /* No CD in drive. */ + return (1); + } + + if (warned) + { + warned = 0; + fprintf(stderr, "Thank you.\n"); + } + + /* Now fill in the relevant parts of the wm_drive structure. */ + fd = d->fd; + + *d = *(find_drive_struct(vendor, model, rev)); + wm_drive_settype(vendor, model, rev); + + (d->init)(d); + + d->fd = fd; + + return (0); +} /* wmcd_open() */ + +/* + * Re-Open the device if it is open. + */ +int +wmcd_reopen( struct wm_drive *d ) +{ + int status; + + do { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "wmcd_reopen "); + if (d->fd >= 0) /* Device really open? */ + { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "closes the device and "); + status = close( d->fd ); /* close it! */ + /* we know, that the file is closed, do we? */ + d->fd = -1; + } + wm_susleep( 1000 ); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "calls wmcd_open()\n"); + status = wmcd_open( d ); /* open it as usual */ + wm_susleep( 1000 ); + } while ( status != 0 ); + return status; +} /* wmcd_reopen() */ + +#endif diff --git a/ascd/libworkman/plat_hpux.c b/ascd/libworkman/plat_hpux.c new file mode 100644 index 0000000..0175401 --- /dev/null +++ b/ascd/libworkman/plat_hpux.c @@ -0,0 +1,355 @@ +/* + * $Id: plat_hpux.c,v 1.8 1999/06/17 06:48:03 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * HP/UX-specific drive control routines. + */ + +#if defined(hpux) || defined(__hpux) + +static char plat_hpux_id[] = "$Id: plat_hpux.c,v 1.8 1999/06/17 06:48:03 dirk Exp $"; + +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <ustat.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/stat.h> + +#include "include/wm_config.h" + +/* + * this is for glibc 2.x which the ust structure in + * ustat.h not stat.h + */ +#ifdef __GLIBC__ +#include <sys/ustat.h> +#endif + +#include <sys/time.h> +#include <sys/scsi.h> + +#include "include/wm_struct.h" +#include "include/wm_helpers.h" + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +void *malloc(); +char *strchr(); + +int min_volume = 0; +int max_volume = 255; + +extern char *cd_device; + +/*--------------------------------------------------------* + * Initialize the drive. A no-op for the generic driver. + *--------------------------------------------------------*/ +int +gen_init( struct wm_drive *d ) +{ + return (0); +} + +/*-------------------------------------* + * Get the number of tracks on the CD. + *-------------------------------------*/ +int +gen_get_trackcount(struct wm_drive *d, int *tracks ) +{ + return (wm_scsi2_get_trackcount(d, tracks)); +} + +/*---------------------------------------------------------* + * Get the start time and mode (data or audio) of a track. + *---------------------------------------------------------*/ +int +gen_get_trackinfo( struct wm_drive *d, int *track, int *data, int *startframe) +{ + return (wm_scsi2_get_trackinfo(d, track, data, startframe)); +} + +/*-------------------------------------* + * Get the number of frames on the CD. + *-------------------------------------*/ +int +gen_get_cdlen(struct wm_drive *d, int *frames) +{ + int tmp; + + return (wm_scsi2_get_cdlen(d, frames)); +} + +/*--------------------------------------------------------------------------* + * Get the current status of the drive: the current play mode, the absolute + * position from start of disc (in frames), and the current track and index + * numbers if the CD is playing or paused. + *--------------------------------------------------------------------------*/ +int +gen_get_drive_status( struct wm_drive *d, enum wm_cd_modes oldmode, enum wm_cd_modes *mode, + int *pos, int *track, int *index ) + struct wm_drive *d; + enum wm_cd_modes oldmode, *mode; + int *pos, *track, *index; +{ + return (wm_scsi2_get_drive_status(d, oldmode, mode, pos, track, index)); +} + +/*---------------------------------------------------------------------* + * Set the volume level for the left and right channels. Their values + * range from 0 to 100. + *---------------------------------------------------------------------*/ +int +gen_set_volume( struct wm_drive *d, int left, int right ) +{ + return (wm_scsi2_set_volume(d, left, right)); +} + +/*---------------------------------------------------------------------* + * Read the initial volume from the drive, if available. Each channel + * ranges from 0 to 100, with -1 indicating data not available. + *---------------------------------------------------------------------*/ +int +gen_get_volume( struct wm_drive *d, int *left, int *right ) +{ + return (wm_scsi2_get_volume(d, left, right)); +} + +/*---------------* + * Pause the CD. + *---------------*/ +int +gen_pause( struct wm_drive *d ) +{ + return (wm_scsi2_pause(d)); +} + +/*-------------------------------------------------* + * Resume playing the CD (assuming it was paused.) + *-------------------------------------------------*/ +int +gen_resume( struct wm_drive *d ) +{ + return (wm_scsi2_resume(d)); +} + +/*--------------* + * Stop the CD. + *--------------*/ +int +gen_stop( struct wm_drive *d ) +{ + return (wm_scsi2_stop(d)); +} + +/*------------------------------------------------------------* + * Play the CD from one position to another (both in frames.) + *------------------------------------------------------------*/ +int +gen_play( struct wm_drive *d, int start, int end ) +{ + return (wm_scsi2_play(d, start, end)); +} + +/*----------------------------------------* + * Eject the current CD, if there is one. + *----------------------------------------*/ +int +gen_eject( struct wm_drive *d ) +{ + struct stat stbuf; + struct ustat ust; + + if (fstat(d->fd, &stbuf) != 0) + return (-2); + + /* Is this a mounted filesystem? */ + if (ustat(stbuf.st_rdev, &ust) == 0) + return (-3); + + return (wm_scsi2_eject(d)); +} + +/*----------------------------------------* + * Close the CD tray + *----------------------------------------*/ + +int gen_closetray(struct wm_drive *d) +{ +#ifdef CAN_CLOSE + return (wm_scsi2_closetray(d)); +#else + /* Always succeed if the drive can't close */ + return(0); +#endif /* CAN_CLOSE */ +} /* gen_closetray() */ + +/*----------------------------------* + * Send a SCSI command out the bus. + *----------------------------------*/ +int +wm_scsi( struct wm_drive *d, unsigned char *cdb, int cdblen, + void *retbuf, int retbuflen, int getreply ) +{ +#ifdef SIOC_IO + struct sctl_io cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cdb_length = cdblen; + cmd.data = retbuf; + cmd.data_length = retbuflen; + cmd.max_msecs = 1000; + cmd.flags = getreply ? SCTL_READ : 0; + memcpy(cmd.cdb, cdb, cdblen); + + return (ioctl(d->fd, SIOC_IO, &cmd)); +#else + /* this code, for pre-9.0, is BROKEN! ugh. */ + char reply_buf[12]; + struct scsi_cmd_parms cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.clock_ticks = 500; + cmd.cmd_mode = 1; + cmd.cmd_type = cdblen; + memcpy(cmd.command, cdb, cdblen); + if (ioctl(d->fd, SIOC_SET_CMD, &cmd) < 0) + return (-1); + + if (! retbuf || ! retbuflen) + (void) read(d->fd, reply_buf, sizeof(reply_buf)); + else if (getreply) + { + if (read(d->fd, retbuf, retbuflen) < 0) + return (-1); + } + else + if (write(d->fd, retbuf, retbuflen) < 0) + return (-1); + + return (0); +#endif +} + +/*-------------------------------------------------------------* + * Open the CD and figure out which kind of drive is attached. + *-------------------------------------------------------------*/ +int +wmcd_open( struct wm_drive *d ) +{ + int fd, flag = 1; + static int warned = 0; +/* unsigned ? */ + char vendor[32], model[32], rev[32]; + + if (d->fd >= 0) /* Device already open? */ + return (0); + + if (cd_device == NULL) + cd_device = DEFAULT_CD_DEVICE; + + d->fd = open(cd_device, O_RDWR); + if (d->fd < 0) + { + if (errno == EACCES) + { + if (! warned) + { + fprintf(stderr, + "As root, please run\n\nchmod 666 %s\n\n%s\n", cd_device, + "to give yourself permission to access the CD-ROM device."); + warned++; + } + } + else if (errno != EINTR) + { + perror(cd_device); + exit(1); + } + + /* No CD in drive. */ + return (1); + } + + /* Prepare the device to receive raw SCSI commands. */ + if (ioctl(d->fd, SIOC_CMD_MODE, &flag) < 0) + { + fprintf(stderr, "%s: SIOC_CMD_MODE: true: %s\n", + cd_device, strerror(errno)); + /*exit(1);*/ + } + + if (warned) + { + warned = 0; + fprintf(stderr, "Thank you.\n"); + } + + /* Now fill in the relevant parts of the wm_drive structure. */ + fd = d->fd; + + /* Default drive is the HP one, which might not respond to INQUIRY */ + strcpy(&vendor, "TOSHIBA"); + strcpy(&model, "XM-3301"); + rev[0] = '\0'; + wm_scsi_get_drive_type(d, vendor, model, rev); + *d = *(find_drive_struct(vendor, model, rev)); + wm_drive_settype(vendor, model, rev); + + d->fd = fd; + + (d->init)(d); + + return (0); +} /* wmcd_open() */ + +/* + * Re-Open the device if it is open. + */ +int +wmcd_reopen( struct wm_drive *d ) +{ + int status; + + do { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "wmcd_reopen "); + if (d->fd >= 0) /* Device really open? */ + { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "closes the device and "); + status = close( d->fd ); /* close it! */ + /* we know, that the file is closed, do we? */ + d->fd = -1; + } + wm_susleep( 1000 ); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "calls wmcd_open()\n"); + status = wmcd_open( d ); /* open it as usual */ + wm_susleep( 1000 ); + } while ( status != 0 ); + return status; +} /* wmcd_reopen() */ + + + +#endif diff --git a/ascd/libworkman/plat_irix.c b/ascd/libworkman/plat_irix.c new file mode 100644 index 0000000..9019588 --- /dev/null +++ b/ascd/libworkman/plat_irix.c @@ -0,0 +1,456 @@ +/* + * $Id: plat_irix.c,v 1.6 1999/03/07 08:36:40 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * IRIX specific. + * + * Taken from the kscd distribution + * + * Paul Kendall + * [email protected], or + * [email protected] + */ + +#ifdef sgi + +static char plat_irix_id[] = "$Id: plat_irix.c,v 1.6 1999/03/07 08:36:40 dirk Exp $"; + +#include "include/wm_config.h" + +/* + * Yes, it was designed for WorkMan 1.4b3 + * Because I did start over from 1.3a, I disable it here. + * There is no guarantee of getting working code by defining + * CDDA yourself. + * + */ +#undef CDDA +/*#define CDDA*/ + +#include <sys/types.h> +#include <sys/time.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <sigfpe.h> +#include <dmedia/cdaudio.h> +#include <dmedia/audio.h> +#include <errno.h> + +#include "include/wm_struct.h" + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +void *malloc(); +char *strchr(); + +int min_volume = 0; +int max_volume = 255; + +extern char *cd_device; + +#ifdef CDDA +static int playing = STOPPED; +static CDPLAYER *icd; +static CDPARSER *icdp; +static CDFRAME cdbuf[12]; +static ALport audioport; +static ALconfig aconfig; +static struct itimerval audiotimer = { {0,0}, {0,25000} }; +static int cdtrack=0; +static int cdframe=0; +static int cdstopframe=0; + +cbprognum(void *arg, CDDATATYPES type, CDPROGNUM* prognum) +{ + cdtrack = prognum->value; +} + +cbabstime(void *arg, CDDATATYPES type, struct cdtimecode* atime) +{ + cdframe = CDtctoframe(atime); + if( cdframe == cdstopframe ) + playing = STOPPED; +} + +cbplayaudio(void *arg, CDDATATYPES type, short* audio) +{ + if(playing != PLAYING) return; + ALwritesamps(audioport, audio, CDDA_NUMSAMPLES); +} + +static void alarmsignal() +{ + int n, i; + if(playing != PLAYING) return; + if( ALgetfilled(audioport) < CDDA_NUMSAMPLES*8 ) { + /* Only get more samples and play them if we're getting low + * this ensures that the CD stays close to the sound + */ + n = CDreadda(icd, cdbuf, 12); + if( n == 0 ) return; + for( i=0 ; i<12 ; i++ ) + CDparseframe(icdp, &cdbuf[i]); + } + signal(SIGALRM, alarmsignal); + setitimer(ITIMER_REAL, &audiotimer, NULL); +} +#endif + +/*--------------------------------------------------------* + * Initialize the drive. A no-op for the generic driver. + *--------------------------------------------------------*/ +int +gen_init( struct wm_drive *d ) +{ +#ifdef CDDA + long Param[4]; + /* Set the audio rate to 44100Hz 16bit 2s-comp stereo */ + aconfig = ALnewconfig(); + ALsetwidth(aconfig, AL_SAMPLE_16); + ALsetsampfmt(aconfig, AL_SAMPFMT_TWOSCOMP); + ALsetchannels(aconfig, 2); + Param[0] = AL_OUTPUT_RATE; Param[1] = AL_RATE_44100; + Param[2] = AL_CHANNEL_MODE; Param[3] = AL_STEREO; + ALsetparams(AL_DEFAULT_DEVICE, Param, 4); + audioport = ALopenport("KDE KSCD Audio", "w", aconfig); + + /* setup cdparser */ + icdp = CDcreateparser(); + CDaddcallback(icdp, cd_audio, (CDCALLBACKFUNC)cbplayaudio, 0); + CDaddcallback(icdp, cd_pnum, (CDCALLBACKFUNC)cbprognum, 0); + CDaddcallback(icdp, cd_atime, (CDCALLBACKFUNC)cbabstime, 0); + + /* Lets handle those floating point exceptions expeditiously. */ + sigfpe_[_UNDERFL].repls = _ZERO; + handle_sigfpes(_ON, _EN_UNDERFL, NULL, _ABORT_ON_ERROR, NULL); +#endif + return 0; +} + +/*-------------------------------------* + * Get the number of tracks on the CD. + *-------------------------------------*/ +int +gen_get_trackcount( struct wm_drive *d, int *tracks ) +{ + CDSTATUS s; + if( CDgetstatus(d->daux, &s)==0 ) + return -1; + *tracks = s.last; + return 0; +} + +/*---------------------------------------------------------* + * Get the start time and mode (data or audio) of a track. + *---------------------------------------------------------*/ +int +gen_get_trackinfo( struct wm_drive *d, int track, int *data, int *startframe) +{ + CDTRACKINFO i; + int ret = CDgettrackinfo(d->daux, track, &i); + if( ret == 0 ) + return -1; + *data = 0; + *startframe = CDmsftoframe(i.start_min,i.start_sec,i.start_frame); + return 0; +} + +/*-------------------------------------* + * Get the number of frames on the CD. + *-------------------------------------*/ +int +gen_get_cdlen( struct wm_drive *d, int *frames ) +{ + CDSTATUS s; + if( CDgetstatus(d->daux, &s)==0 ) + return -1; + *frames = CDmsftoframe(s.total_min,s.total_sec,s.total_frame); + return 0; +} + +/*--------------------------------------------------------------------------* + * Get the current status of the drive: the current play mode, the absolute + * position from start of disc (in frames), and the current track and index + * numbers if the CD is playing or paused. + *--------------------------------------------------------------------------*/ +int +gen_get_drive_status( struct wm_drive *d, enum wm_cd_modes oldmode, + enum wm_cd_modes *mode, int *pos, int *track, + int *index ) +{ +#ifdef CDDA + *mode = playing; + *track = cdtrack; + *pos = cdframe; + *index = 0; +#else + CDSTATUS s; + if( CDgetstatus(d->daux, &s)==0 ) + return -1; + *pos = CDmsftoframe(s.min,s.sec,s.frame); + *track = s.track; + *index = 0; + switch( s.state ) + { + case CD_READY: *mode = WM_CDM_STOPPED; + break; + case CD_STILL: + case CD_PAUSED: *mode = WM_CDM_PAUSED; + break; + case CD_PLAYING: *mode = WM_CDM_PLAYING; + break; + default: *mode = WM_CDM_UNKNOWN; + } +#endif + return 0; +} + +/*---------------------------------------------------------------------* + * Set the volume level for the left and right channels. Their values + * range from 0 to 100. + *---------------------------------------------------------------------*/ +int +gen_set_volume( struct wm_drive *d, int left, int right ) +{ + long Param[4]; + Param[0] = AL_LEFT_SPEAKER_GAIN; Param[1] = left*255/100; + Param[2] = AL_RIGHT_SPEAKER_GAIN; Param[3] = right*255/100; + ALsetparams(AL_DEFAULT_DEVICE, Param, 4); + return 0; +} + +/*---------------------------------------------------------------------* + * Read the initial volume from the drive, if available. Each channel + * ranges from 0 to 100, with -1 indicating data not available. + *---------------------------------------------------------------------*/ +int +gen_get_volume( struct wm_drive *d, int *left, int *right ) +{ + long Param[4]; + Param[0] = AL_LEFT_SPEAKER_GAIN; Param[1] = 0; + Param[2] = AL_RIGHT_SPEAKER_GAIN; Param[3] = 0; + ALgetparams(AL_DEFAULT_DEVICE, Param, 4); + *left = Param[1] * 100 / 255; + *right = Param[3] * 100 / 255; + return 0; +} + +/*---------------* + * Pause the CD. + *---------------*/ +int +gen_pause( struct wm_drive *d ) +{ +#ifdef CDDA + playing = WM_CDM_PAUSED; +#else + CDSTATUS s; + if( CDgetstatus(d->daux, &s)==0 ) + return -1; + if(s.state == CD_PLAYING) + CDtogglepause(d->daux); +#endif + return 0; +} + +/*-------------------------------------------------* + * Resume playing the CD (assuming it was paused.) + *-------------------------------------------------*/ +int +gen_resume( struct wm_drive *d ) +{ +#ifdef CDDA + playing = WM_CDM_PLAYING; + signal(SIGALRM, alarmsignal); + setitimer(ITIMER_REAL, &audiotimer, NULL); +#else + CDSTATUS s; + if( CDgetstatus(d->daux, &s)==0 ) + return -1; + if(s.state == CD_PAUSED) + CDtogglepause(d->daux); +#endif + return 0; +} + +/*--------------* + * Stop the CD. + *--------------*/ +int +gen_stop( struct wm_drive *d ) +{ +#ifdef CDDA + playing = WM_CDM_STOPPED; +#else + CDstop(d->daux); +#endif + return 0; +} + +/*------------------------------------------------------------* + * Play the CD from one position to another (both in frames.) + *------------------------------------------------------------*/ +int +gen_play( struct wm_drive *d, int start, int end ) +{ +#ifdef CDDA + int m, s, f; + CDframetomsf(start, &m, &s, &f); + CDseek(icd, m, s, f); + cdstopframe = end; + playing = PLAYING; + signal(SIGALRM, alarmsignal); + setitimer(ITIMER_REAL, &audiotimer, NULL); +#else + int m, s, f; + CDframetomsf(start, &m, &s, &f); + CDplayabs(d->daux, m, s, f, 1); +#endif + return 0; +} + +/*----------------------------------------* + * Eject the current CD, if there is one. + *----------------------------------------*/ +int +gen_eject( struct wm_drive *d ) +{ +#ifdef CDDA + playing = WM_CDM_STOPPED; +#endif + CDeject(d->daux); + return 0; +} + +/*----------------------------------------* + * Close the CD tray + * + * Please edit and send changes to + * [email protected] + *----------------------------------------*/ + +int gen_closetray(struct wm_drive *d) +{ +#ifdef CAN_CLOSE + if(!close(d->fd)) + { + d->fd=-1; + return(wmcd_reopen(d)); + } else { + return(-1); + } +#else + /* Always succeed if the drive can't close */ + return(0); +#endif /* CAN_CLOSE */ +} /* gen_closetray() */ + + +/*-------------------------------------------------------------* + * Open the CD and figure out which kind of drive is attached. + *-------------------------------------------------------------*/ +int +wmcd_open( struct wm_drive *d ) +{ + int fd; + CDSTATUS s; + + if (d->fd < 0) /* Device already open? */ + { + if (cd_device == NULL) + cd_device = DEFAULT_CD_DEVICE; + + d->fd = 1; + + /* Now fill in the relevant parts of the wm_drive structure. */ + fd = d->fd; + *d = *(find_drive_struct("", "", "")); + d->fd = fd; + (d->init)(d); + + d->daux = CDopen(cd_device,"r"); + if (d->daux == 0) + { + perror(cd_device); + exit(1); + } +#ifdef CDDA + icd = d->daux; +#endif + } + + CDgetstatus(d->daux, &s); + if( s.state==CD_NODISC || s.state==CD_ERROR ) + return 1; + + return (0); +} /* wmcd_open() */ + +/* + * Re-Open the device if it is open. + */ +int +wmcd_reopen( struct wm_drive *d ) +{ + int status; + + do { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "wmcd_reopen "); + if (d->fd >= 0) /* Device really open? */ + { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "closes the device and "); + status = close( d->fd ); /* close it! */ + /* we know, that the file is closed, do we? */ + d->fd = -1; + } + wm_susleep( 1000 ); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "calls wmcd_open()\n"); + status = wmcd_open( d ); /* open it as usual */ + wm_susleep( 1000 ); + } while ( status != 0 ); + return status; +} /* wmcd_reopen() */ + + +/*----------------------------------* + * Send a SCSI command out the bus. + *----------------------------------*/ +int +wm_scsi(d, xcdb, cdblen, retbuf, retbuflen, getreply) + struct wm_drive *d; + unsigned char *xcdb; + int cdblen; + int getreply; + char *retbuf; + int retbuflen; +{ + return -1; +} + +#endif + diff --git a/ascd/libworkman/plat_linux.c b/ascd/libworkman/plat_linux.c new file mode 100644 index 0000000..4b674a8 --- /dev/null +++ b/ascd/libworkman/plat_linux.c @@ -0,0 +1,866 @@ +/* + * $Id: plat_linux.c,v 1.8 1999/06/17 06:48:03 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Linux-specific drive control routines. Very similar to the Sun module. + */ + +#ifdef linux +/* Id for ident command */ +static char plat_linux_id[] = "$Id: plat_linux.c,v 1.8 1999/06/17 06:48:03 dirk Exp $"; + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <malloc.h> +#include <string.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include "include/wm_config.h" + +#if defined(BSD_MOUNTTEST) + #include <mntent.h> +#else + /* + * this is for glibc 2.x which defines ust structure in + * ustat.h not stat.h + */ + #ifdef __GLIBC__ + #include <sys/ustat.h> + #endif +#endif + +#include <sys/time.h> +#include <sys/ioctl.h> +#include <linux/cdrom.h> + +#include "include/wm_cdda.h" +#include "include/wm_struct.h" +#include "include/wm_platform.h" +#include "include/wm_cdrom.h" +#include "include/wm_scsi.h" +#include "include/wm_helpers.h" + +#ifdef OSS_SUPPORT +#include <linux/soundcard.h> +#define CD_CHANNEL SOUND_MIXER_CD +#endif + +#define max(a,b) ((a) > (b) ? (a) : (b)) + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +#ifdef LINUX_SCSI_PASSTHROUGH +/* this is from <scsi/scsi_ioctl.h> */ +# define SCSI_IOCTL_SEND_COMMAND 1 +#endif + +int min_volume = 0; +int max_volume = 255; + +#ifdef OSS_SUPPORT +int mixer; +char mixer_dev_name[20] = "/dev/mixer"; +#endif +extern char *cd_device, *cddaslave_path; + +int cdda_slave = -1; + +/* + * Wait for an acknowledgement from the CDDA slave. + */ +static int +get_ack(int fd) +{ +#if defined(BUILD_CDDA) && defined(WMCDDA_DONE) /* { */ + struct cdda_block blk; + + do + if (read(fd, &blk, sizeof(blk)) <= 0) + return (0); + while (blk.status != WMCDDA_ACK); +#endif /* } */ + + return (1); +} /* get_ack() */ + +/*--------------------------------------------------------* + * Initialize the drive. A no-op for the generic driver. + *--------------------------------------------------------*/ +int +gen_init( struct wm_drive *d ) +{ +#ifdef OSS_SUPPORT +/* + * Open and use the mixer device to set volume + */ + if( ( mixer = open( mixer_dev_name, O_RDWR, 0 ) ) == -1 ) + { + perror( mixer_dev_name ); + exit( -1 ); + } +#endif + return (0); +} /* gen_init() */ + +/* + * Try to initialize the CDDA slave. Returns 0 on error. + */ +int +cdda_init( struct wm_drive *d ) +{ +#if defined(BUILD_CDDA) && defined(WMCDDA_DONE) /* { */ + int slavefds[2]; + + if (cdda_slave > -1) + return (1); + + fprintf( stderr, "slave okay\n" ); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, slavefds)) + { + perror("socketpair"); + return (0); + } + + fprintf( stderr, "going to fork\n" ); + switch (fork()) { + case 0: + close(slavefds[0]); + dup2(slavefds[1], 1); + dup2(slavefds[1], 0); + close(slavefds[1]); + close(d->fd); + /* Try the default path first. */ + execl(cddaslave_path, cddaslave_path, cd_device, NULL); + /* Search $PATH if that didn't work. */ + execlp("cddaslave", "cddaslave", cd_device, NULL); + perror(cddaslave_path); + exit(1); + + case -1: + close(slavefds[0]); + close(slavefds[1]); + perror("fork"); + return (0); + } + + close(slavefds[1]); + cdda_slave = slavefds[0]; + + if (!get_ack(cdda_slave)) + { + fprintf( stderr, "get_ack failed\n" ); + cdda_slave = -1; +/* codec_start(); */ + return (0); + } + + return (1); + +#else /* BUILD_CDDA } { */ + /* + * If we're not building CDDA support, don't even bother trying. + */ + return (0); +#endif +} /* cdda_init() */ + +/* + * Turn off the CDDA slave. + */ +void +cdda_kill( struct wm_drive *d ) +{ + if (cdda_slave > -1) + { + write(cdda_slave, "Q", 1); + get_ack(cdda_slave); + wait(NULL); + cdda_slave = -1; +/* codec_start(); */ + } +} /* cdda_kill() */ + +/*-------------------------------------* + * Get the number of tracks on the CD. + *-------------------------------------*/ +int +gen_get_trackcount(struct wm_drive *d, int *tracks) +{ + struct cdrom_tochdr hdr; + + if (ioctl(d->fd, CDROMREADTOCHDR, &hdr)) + return (-1); + + *tracks = hdr.cdth_trk1; + return (0); +} + +/*---------------------------------------------------------* + * Get the start time and mode (data or audio) of a track. + *---------------------------------------------------------*/ +int +gen_get_trackinfo(struct wm_drive *d, int track, int *data, int *startframe) +{ + struct cdrom_tocentry entry; + + entry.cdte_track = track; + entry.cdte_format = CDROM_MSF; + + if (ioctl(d->fd, CDROMREADTOCENTRY, &entry)) + return (-1); + + *startframe = entry.cdte_addr.msf.minute * 60 * 75 + + entry.cdte_addr.msf.second * 75 + + entry.cdte_addr.msf.frame; + *data = entry.cdte_ctrl & CDROM_DATA_TRACK ? 1 : 0; + + return (0); +} + +/*-------------------------------------* + * Get the number of frames on the CD. + *-------------------------------------*/ +int +gen_get_cdlen(struct wm_drive *d, int *frames) +{ + int tmp; + + return (gen_get_trackinfo(d, CDROM_LEADOUT, &tmp, frames)); +} + + +/* Alarm signal handler. +static void do_nothing(int x) { x++; } +*/ + +/*--------------------------------------------------------------------------* + * Get the current status of the drive: the current play mode, the absolute + * position from start of disc (in frames), and the current track and index + * numbers if the CD is playing or paused. + *--------------------------------------------------------------------------*/ +int +gen_get_drive_status( struct wm_drive *d, enum wm_cd_modes oldmode, + enum wm_cd_modes *mode, int *pos, int *track, int *index ) +{ + struct cdrom_subchnl sc; + +#ifdef SBPCD_HACK + static int prevpos = 0; +#endif + + /* If we can't get status, the CD is ejected, so default to that. */ + *mode = WM_CDM_EJECTED; + + /* Is the device open? */ + if (d->fd < 0) + { + switch (wmcd_open(d)) + { + + case -1: /* error */ + return (-1); + + case 1: /* retry */ + return (0); + } + } + + sc.cdsc_format = CDROM_MSF; + + if (ioctl(d->fd, CDROMSUBCHNL, &sc)) + return (0); + + switch (sc.cdsc_audiostatus) { + case CDROM_AUDIO_PLAY: + *mode = WM_CDM_PLAYING; + *track = sc.cdsc_trk; + *index = sc.cdsc_ind; + *pos = sc.cdsc_absaddr.msf.minute * 60 * 75 + + sc.cdsc_absaddr.msf.second * 75 + + sc.cdsc_absaddr.msf.frame; +#ifdef SBPCD_HACK + if( *pos < prevpos ) + { + if( (prevpos - *pos) < 75 ) + { + *mode = WM_CDM_TRACK_DONE; + } + } + + prevpos = *pos; +#endif + break; + + case CDROM_AUDIO_PAUSED: + case CDROM_AUDIO_NO_STATUS: + case CDROM_AUDIO_INVALID: /**/ + if (oldmode == WM_CDM_PLAYING || oldmode == WM_CDM_PAUSED) + { + *mode = WM_CDM_PAUSED; + *track = sc.cdsc_trk; + *index = sc.cdsc_ind; + *pos = sc.cdsc_absaddr.msf.minute * 60 * 75 + + sc.cdsc_absaddr.msf.second * 75 + + sc.cdsc_absaddr.msf.frame; + } + else + *mode = WM_CDM_STOPPED; + break; + + case CDROM_AUDIO_COMPLETED: + *mode = WM_CDM_TRACK_DONE; /* waiting for next track. */ + break; + + default: + *mode = WM_CDM_UNKNOWN; + break; + } + + return (0); +} + +/*------------------------------------------------------------------------* + * scale_volume(vol, max) + * + * Return a volume value suitable for passing to the CD-ROM drive. "vol" + * is a volume slider setting; "max" is the slider's maximum value. + * This is not used if sound card support is enabled. + * + *------------------------------------------------------------------------*/ +#ifndef OSS_SUPPORT +static int +scale_volume( int vol, int max ) +{ +#ifdef CURVED_VOLUME + return ((max * max - (max - vol) * (max - vol)) * + (max_volume - min_volume) / (max * max) + min_volume); +#else + return ((vol * (max_volume - min_volume)) / max + min_volume); +#endif +} /* scale_volume() */ +#endif + +/*---------------------------------------------------------------------* + * Set the volume level for the left and right channels. Their values + * range from 0 to 100. + *---------------------------------------------------------------------*/ +int +gen_set_volume( struct wm_drive *d, int left, int right ) +{ +#ifndef OSS_SUPPORT + struct cdrom_volctrl v; +#endif + int vol; + +#ifdef OSS_SUPPORT + + left = left < 0 ? 0 : left > 100 ? 100 : left; + right = right < 0 ? : right > 100 ? 100 : right; + + vol = ( 0x007f & left ) | ( 0x7f00 & ( right << 8 ) ); + if( ioctl( mixer, MIXER_WRITE( CD_CHANNEL ), &vol ) == -1 ) + { + perror( "MIXER_WRITE" ); + return ( -1 ); + } + return ( 0 ); +#else + +/* Adjust the volume to make up for the CD-ROM drive's weirdness. */ + left = scale_volume(left, 100); + right = scale_volume(right, 100); + + v.channel0 = v.channel2 = left < 0 ? 0 : left > 255 ? 255 : left; + v.channel1 = v.channel3 = right < 0 ? 0 : right > 255 ? 255 : right; + + return (ioctl(d->fd, CDROMVOLCTRL, &v)); +# endif +} + +/*---------------* + * Pause the CD. + *---------------*/ +int +gen_pause(d) + struct wm_drive *d; +{ + return (ioctl(d->fd, CDROMPAUSE)); +} + +/*-------------------------------------------------* + * Resume playing the CD (assuming it was paused.) + *-------------------------------------------------*/ +int +gen_resume(struct wm_drive *d) +{ + return (ioctl(d->fd, CDROMRESUME)); +} + +/*--------------* + * Stop the CD. + *--------------*/ +int +gen_stop(struct wm_drive *d) +{ + return (ioctl(d->fd, CDROMSTOP)); +} + +/*------------------------------------------------------------* + * Play the CD from one position to another (both in frames.) + *------------------------------------------------------------*/ +int +gen_play(struct wm_drive *d, int start, int end) +{ + struct cdrom_msf msf; + + msf.cdmsf_min0 = start / (60*75); + msf.cdmsf_sec0 = (start % (60*75)) / 75; + msf.cdmsf_frame0 = start % 75; + msf.cdmsf_min1 = end / (60*75); + msf.cdmsf_sec1 = (end % (60*75)) / 75; + msf.cdmsf_frame1 = end % 75; + +#ifndef FAST_IDE + if (ioctl(d->fd, CDROMSTART)) + return (-1); +#endif + if (ioctl(d->fd, CDROMPLAYMSF, &msf)) + return (-2); + + return (0); +} + +/*----------------------------------------* + * Eject the current CD, if there is one. + *----------------------------------------*/ +int +gen_eject(struct wm_drive *d) +{ + struct stat stbuf; +#if !defined(BSD_MOUNTTEST) + struct ustat ust; +#else + struct mntent *mnt; + FILE *fp; +#endif + + if (fstat(d->fd, &stbuf) != 0) + return (-2); + + /* Is this a mounted filesystem? */ +#if !defined(BSD_MOUNTTEST) + if (ustat(stbuf.st_rdev, &ust) == 0) + return (-3); +#else + /* + * This is the same test as in the WorkBone interface. + * I should eliminate it there, because there is no need + * for it in the UI + */ + /* check if drive is mounted (from Mark Buckaway's cdplayer code) */ + /* Changed it again (look at XPLAYCD from ???? */ + /* It's better to check the device name rather than one device is */ + /* mounted as iso9660. That prevents "no playing" if you have more*/ + /* than one CD-ROM, and one of them is mounted, but it's not the */ + /* audio CD -dirk */ + if ((fp = setmntent (MOUNTED, "r")) == NULL) + { + fprintf (stderr, "Could not open %s: %s\n", MOUNTED, strerror (errno)); + return(-3); + } + while ((mnt = getmntent (fp)) != NULL) + { + if (strcmp (mnt->mnt_fsname, cd_device) == 0) + { + fputs ("CDROM already mounted (according to mtab). Operation aborted.\n", stderr); + endmntent (fp); + return(-3); + } + } + endmntent (fp); +#endif /* BSD_MOUNTTEST */ + + if (ioctl(d->fd, CDROMEJECT)) + return (-1); +/*------------------ + * Things in "foobar_one" are left over from 1.4b3 + * I put them here for further observation. In 1.4b3, however, + * that workaround didn't help at least for /dev/sbpcd + * (The tray closed just after ejecting because re-opening the + * device causes the tray to close) + *------------------*/ +#ifdef foobar_one +extern int intermittent_dev + /* + * Some drives (drivers?) won't recognize a new CD if we leave the + * device open. + */ + if (intermittent_dev) + { + close(d->fd); + d->fd = -1; + } +#endif + + return (0); +} /* gen_eject() */ + +/*----------------------------------------* + * Close the CD tray + *----------------------------------------*/ + +int gen_closetray(struct wm_drive *d) +{ +#ifdef CAN_CLOSE +#ifdef CDROMCLOSETRAY + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "CDROMCLOSETRAY closing tray...\n"); + if (ioctl(d->fd, CDROMCLOSETRAY)) + return (-1); +#else + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "wmcd_reopen() closing tray...\n"); + if(!close(d->fd)) + { + d->fd=-1; + return(wmcd_reopen(d)); + } else { + return(-1); + } +#endif /* CDROMCLOSETRAY */ +#else /* CAN_CLOSE */ + /* Always succeed if the drive can't close. */ + return(0); +#endif /* CAN_CLOSE */ +} /* gen_closetray() */ + +/*--------------------------------* + * Keep the CD open all the time. + * disabled, analogous to 1.4b3 + *--------------------------------* +void +keep_cd_open( void ) +{ + int fd; + struct flock fl; + extern end; + + + for (fd = 0; fd < 256; fd++) + close(fd); + + if (fork()) + exit(0); + + if ((fd = open("/tmp/cd.lock", O_RDWR | O_CREAT, 0666)) < 0) + exit(0); + fl.l_type = F_WRLCK; + fl.l_whence = 0; + fl.l_start = 0; + fl.l_len = 0; + if (fcntl(fd, F_SETLK, &fl) < 0) + exit(0); + + if (open(cd_device, 0) >= 0) + { + brk(&end); + pause(); + } + + exit(0); +} +*/ + +/*---------------------------------------------------------------------* + * Read the initial volume from the drive, if available. Each channel + * ranges from 0 to 100, with -1 indicating data not available. + *---------------------------------------------------------------------*/ +int +gen_get_volume( struct wm_drive *d, int *left, int *right ) +{ +#if defined(BUILD_CDDA) && defined(WMCDDA_DONE) /* { */ + struct cdda_block blk; + + if (cdda_slave > -1) + { + write(cdda_slave, "G", 1); + get_ack(cdda_slave); + read(cdda_slave, &blk, sizeof(blk)); + + *left = *right = (blk.volume * 100 + 254) / 255; + + if (blk.balance < 110) + *right = (((blk.volume * blk.balance + 127) / 128) * + 100 + 254) / 255; + else if (blk.balance > 146) + *left = (((blk.volume * (255 - blk.balance) + + 127) / 128) * 100 + 254) / 255; + + return (0); + } +#else /* } */ +#ifdef OSS_SUPPORT + int vol; + + if( ioctl( mixer, MIXER_READ( CD_CHANNEL ), &vol ) == -1 ) + { + perror( "MIXER_READ" ); + *left = *right = -1; + } + *right = 0x007f & ( vol >> 8 ); + *left = 0x007f & vol; + +#else + /* Suns, HPs, Linux, NEWS can't read the volume; oh well */ + *left = *right = -1; + +#endif +#endif + return (0); +} + +#ifdef BUILD_CDDA /* { */ + +/* + * Tell the CDDA slave to set the play direction. + */ +void +gen_set_direction( int newdir ) +{ + unsigned char buf[2]; + + if (cdda_slave > -1) + { + buf[0] = 'd'; + buf[1] = newdir; + write(cdda_slave, buf, 2); + get_ack(cdda_slave); + } +} + +/* + * Tell the CDDA slave to set the play speed. + */ +void +gen_set_speed( int speed ) +{ + unsigned char buf[2]; + + if (cdda_slave > -1) + { + buf[0] = 's'; + buf[1] = speed; + write(cdda_slave, buf, 2); + get_ack(cdda_slave); + } +} + +/* + * Tell the CDDA slave to set the loudness level. + */ +void +gen_set_loudness( int loud ) +{ + unsigned char buf[2]; + + if (cdda_slave > -1) + { + buf[0] = 'L'; + buf[1] = loud; + write(cdda_slave, buf, 2); + get_ack(cdda_slave); + } +} + +/* + * Tell the CDDA slave to start (or stop) saving to a file. + */ +void +gen_save( char *filename ) +{ + int len; + + if (filename == NULL || filename[0] == '\0') + len = 0; + else + len = strlen(filename); + write(cdda_slave, "F", 1); + write(cdda_slave, &len, sizeof(len)); + if (len) + write(cdda_slave, filename, len); + get_ack(cdda_slave); +} + +#endif /* BUILD_CDDA } */ + +/*---------------------------------------------* + * Send an arbitrary SCSI command to a device. + *---------------------------------------------*/ +int +wm_scsi( struct wm_drive *d, unsigned char *cdb, int cdblen, + void *retbuf, int retbuflen, int getreply ) +{ +#ifdef LINUX_SCSI_PASSTHROUGH + + char *cmd; + int cmdsize; + + cmdsize = 2 * sizeof(int); + if (retbuf) + { + if (getreply) cmdsize += max(cdblen, retbuflen); + else cmdsize += (cdblen + retbuflen); + } + else cmdsize += cdblen; + + cmd = malloc(cmdsize); + if (cmd == NULL) + return (-1); + + ((int*)cmd)[0] = cdblen + ((retbuf && !getreply) ? retbuflen : 0); + ((int*)cmd)[1] = ((retbuf && getreply) ? retbuflen : 0); + + memcpy(cmd + 2*sizeof(int), cdb, cdblen); + if (retbuf && !getreply) + memcpy(cmd + 2*sizeof(int) + cdblen, retbuf, retbuflen); + + if (ioctl(d->fd, SCSI_IOCTL_SEND_COMMAND, cmd)) + { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "%s: ioctl() failure\n", __FILE__); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "command buffer is:\n"); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "%02x %02x %02x %02x %02x %02x\n", + cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]); + free(cmd); + return (-1); + } + + if (retbuf && getreply) + memcpy(retbuf, cmd + 2*sizeof(int), retbuflen); + + free(cmd); + return 0; + +#else /* Linux SCSI passthrough*/ + return (-1); +#endif +} + +/*---------------------------------------------------------------------------* + * Open the CD device and figure out what kind of drive is attached. + *---------------------------------------------------------------------------*/ +int +wmcd_open( struct wm_drive *d ) +{ + int fd; + static int warned = 0; + int retval = 0; + char vendor[32], model[32], rev[32]; + + if (cd_device == NULL) + cd_device = DEFAULT_CD_DEVICE; + + if (d->fd >= 0) /* Device already open? */ + { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "wmcd_open(): [device is open (fd=%d)]\n", d->fd); + return (0); + } + + d->fd = open(cd_device, O_RDONLY | O_NONBLOCK); + if (d->fd < 0) + { + if (errno == EACCES) + { + if (!warned) + { + fprintf(stderr, + "As root, please run\n\nchmod 666 %s\n\n%s\n", cd_device, + "to give yourself permission to access the CD-ROM device."); + warned++; + } + } +/* Hack proposed by Carey Evans, introduced by Debian maintainer : + * treat EIO like ENXIO since some Linux drives do never return ENXIO + */ + else if ((errno != ENXIO) && (errno != EIO) && (errno != ENOMEDIUM)) + { + perror(cd_device); + exit(1); + } + + /* No CD in drive. */ + retval = 1; + } + + if (warned) + { + warned = 0; + fprintf(stderr, "Thank you.\n"); + } + + /* Now fill in the relevant parts of the wm_drive structure. */ + fd = d->fd; + +#ifdef LINUX_SCSI_PASSTHROUGH + /* Can we figure out the drive type? */ + wm_scsi_get_drive_type(d, vendor, model, rev); +#endif + *d = *(find_drive_struct(vendor, model, rev)); + wm_drive_settype(vendor, model, rev); + + d->fd = fd; + (d->init)(d); + return retval; +} /* wmcd_open() */ + +/* + * Re-Open the device if it is open. + */ +int +wmcd_reopen( struct wm_drive *d ) +{ + int status; + + do { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "wmcd_reopen "); + if (d->fd >= 0) /* Device really open? */ + { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "closes the device and "); + status = close( d->fd ); /* close it! */ + /* we know, that the file is closed, do we? */ + d->fd = -1; + } + wm_susleep( 1000 ); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "calls wmcd_open()\n"); + status = wmcd_open( d ); /* open it as usual */ + wm_susleep( 1000 ); + } while ( status != 0 ); + return status; +} /* wmcd_reopen() */ + +#endif /* linux */ diff --git a/ascd/libworkman/plat_linux_audio.c b/ascd/libworkman/plat_linux_audio.c new file mode 100644 index 0000000..c761e9f --- /dev/null +++ b/ascd/libworkman/plat_linux_audio.c @@ -0,0 +1,490 @@ +/* + * $Id: plat_linux_audio.c,v 1.3 1999/03/07 08:36:40 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Linux digital audio functions. + */ + +#include "include/wm_config.h" + +#if defined(linux) && defined(BUILD_CDDA) /* { */ + +static char plat_linux_audio_id[] = "$Id: plat_linux_audio.c,v 1.3 1999/03/07 08:36:40 dirk Exp $"; + +#include "include/wm_cdda.h" + +/* types.h included by wm_cdda.h */ + +#include <stdio.h> +#include <malloc.h> +#include <sys/ioctl.h> +#include <sys/audioio.h> +#include <sys/stropts.h> +#include <sys/time.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +/* + * Since there's a lag time between writing audio to the audio device and + * hearing it, we need to make sure the status indicators correlate to what's + * playing out the speaker. Luckily, Solaris gives us some audio + * synchronization facilities that make this pretty easy. + * + * We maintain a circular queue of status information. When we write some + * sound to the audio device, we put its status info into the queue. We write + * a marker into the audio stream; when the audio device driver encounters the + * marker, it increments a field in a status structure. When we see that + * field go up, we grab the next status structure from the queue and send it + * to the parent process. + * + * The minimum size of the queue depends on the latency of the audio stream. + */ +#define QSIZE 500 + +struct cdda_block queue[QSIZE]; +int qtail; +int qstart; + +/* + * We only send WMCDDA_PLAYED status messages upstream when the CD is supposed + * to be playing; this is used to keep track. + */ +extern int playing; + +static int aufd, aucfd; +static int raw_audio = 1; /* Can /dev/audio take 44.1KHz stereo? */ + +/* + * For fast linear-to-ulaw mapping, we use a lookup table that's generated + * at startup. + */ +unsigned char *ulawmap, linear_to_ulaw(); + +char *getenv(); + +/* + * Dummy signal handler so writes to /dev/audio will interrupt. + */ +static void +dummy( void ) +{ + signal(SIGALRM, dummy); +} + +/* + * Initialize the audio device. + */ +void +wmaudio_init( void ) +{ + audio_info_t info; + char *audiodev, *acdev; + int linval; + + audiodev = getenv("AUDIODEV"); + if (audiodev == NULL) + audiodev = "/dev/audio"; + + acdev = malloc(strlen(audiodev) + 4); + if (acdev == NULL) + { + perror("Can't allocate audio control filename"); + exit(1); + } + strcpy(acdev, audiodev); + strcat(acdev, "ctl"); + + aucfd = open(acdev, O_WRONLY, 0); + if (aucfd < 0) + { + perror(acdev); + exit(1); + } + free(acdev); + + aufd = open(audiodev, O_WRONLY, 0); + if (aufd < 0) + { + perror(audiodev); + exit(1); + } + + signal(SIGALRM, dummy); + + /* + * Try to set the device to CD-style audio; we can process it + * with the least CPU overhead. + */ + AUDIO_INITINFO(&info); + info.play.sample_rate = 44100; + info.play.channels = 2; + info.play.precision = 16; + info.play.encoding = AUDIO_ENCODING_LINEAR; + info.play.pause = 0; + info.record.pause = 0; + info.monitor_gain = 0; + + if (ioctl(aufd, AUDIO_SETINFO, &info) < 0) + if (errno == EINVAL) + { + /* + * Oh well, so much for that idea. + */ + AUDIO_INITINFO(&info); + info.play.sample_rate = 8000; + info.play.channels = 1; + info.play.precision = 8; + info.play.encoding = AUDIO_ENCODING_ULAW; + info.play.pause = 0; + info.record.pause = 0; + info.monitor_gain = 0; + if (ioctl(aufd, AUDIO_SETINFO, &info) < 0) + { + perror("Can't set up audio device"); + exit(1); + } + + /* + * Initialize the linear-to-ulaw mapping table. + */ + if (ulawmap == NULL) + ulawmap = malloc(65536); + if (ulawmap == NULL) + { + perror("malloc"); + exit(1); + } + for (linval = 0; linval < 65536; linval++) + ulawmap[linval] = linear_to_ulaw(linval-32768); + ulawmap += 32768; + raw_audio = 0; + } + else + { + perror(audiodev); + exit(1); + } +} + +/* + * Get ready to play some sound. + */ +void +wmaudio_ready( void ) +{ + audio_info_t info; + + /* + * Start at the correct queue position. + */ + if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) perror("AUDIO_GETINFO"); + qtail = info.play.eof % QSIZE; + qstart = qtail; + + queue[qtail].status = WMCDDA_OK; +} + +/* + * Stop the audio immediately. + */ +void +wmaudio_stop( void ) +{ + if (ioctl(aufd, I_FLUSH, FLUSHRW) < 0) + perror("flush"); +} + +/* + * Close the audio device. + */ +void +wmaudio_close( void ) +{ + wmaudio_stop(); + close(aufd); + close(aucfd); +} + +/* + * Set the volume level. + */ +void +wmaudio_volume(int level) +{ + audio_info_t info; + + AUDIO_INITINFO(&info); + if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) perror("AUDIO_GETINFO"); + info.play.gain = level; + if (ioctl(aucfd, AUDIO_SETINFO, &info) < 0) perror("AUDIO_SETINFO"); +} + +/* + * Set the balance level. + */ +void +wmaudio_balance(int level) +{ + audio_info_t info; + + AUDIO_INITINFO(&info); + if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) perror("AUDIO_GETINFO"); + level *= AUDIO_RIGHT_BALANCE; + info.play.balance = level / 255; + if (ioctl(aucfd, AUDIO_SETINFO, &info) < 0) perror("AUDIO_SETINFO"); +} + +/* + * Mark the most recent audio block on the queue as the last one. + */ +void +wmaudio_mark_last( void ) +{ + queue[qtail].status = WMCDDA_DONE; +} + +/* + * Figure out the most recent status information and send it upstream. + */ +int +wmaudio_send_status( void ) +{ + audio_info_t info; + int qhead; + + /* + * Now send the most current status information to our parent. + */ + if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) + perror("AUDIO_GETINFO"); + qhead = info.play.eof % QSIZE; + + if (qhead != qstart && playing) + { + int balance; + + if (queue[qhead].status != WMCDDA_DONE) + queue[qhead].status = WMCDDA_PLAYED; + queue[qhead].volume = info.play.gain; + queue[qhead].balance = (info.play.balance * 255) / + AUDIO_RIGHT_BALANCE; + + send_status(queue + qhead); + qstart = -1; + } + + return (queue[qhead].status == WMCDDA_DONE); +} + +/* + * Play some audio and pass a status message upstream, if applicable. + * Returns 0 on success. + */ +int +wmaudio_play(unsigned char *rawbuf, long buflen, struct cdda_block *blk) +{ + int i; + short *buf16; + int alarmcount = 0; + struct itimerval it; + + alarm(1); + + while (write(aufd, rawbuf, buflen) <= 0) + if (errno == EINTR) + { + if (! raw_audio && alarmcount++ < 5) + { + /* + * 8KHz /dev/audio blocks for several seconds + * waiting for its queue to drop below a low + * water mark. + */ + wmaudio_send_status(); + timerclear(&it.it_interval); + timerclear(&it.it_value); + it.it_value.tv_usec = 500000; + setitimer(ITIMER_REAL, &it, NULL); + continue; + } + +/* close(aufd); + close(aucfd); + wmaudio_init(); +*/ wmaudio_stop( void ); + alarm(2); + continue; + } + else + { + blk->status = WMCDDA_ERROR; + return (-1); + } + alarm(0); + + /* + * Mark this spot in the audio stream. + * + * Marks don't always succeed (if the audio buffer is empty + * this call will block forever) so do it asynchronously. + */ + fcntl(aufd, F_SETFL, O_NONBLOCK); + if (write(aufd, rawbuf, 0) < 0) + { + if (errno != EAGAIN) + perror("audio mark"); + } + else + qtail = (qtail + 1) % QSIZE; + + fcntl(aufd, F_SETFL, 0); + + queue[qtail] = *blk; + + if (wmaudio_send_status() < 0) + return (-1); + else + return (0); +} + +/* + * Get the current audio state. + */ +void +wmaudio_state(struct cdda_block *blk) +{ + audio_info_t info; + int balance; + + if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) + perror("AUDIO_GETINFO"); + blk->volume = info.play.gain; + blk->balance = (info.play.balance * 255) / AUDIO_RIGHT_BALANCE; +} + +/* +** This routine converts from linear to ulaw. +** +** Craig Reese: IDA/Supercomputing Research Center +** Joe Campbell: Department of Defense +** 29 September 1989 +** +** References: +** 1) CCITT Recommendation G.711 (very difficult to follow) +** 2) "A New Digital Technique for Implementation of Any +** Continuous PCM Companding Law," Villeret, Michel, +** et al. 1973 IEEE Int. Conf. on Communications, Vol 1, +** 1973, pg. 11.12-11.17 +** 3) MIL-STD-188-113,"Interoperability and Performance Standards +** for Analog-to_Digital Conversion Techniques," +** 17 February 1987 +** +** Input: Signed 16 bit linear sample +** Output: 8 bit ulaw sample +*/ +#define ZEROTRAP /* turn on the trap as per the MIL-STD */ +#define BIAS 0x84 /* define the add-in bias for 16 bit samples */ +#define CLIP 32635 + +unsigned char +linear_to_ulaw( sample ) +int sample; +{ + static int exp_lut[256] = {0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7}; + int sign, exponent, mantissa; + unsigned char ulawbyte; + + /* Get the sample into sign-magnitude. */ + sign = (sample >> 8) & 0x80; /* set aside the sign */ + if ( sign != 0 ) sample = -sample; /* get magnitude */ + if ( sample > CLIP ) sample = CLIP; /* clip the magnitude */ + + /* Convert from 16 bit linear to ulaw. */ + sample = sample + BIAS; + exponent = exp_lut[( sample >> 7 ) & 0xFF]; + mantissa = ( sample >> ( exponent + 3 ) ) & 0x0F; + ulawbyte = ~ ( sign | ( exponent << 4 ) | mantissa ); +#ifdef ZEROTRAP + if ( ulawbyte == 0 ) ulawbyte = 0x02; /* optional CCITT trap */ +#endif + + return ulawbyte; +} + +/* + * Downsample a block of CDDA data, if necessary, for playing out an old-style + * audio device. + */ +long +wmaudio_convert(unsigned char *rawbuf, long buflen, struct cdda_block *blk) +{ + short *buf16 = (short *)rawbuf; + int i, j, samples; + int mono_value; + unsigned char *rbend = rawbuf + buflen; + + /* Don't do anything if the audio device can take the raw values. */ + if (raw_audio) + return (buflen); + + for (i = 0; buf16 < (short *)(rbend); i++) + { + /* Downsampling to 8KHz is a little irregular. */ + samples = (i & 1) ? ((i % 20) ? 10 : 12) : 12; + + /* And unfortunately, we don't always end on a nice boundary. */ + if (buf16 + samples > (short *)(rbend)) + samples = ((short *)rbend) - buf16; + + /* + * No need to average all the values; taking the first one + * is sufficient and less CPU-intensive. But we do need to + * do both channels. + */ + mono_value = (buf16[0] + buf16[1]) / 2; + buf16 += samples; + rawbuf[i] = ulawmap[mono_value]; + } + + return (i); +} + +#endif /* ... && BUILD_CDDA */ diff --git a/ascd/libworkman/plat_linux_cdda.c b/ascd/libworkman/plat_linux_cdda.c new file mode 100644 index 0000000..babd4dd --- /dev/null +++ b/ascd/libworkman/plat_linux_cdda.c @@ -0,0 +1,371 @@ +/* + * $Id: plat_linux_cdda.c,v 1.3 1999/03/07 08:36:40 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Linux CDDA functions. Derived from the SUN module. + */ + +#include "include/wm_config.h" + +#if defined(linux) && defined(BUILD_CDDA) /* { */ + +static char plat_linux_cdda_id[] = "$Id: plat_linux_cdda.c,v 1.3 1999/03/07 08:36:40 dirk Exp $"; + + +#include "include/wm_cdda.h" +/* types.h and cdio.h are included by wmcdda.h */ + +#include <stdio.h> +#include <math.h> +#include <sys/ioctl.h> +#include <malloc.h> +#include <errno.h> + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +#define CDDABLKSIZE 2368 +#define SAMPLES_PER_BLK 588 + +/* Address of next block to read. */ +int current_position = 0; + +/* Address of first and last blocks to read. */ +int starting_position = 0; +int ending_position = 0; + +/* Playback direction. */ +int direction = 1; + +/* Number of blocks to read at once; initialize to the maximum. */ +int numblocks = 30; + +/* + * This is the fastest way to convert from BCD to 8-bit. + */ +unsigned char unbcd[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,0,0,0,0,0, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0,0,0,0,0,0, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 0,0,0,0,0,0, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 0,0,0,0,0,0, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0,0,0,0,0,0, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 0,0,0,0,0,0, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 0,0,0,0,0,0, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 0,0,0,0,0,0, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 0,0,0,0,0,0, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +/* + * Initialize the CDDA data buffer and open the appropriate device. + * + * NOTE: We allocate twice as much space as we need to actually read a block; + * this lets us do audio manipulations without bothering to malloc a second + * buffer. + * + * Also, test to see if we can actually *do* CDDA on this drive; if not, we + * need to exit right away so the UI doesn't show the user any CDDA controls. + */ +int +wmcdda_init(char **bufadr, long *buflenadr, int init_fd, char *devname) +{ + struct cdrom_cdda cdda; + + *bufadr = malloc(numblocks * CDDABLKSIZE * 2); + if (*bufadr == NULL) + return (-1); + + *buflenadr = numblocks * CDDABLKSIZE; + + /*init_fd = open(devname, 0); + if (init_fd == -1) + init_fd = open("/dev/rdsk/c0t6d0s2", 0); + */ + init_fd = wmcdda_open(devname); + + if (init_fd > -1) + { + cdda.cdda_addr = 200; + cdda.cdda_length = 1; + cdda.cdda_data = *bufadr; + cdda.cdda_subcode = CDROM_DA_SUBQ; + + if (ioctl(init_fd, CDROMCDDA, &cdda) < 0) + { + close(init_fd); + init_fd = -1; + } + } + + return (init_fd); +} + +/* + * Try to open the CD device + */ +int +wmcdda_open(char *devname) +{ + int fd; + + fd = open(devname, 0); + if (fd == -1) + fd = open("/dev/rdsk/c0t6d0s2", 0); + + return(fd); +} + + +/* + * Close the CD-ROM device in preparation for exiting. + */ +void +wmcdda_close(int fd) +{ + close(fd); +} + +/* + * Set up for playing the CD. Actually this doesn't play a thing, just sets a + * couple variables so we'll know what to do when we're called. + */ +void +wmcdda_setup(int start, int end, int realstart) +{ + current_position = start - 150; + ending_position = end - 150; + starting_position = realstart - 150; + + /* + * Special case: don't start at the "end" of a track if we're + * playing backwards! + */ + if (direction == -1 && start == realstart) + current_position = ending_position - numblocks; +} + +/* + * Read some blocks from the CD. Stop if we hit the end of the current region. + * + * Returns number of bytes read, -1 on error, 0 if stopped for a benign reason. + */ +long +wmcdda_read(int fd, unsigned char *rawbuf, long buflen, + struct cdda_block *block) +{ + struct cdrom_cdda cdda; + int blk; + unsigned char *q; + extern int speed; + + if ((direction > 0 && current_position >= ending_position) || + (direction < 0 && current_position < starting_position)) + { + block->status = WMCDDA_DONE; + return (0); + } + + cdda.cdda_addr = current_position; + if (ending_position && current_position + numblocks > ending_position) + cdda.cdda_length = ending_position - current_position; + else + cdda.cdda_length = numblocks; + cdda.cdda_data = rawbuf; + cdda.cdda_subcode = CDROM_DA_SUBQ; + + if (ioctl(fd, CDROMCDDA, &cdda) < 0) + { + if (errno == ENXIO) /* CD ejected! */ + { + block->status = WMCDDA_EJECTED; + return (-1); + } + + if (current_position + numblocks > ending_position) + { + /* + * Hit the end of the CD, probably. + */ + block->status = WMCDDA_DONE; + return (0); + } + + /* Sometimes it fails once, dunno why */ + if (ioctl(fd, CDROMCDDA, &cdda) < 0) + { + if (ioctl(fd, CDROMCDDA, &cdda) < 0) + { + if (ioctl(fd, CDROMCDDA, &cdda) < 0) + { + perror("CDROMCDDA"); + block->status = WMCDDA_ERROR; + return (-1); + } + } + } + } + + if (speed > 148) + { + /* + * We want speed=148 to advance by cdda_length, but + * speed=256 to advance cdda_length * 4. + */ + current_position = current_position + + (cdda.cdda_length * direction * (speed - 112)) / 36; + } + else + current_position = current_position + + cdda.cdda_length * direction; + + for (blk = 0; blk < numblocks; blk++) + { + /* + * New valid Q-subchannel information? Update the block + * status. + */ + q = &rawbuf[blk * CDDABLKSIZE + SAMPLES_PER_BLK * 4]; + if (*q == 1) + { + block->status = WMCDDA_OK; + block->track = unbcd[q[1]]; + block->index = unbcd[q[2]]; + block->minute = unbcd[q[7]]; + block->second = unbcd[q[8]]; + block->frame = unbcd[q[9]]; + } + } + + return (cdda.cdda_length * CDDABLKSIZE); +} + +/* + * Normalize a bunch of CDDA data. Basically this means ripping out the + * Q subchannel data and doing byte-swapping, since the CD audio is in + * littleendian format. + * + * Scanning is handled here too. + * + * XXX - do byte swapping on Intel boxes? + */ +long +wmcdda_normalize(unsigned char *rawbuf, long buflen, struct cdda_block *block) +{ + int i, nextq; + int blocks = buflen / CDDABLKSIZE; + unsigned char *dest = rawbuf; + unsigned char tmp; + long *buf32 = (long *)rawbuf, tmp32; + +/* + * this was #ifndef LITTLEENDIAN + * in wmcdda it was called LITTLE_ENDIAN. Was this a flaw? + */ +#if WM_BIG_ENDIAN + if (blocks--) + for (i = 0; i < SAMPLES_PER_BLK * 2; i++) + { + /* Only need to use temp buffer on first block. */ + tmp = *rawbuf++; + *dest++ = *rawbuf++; + *dest++ = tmp; + } +#endif + + while (blocks--) + { + /* Skip over Q data. */ + rawbuf += 16; + + for (i = 0; i < SAMPLES_PER_BLK * 2; i++) + { +#if WM_LITTLE_ENDIAN + *dest++ = *rawbuf++; + *dest++ = *rawbuf++; +#else + *dest++ = rawbuf[1]; + *dest++ = rawbuf[0]; + rawbuf += 2; +#endif + } + } + + buflen -= ((buflen / CDDABLKSIZE) * 16); + + /* + * Reverse the data here if we're playing backwards. + * XXX - ideally this should be done above. + */ + if (direction < 0) + { + buflen /= 4; /* we can move 32 bits at a time. */ + + for (i = 0; i < buflen / 2; i++) + { + tmp32 = buf32[i]; + buf32[i] = buf32[buflen - i - 1]; + buf32[buflen - i - 1] = tmp32; + } + + buflen *= 4; + } + + return (buflen); +} + +/* + * Set the playback direction. + */ +void +wmcdda_direction(int newdir) +{ + if (newdir == 0) + { + numblocks = 20; + direction = 1; + } + else + { + numblocks = 30; + direction = -1; + } +} + +/* + * Do system-specific stuff to get ready to play at a particular speed. + */ +void +wmcdda_speed(int speed) +{ + if (speed > 128) + numblocks = 12; + else + numblocks = direction > 0 ? 20 : 30; +} + +#endif /* } */ diff --git a/ascd/libworkman/plat_news.c b/ascd/libworkman/plat_news.c new file mode 100644 index 0000000..6736cd7 --- /dev/null +++ b/ascd/libworkman/plat_news.c @@ -0,0 +1,474 @@ +/* + * $Id: plat_news.c,v 1.7 1999/03/07 08:36:40 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Sony NEWS-specific drive control routines. + */ + +#if defined( __sony_news) || defined(sony_news) + +static char plat_news_id[] = "$Id: plat_news.c,v 1.7 1999/03/07 08:36:40 dirk Exp $"; + +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <ustat.h> +#include <CD.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include "include/wm_config.h" +#include "include/wm_struct.h" + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +void *malloc(); +char *strchr(); + +extern char *cd_device; +extern int intermittent_dev; + +int min_volume = 128; +int max_volume = 255; + +/* + * Initialize the drive. A no-op for the generic driver. + */ +int +gen_init(d) + struct wm_drive *d; +{ + return (0); +} + +/* + * Get the number of tracks on the CD. + */ +int +gen_get_trackcount(d, tracks) + struct wm_drive *d; + int *tracks; +{ + struct CD_Capacity cc; + + if (CD_GetCapacity(d->fd, &cc)) + return (-1); + + *tracks = cc.etrack - 1; + return (0); +} + +/* + * Get the start time and mode (data or audio) of a track. + */ +int +gen_get_trackinfo(d, track, data, startframe) + struct wm_drive *d; + int track, *data, *startframe; +{ + struct CD_TOCinfo hdr; + struct CD_TOCdata ent; + + hdr.strack = track; + hdr.ntrack = 1; + hdr.data = &ent; + if (CD_ReadTOC(d->fd, &hdr)) + return (-1); + + *data = (ent.control & 4) ? 1 : 0; + *startframe = ent.baddr; + + return (0); +} + +/* + * Get the number of frames on the CD. + */ +int +gen_get_cdlen(d, frames) + struct wm_drive *d; + int *frames; +{ + int tmp; + + if ((d->get_trackcount)(d, &tmp)) + return (-1); + + return (gen_get_trackinfo(d, tmp + 1, &tmp, frames)); +} + +/* + * Get the current status of the drive: the current play mode, the absolute + * position from start of disc (in frames), and the current track and index + * numbers if the CD is playing or paused. + */ +int +gen_get_drive_status(d, oldmode, mode, pos, track, index) + struct wm_drive *d; + enum wm_cd_modes oldmode, *mode; + int *pos, *track, *index; +{ + struct CD_Status sc; + + /* If we can't get status, the CD is ejected, so default to that. */ + *mode = WM_CDM_EJECTED; + + /* Is the device open? */ + if (d->fd < 0) + { + switch (wmcd_open(d)) { + case -1: /* error */ + return (-1); + + case 1: /* retry */ + return (0); + } + } + + /* Disc is ejected. Close the device. */ + if (CD_GetStatus(d->fd, &sc)) + { + WMclose(d->fd); + d->fd = -1; + return (0); + } + + switch (sc.status) { + case CDSTAT_PLAY: + *mode = PLAYING; + *track = sc.tno; + *index = sc.index; + *pos = sc.baddr; + break; + + case CDSTAT_PAUSE: + if (oldmode == WM_CDM_PLAYING || oldmode == WM_CDM_PAUSED) + { + *mode = WM_CDM_PAUSED; + *track = sc.tno; + *index = sc.index; + *pos = sc.baddr; + } + else + *mode = WM_CDM_STOPPED; + break; + + case CDSTAT_STOP: + if (oldmode == WM_CDM_PLAYING) + { + *mode = WM_CDM_TRACK_DONE; /* waiting for next track. */ + break; + } + /* fall through */ + + default: + *mode = WM_CDM_STOPPED; + break; + } + + return (0); +} + +/* + * Set the volume level for the left and right channels. Their values + * range from 0 to 100. + */ +int +gen_set_volume(d, left, right) + struct wm_drive *d; + int left, right; +{ + /* NEWS can't adjust volume! */ + return (0); +} + +/* + * Pause the CD. + */ +int +gen_pause(d) + struct wm_drive *d; +{ + CD_Pause(d->fd); + + return (0); +} + +/* + * Resume playing the CD (assuming it was paused.) + */ +int +gen_resume(d) + struct wm_drive *d; +{ + CD_Restart(d->fd); + + return (0); +} + +/* + * Stop the CD. + */ +int +gen_stop(d) + struct wm_drive *d; +{ + CD_Stop(d->fd); + + return (0); +} + +/* + * Play the CD from one position to another (both in frames.) + */ +int +gen_play(d, start, end) + struct wm_drive *d; + int start, end; +{ + struct CD_PlayAddr msf; + + msf.addrmode = CD_MSF; + msf.addr.msf.startmsf.min = start / (60*75); + msf.addr.msf.startmsf.sec = (start % (60*75)) / 75; + msf.addr.msf.startmsf.frame = start % 75; + msf.addr.msf.endmsf.min = end / (60*75); + msf.addr.msf.endmsf.sec = (end % (60*75)) / 75; + msf.addr.msf.endmsf.frame = end % 75; + + if (CD_Play(d->fd, &msf)) + { + printf("wm_cd_play_chunk(%d,%d)\n",start,end); + printf("msf = %d:%d:%d %d:%d:%d\n", + msf.addr.msf.startmsf.min, + msf.addr.msf.startmsf.sec, + msf.addr.msf.startmsf.frame, + msf.addr.msf.endmsf.min, + msf.addr.msf.endmsf.sec, + msf.addr.msf.endmsf.frame); + perror("CD_Play"); + return (-1); + } + + return (0); +} + +/* + * Eject the current CD, if there is one. + */ +int +gen_eject(d) + struct wm_drive *d; +{ + struct stat stbuf; + struct ustat ust; + + if (fstat(d->fd, &stbuf) != 0) + return (-2); + + /* Is this a mounted filesystem? */ + if (ustat(stbuf.st_rdev, &ust) == 0) + return (-3); + + if (CD_AutoEject(d->fd)) + return (-1); + + /* Close the device if it needs to vanish. */ + if (intermittent_dev) + { + WMclose(d->fd); + d->fd = -1; + } + + return (0); +} + +/*----------------------------------------* + * Close the CD tray + * + * Please edit and send changes to + * [email protected] + *----------------------------------------*/ + +int gen_closetray(struct wm_drive *d) +{ +#ifdef CAN_CLOSE + if(!close(d->fd)) + { + d->fd=-1; + return(wmcd_reopen(d)); + } else { + return(-1); + } +#else + /* Always succeed if the drive can't close */ + return(0); +#endif /* CAN_CLOSE */ +} /* gen_closetray() */ + + +/* + * Close the CD device. + */ +int +WMclose(fd) + int fd; +{ + int ret; + + ret = CD_Close(fd); + wm_susleep(3000000); + return (ret); +} + +/* + * Read the initial volume from the drive, if available. Each channel + * ranges from 0 to 100, with -1 indicating data not available. + */ +int +gen_get_volume(d, left, right) + struct wm_drive *d; + int *left, *right; +{ + /* Suns, HPs, Linux, NEWS can't read the volume; oh well */ + *left = *right = -1; + + return (0); +} + +/* + * Pass SCSI commands to the device. + */ +int +wm_scsi(d, cdb, cdblen, buf, buflen, getreply) + struct wm_drive *d; + unsigned char *cdb, *buf; + int cdblen, buflen, getreply; +{ + /* NEWS can't do SCSI passthrough... or can it? */ + return (-1); +} + +/* + * Open the CD device and figure out what kind of drive is attached. + */ +int +wmcd_open(d) + struct wm_drive *d; +{ + int fd; + static int warned = 0; + char vendor[32] = WM_STR_GENVENDOR; + char model[32] = WM_STR_GENMODEL; + char rev[32] = WM_STR_GENREV; + + if (d->fd >= 0) /* Device already open? */ + return (0); + + intermittent_dev = 1; + if (cd_device == NULL) + cd_device = DEFAULT_CD_DEVICE; + + if ((d->fd = CD_Open(cd_device, 0)) < 0) + { + /* Solaris 2.2 volume manager moves links around */ + if (errno == ENOENT && intermittent_dev) + return (0); + + if (errno == EACCES) + { + if (!warned) + { + char realname[MAXPATHLEN]; + + if (realpath(cd_device, realname) == NULL) + { + perror("realpath"); + return (0); + } + + fprintf(stderr, + "As root, please run\n\nchmod 666 %s\n\n%s\n", realname, + "to give yourself permission to access the CD-ROM device."); + warned++; + } + } + else if (errno != EIO) /* defined at top */ + { + perror(cd_device); + exit(1); + } + + /* No CD in drive. */ + return (1); + } + + if (warned) + { + warned = 0; + fprintf(stderr, "Thank you.\n"); + } + + /* Now fill in the relevant parts of the wm_drive structure. */ + fd = d->fd; + + /* Figure out the drive type, if possible */ + wm_scsi_get_drive_type(d, vendor, model, rev); + *d = *(find_drive_struct(vendor, model, rev)); + wm_drive_settype(vendor, model, rev); + + d->fd = fd; + + (d->init)(d); + + return (0); +} /* wmcd_open() */ + +/* + * Re-Open the device if it is open. + */ +int +wmcd_reopen( struct wm_drive *d ) +{ + int status; + + do { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "wmcd_reopen "); + if (d->fd >= 0) /* Device really open? */ + { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "closes the device and "); + status = close( d->fd ); /* close it! */ + /* we know, that the file is closed, do we? */ + d->fd = -1; + } + wm_susleep( 1000 ); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "calls wmcd_open()\n"); + status = wmcd_open( d ); /* open it as usual */ + wm_susleep( 1000 ); + } while ( status != 0 ); + return status; +} /* wmcd_reopen() */ + +#endif diff --git a/ascd/libworkman/plat_openbsd.c b/ascd/libworkman/plat_openbsd.c new file mode 100644 index 0000000..bdc2fb7 --- /dev/null +++ b/ascd/libworkman/plat_openbsd.c @@ -0,0 +1,530 @@ +/* + * $Id: plat_openbsd.c,v 1.7 1999/03/07 08:36:40 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * OpenBSD-specific drive control routines. (Based on plat_freebsd.c) + * + * Michael Shalayeff, 7/24/96 + * Todd Pfaff, 3/20/94 + * + */ + +#if defined(__OpenBSD__) + +static char plat_openbsd[] = "$Id: plat_openbsd.c,v 1.7 1999/03/07 08:36:40 dirk Exp $"; + +#include <errno.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/mount.h> +#include <fcntl.h> +#include <sys/param.h> +#include <sys/stat.h> + +#include "include/wm_config.h" + +/* this is for glibc 2.x which defines the ust structure in ustat.h not stat.h */ +#ifdef __GLIBC__ +#include <sys/ustat.h> +#endif + +#include <sys/time.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/cdio.h> +#include <sys/scsiio.h> +#include <scsi/scsi_all.h> +#include <scsi/scsi_cd.h> + +#include "include/wm_struct.h" + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +void *malloc(); + +int min_volume = 10; +int max_volume = 255; + +extern char *cd_device; + + +/* + * Initialize the drive. A no-op for the generic driver. + */ +int +gen_init(struct wm_drive *d) +{ + return (0); +} + +/* + * Get the number of tracks on the CD. + */ +int +gen_get_trackcount(struct wm_drive *d, int tracks) +{ + struct ioc_toc_header hdr; + + if (ioctl(d->fd, CDIOREADTOCHEADER, &hdr) == -1) + return (-1); + + *tracks = hdr.ending_track - hdr.starting_track + 1; + + return (0); +} + +/* + * Get the start time and mode (data or audio) of a track. + * + * XXX - this should get cached, but that means keeping track of ejects. + */ +int +gen_get_trackinfo(struct wm_drive *d, int track, int *data, int *startframe) +{ + struct ioc_read_toc_entry toc; + struct cd_toc_entry toc_buffer; + + bzero((char *)&toc_buffer, sizeof(toc_buffer)); + toc.address_format = CD_MSF_FORMAT; + toc.starting_track = track; + toc.data_len = sizeof(toc_buffer); + toc.data = &toc_buffer; + + if (ioctl(d->fd, CDIOREADTOCENTRYS, &toc)) + return (-1); + + *data = ((toc_buffer.control & 0x4) != 0); + + *startframe = toc_buffer.addr.msf.minute*60*75 + + toc_buffer.addr.msf.second * 75 + + toc_buffer.addr.msf.frame; + + return (0); +} + +/* + * Get the number of frames on the CD. + */ +int +gen_get_cdlen(struct wm_drive *d, int *frames) +{ + int tmp; + struct ioc_toc_header hdr; + int status; + +#define LEADOUT 0xaa /* see scsi.c. what a hack! */ + return gen_get_trackinfo(d, LEADOUT, &tmp, frames); +} + +/* + * Get the current status of the drive: the current play mode, the absolute + * position from start of disc (in frames), and the current track and index + * numbers if the CD is playing or paused. + */ +int +gen_get_drive_status(struct wm_drive *d, enum wm_cd_modes oldmode, + enum wm_cd_modes *mode, int *pos, int *track, int *index) +{ + struct ioc_read_subchannel sc; + struct cd_sub_channel_info scd; + + /* If we can't get status, the CD is ejected, so default to that. */ + *mode = WM_CDM_EJECTED; + + sc.address_format = CD_MSF_FORMAT; + sc.data_format = CD_CURRENT_POSITION; + sc.track = 0; + sc.data_len = sizeof(scd); + sc.data = (struct cd_sub_channel_info *)&scd; + + /* Is the device open? */ + if (d->fd < 0) + { + switch (wmcd_open(d)) { + case -1: /* error */ + return (-1); + + case 1: /* retry */ + return (0); + } + } + + if (ioctl(d->fd, CDIOCREADSUBCHANNEL, &sc)) { + /* we need to release the device so the kernel will notice + reloaded media */ + (void) close(d->fd); + d->fd = -1; + return (0); /* ejected */ + } + + switch (scd.header.audio_status) { + case CD_AS_PLAY_IN_PROGRESS: + *mode = WM_CDM_PLAYING; +dopos: + *pos = scd.what.position.absaddr.msf.minute * 60 * 75 + + scd.what.position.absaddr.msf.second * 75 + + scd.what.position.absaddr.msf.frame; + *track = scd.what.position.track_number; + *index = scd.what.position.index_number; + break; + + case CD_AS_PLAY_PAUSED: + if (oldmode == WM_CDM_PLAYING || oldmode == WM_CDM_PAUSED) + { + *mode = WM_CDM_PAUSED; + goto dopos; + } + else + *mode = WM_CDM_STOPPED; + break; + + case CD_AS_PLAY_COMPLETED: + *mode = WM_CDM_TRACK_DONE; /* waiting for next track. */ + break; + + case CD_AS_NO_STATUS: + case 0: + *mode = WM_CDM_STOPPED; + break; + } + + return (0); +} + +/* + * scale_volume(vol, max) + * + * Return a volume value suitable for passing to the CD-ROM drive. "vol" + * is a volume slider setting; "max" is the slider's maximum value. + * + * On Sun and DEC CD-ROM drives, the amount of sound coming out the jack + * increases much faster toward the top end of the volume scale than it + * does at the bottom. To make up for this, we make the volume scale look + * sort of logarithmic (actually an upside-down inverse square curve) so + * that the volume value passed to the drive changes less and less as you + * approach the maximum slider setting. The actual formula looks like + * + * (max^2 - (max - vol)^2) * (max_volume - min_volume) + * v = --------------------------------------------------- + min_volume + * max^2 + * + * If your system's volume settings aren't broken in this way, something + * like the following should work: + * + * return ((vol * (max_volume - min_volume)) / max + min_volume); + */ +static int +scale_volume(int vol, int max) +{ + return ((vol * (max_volume - min_volume)) / max + min_volume); +} + +/* + * Set the volume level for the left and right channels. Their values + * range from 0 to 100. + */ +int +gen_set_volume(struct wm_drive *d, int left, int right) +{ + struct ioc_vol vol; + + if (left < 0) /* don't laugh, I saw this happen once! */ + left = 0; + if (right < 0) + right = 0; + left = scale_volume(left, 100); + right = scale_volume(right, 100); + + bzero((char *)&vol, sizeof(vol)); + + vol.vol[LEFT_PORT] = left; + vol.vol[RIGHT_PORT] = right; + + if (ioctl(d->fd, CDIOCSETVOL, &vol)) + return (-1); + + return (0); +} + +/* + * Pause the CD. + */ +int +gen_pause(struct wm_drive *d) +{ + return (ioctl(d->fd, CDIOCPAUSE)); +} + +/* + * Resume playing the CD (assuming it was paused.) + */ +int +gen_resume(struct wm_drive *d) +{ + return (ioctl(d->fd, CDIOCRESUME)); +} + +/* + * Stop the CD. + */ +int +gen_stop(struct wm_drive *d) +{ + return (ioctl(d->fd, CDIOCSTOP)); +} + +/* + * Play the CD from one position to another (both in frames.) + */ +int +gen_play(struct wm_drive *d, int start, int end) +{ + struct ioc_play_msf msf; + + msf.start_m = start / (60*75); + msf.start_s = (start % (60*75)) / 75; + msf.start_f = start % 75; + msf.end_m = end / (60*75); + msf.end_s = (end % (60*75)) / 75; + msf.end_f = end % 75; + + if (ioctl(d->fd, CDIOCSTART)) + return (-1); + + if (ioctl(d->fd, CDIOCPLAYMSF, &msf)) + return (-2); + + return (0); +} + +/* + * Eject the current CD, if there is one. + */ +int +gen_eject(struct wm_drive *d) +{ + /* On some systems, we can check to see if the CD is mounted. */ + struct stat stbuf; + struct statfs buf; + int rval; + + if (fstat(d->fd, &stbuf) != 0) + return (-2); + + /* Is this a mounted filesystem? */ + if (fstatfs(stbuf.st_rdev, &buf) == 0) + return (-3); + + rval = ioctl(d->fd, CDIOCALLOW); + if (rval == 0) + rval = ioctl(d->fd, CDIOCEJECT); + if (rval == 0) + rval = ioctl(d->fd, CDIOCPREVENT); + if (rval == 0) + rval = close(d->fd); + if (rval == 0) + d->fd = -1; + return rval; +} /* gen_eject() */ + + +/*----------------------------------------* + * Close the CD tray + * + * Please edit and send changes to + * [email protected] + *----------------------------------------*/ + +int gen_closetray(struct wm_drive *d) +{ +#ifdef CAN_CLOSE + if(!close(d->fd)) + { + d->fd=-1; + return(wmcd_reopen(d)); + } else { + return(-1); + } +#else + /* Always succeed if the drive can't close */ + return(0); +#endif /* CAN_CLOSE */ +} /* gen_closetray() */ + + +/* + * unscale_volume(cd_vol, max) + * + * Given a value between min_volume and max_volume, return the volume slider + * value needed to achieve that value. + * + * Rather than perform floating-point calculations to reverse the above + * formula, we simply do a binary search of scale_volume()'s return values. + */ +static int +unscale_volume(int cd_vol, int max) +{ + int vol = 0, top = max, bot = 0, scaled; + + while (bot <= top) + { + vol = (top + bot) / 2; + scaled = scale_volume(vol, max); + if (cd_vol == scaled) + break; + if (cd_vol < scaled) + top = vol - 1; + else + bot = vol + 1; + } + + if (vol < 0) + vol = 0; + else if (vol > max) + vol = max; + + return (vol); +} + +/* + * Read the initial volume from the drive, if available. Each channel + * ranges from 0 to 100, with -1 indicating data not available. + */ +int +gen_get_volume(struct wm_drive *d, int *left, int *right) +{ + struct ioc_vol vol; + + if (d->fd >= 0) + { + bzero((char *)&vol, sizeof(vol)); + + if (ioctl(d->fd, CDIOCGETVOL, &vol)) + *left = *right = -1; + else + { + *left = unscale_volume(vol.vol[LEFT_PORT], 100); + *right = unscale_volume(vol.vol[RIGHT_PORT], 100); + } + } + else + *left = *right = -1; + + return (0); +} + + +/* + * Send an arbitrary SCSI command to a device. + * + */ +int +wm_scsi(struct wm_drive *d, unsigned char *cdb, + int cdblen, void *retbuf, int retbuflen, int getreply) +{ + return (-1); +} + + +/* + * Open the CD device and figure out what kind of drive is attached. + */ +int +wmcd_open(struct wm_drive *d) +{ + int fd; + static int warned = 0; + char vendor[32] = WM_STR_GENVENDOR; + char model[32] = WM_STR_GENMODEL; + char rev[32] = WM_STR_GENREV; + + if (d->fd >= 0) /* Device already open? */ + return (0); + + if (cd_device == NULL) + cd_device = DEFAULT_CD_DEVICE; + + d->fd = open(cd_device, 0); + if (d->fd < 0) + { + if (errno == EACCES) + { + if (!warned) + { + fprintf(stderr, + "As root, please run\n\nchmod 666 %s\n\n%s\n", cd_device, + "to give yourself permission to access the CD-ROM device."); + warned++; + } + } + + /* No CD in drive. */ + return (1); + } + + if (warned) + { + warned = 0; + fprintf(stderr, "Thank you.\n"); + } + + /* Now fill in the relevant parts of the wm_drive structure. */ + fd = d->fd; + + *d = *(find_drive_struct(vendor, model, rev)); + wm_drive_settype(vendor, model, rev); + + (d->init)(d); + + d->fd = fd; + + return (0); +} /* wmcd_open() */ + +/* + * Re-Open the device if it is open. + */ +int +wmcd_reopen( struct wm_drive *d ) +{ + int status; + + do { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "wmcd_reopen "); + if (d->fd >= 0) /* Device really open? */ + { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "closes the device and "); + status = close( d->fd ); /* close it! */ + /* we know, that the file is closed, do we? */ + d->fd = -1; + } + wm_susleep( 1000 ); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "calls wmcd_open()\n"); + status = wmcd_open( d ); /* open it as usual */ + wm_susleep( 1000 ); + } while ( status != 0 ); + return status; +} /* wmcd_reopen() */ + +#endif diff --git a/ascd/libworkman/plat_osf1.c b/ascd/libworkman/plat_osf1.c new file mode 100644 index 0000000..538871c --- /dev/null +++ b/ascd/libworkman/plat_osf1.c @@ -0,0 +1,666 @@ +/* + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * OSF drive control routines. + */ + +#if defined(__osf__) + +static char plat_osf_id[] = "$Id: plat_osf1.c,v 1.6 1999/03/07 08:36:40 dirk Exp $"; + +#include <errno.h> +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <ustat.h> +#include <string.h> +/* #include <sys/rzdisk.h> +#include <sys/cdrom.h> */ +#include <io/cam/rzdisk.h> +#include <io/cam/cdrom.h> + +#include "include/wm_config.h" +#include "include/wm_struct.h" + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +/* + * This structure will be filled with the TOC header and all entries. + * Ultrix doesn't seem to allow getting single TOC entries. + * - Chris Ross ([email protected]) + */ +struct cd_toc_header_and_entries { + struct cd_toc_header cdth; + struct cd_toc_entry cdte[CDROM_MAX_TRACK+1]; +}; + +void *malloc(); +char *strchr(); + +int min_volume = 128; +int max_volume = 255; + +extern char *cd_device; + +/* + * find_cdrom + * + * Determine the name of the CD-ROM device. + * + * Read through the boot records (via a call to uerf) and find the SCSI + * address of the CD-ROM. + */ +void +find_cdrom() +{ + char *data, *fgetline(); + FILE *uerf; + int fds[2]; + int pid; + extern char *getenv(); + + pipe(fds); + + cd_device = getenv("CDROM"); + if (cd_device != NULL) + return; + if ((pid = fork()) == 0) { + close(fds[0]); + dup2(fds[1], 1); + execl("/etc/uerf", "uerf", "-R", "-r", "300", NULL); + execl("/usr/sbin/uerf", "uerf", "-R", "-r", "300", NULL); + _exit(1); + } else if (pid < 0) { + perror("fork"); + exit(1); + } + + close(fds[1]); + uerf = fdopen(fds[0], "r"); + + while (data = fgetline(uerf)) + if (strstr(data, "RRD42")) { + char *device; + + cd_device = (char *)malloc(sizeof("/dev/rrz##c")); + strcpy(cd_device, "/dev/r"); + device = strstr(data, "rz"); + device[(int)(strchr(device, ' ') - device)] = '\0'; + strcat(cd_device, device); + strcat(cd_device, "c"); + break; + } + + fclose(uerf); + + if (cd_device == NULL) { + fprintf(stderr, + "No cdrom (RRD42) is installed on this system\n"); + exit(1); + } + + kill(pid, 15); + (void)wait((int *)NULL); +} + +/* + * Initialize the drive. A no-op for the generic driver. + */ +int +gen_init(d) + struct wm_drive *d; +{ + return (0); +} + +/* + * Get the number of tracks on the CD. + */ +int +gen_get_trackcount(d, tracks) + struct wm_drive *d; + int *tracks; +{ + struct cd_toc_header hdr; + + if (ioctl(d->fd, CDROM_TOC_HEADER, &hdr)) + return (-1); + + *tracks = hdr.th_ending_track; + + return (0); +} + +/* + * Get the start time and mode (data or audio) of a track. + * + * XXX - this should get cached, but that means keeping track of ejects. + */ +int +gen_get_trackinfo(d, track, data, startframe) + struct wm_drive *d; + int track, *data, *startframe; +{ + struct cd_toc toc; + struct cd_toc_header hdr; + struct cd_toc_header_and_entries toc_buffer; + + if (ioctl(d->fd, CDROM_TOC_HEADER, &hdr)) + return (-1); + + bzero((char *)&toc_buffer, sizeof(toc_buffer)); + toc.toc_address_format = CDROM_MSF_FORMAT; + toc.toc_starting_track = 0; + toc.toc_alloc_length = (u_short)(((hdr.th_data_len1 << 8) + + hdr.th_data_len0) & 0xfff) + 2; + toc.toc_buffer = (caddr_t)&toc_buffer; + + if (ioctl(d->fd, CDROM_TOC_ENTRYS, &toc)) + return (-1); + + if (track == 0) + track = hdr.th_ending_track + 1; + + *data = (toc_buffer.cdte[track-1].te_control & CDROM_DATA_TRACK) ? 1:0; + *startframe = toc_buffer.cdte[track - 1].te_absaddr.msf.m_units*60*75 + + toc_buffer.cdte[track - 1].te_absaddr.msf.s_units * 75 + + toc_buffer.cdte[track - 1].te_absaddr.msf.f_units; + + return (0); +} + +/* + * Get the number of frames on the CD. + */ +int +gen_get_cdlen(d, frames) + struct wm_drive *d; + int *frames; +{ + int tmp; + + return (gen_get_trackinfo(d, 0, &tmp, frames)); +} + +/* + * Get the current status of the drive: the current play mode, the absolute + * position from start of disc (in frames), and the current track and index + * numbers if the CD is playing or paused. + */ +int +gen_get_drive_status(d, oldmode, mode, pos, track, index) + struct wm_drive *d; + enum wm_cd_modes oldmode, *mode; + int *pos, *track, *index; +{ + struct cd_sub_channel sc; + struct cd_subc_channel_data scd; + + /* If we can't get status, the CD is ejected, so default to that. */ + *mode = WM_CDM_EJECTED; + + sc.sch_address_format = CDROM_MSF_FORMAT; + sc.sch_data_format = CDROM_CURRENT_POSITION; + sc.sch_track_number = 0; + sc.sch_alloc_length = sizeof(scd); + sc.sch_buffer = (caddr_t)&scd; + + /* Is the device open? */ + if (d->fd < 0) + { + switch (wmcd_open(d)) { + case -1: /* error */ + return (-1); + + case 1: /* retry */ + return (0); + } + } + + if (ioctl(d->fd, CDROM_READ_SUBCHANNEL, &sc)) + return (0); /* ejected */ + + switch (scd.scd_header.sh_audio_status) { + case AS_PLAY_IN_PROGRESS: + *mode = WM_CDM_PLAYING; +dopos: + *pos = scd.scd_position_data.scp_absaddr.msf.m_units * 60 * 75 + + scd.scd_position_data.scp_absaddr.msf.s_units * 75 + + scd.scd_position_data.scp_absaddr.msf.f_units; + *track = scd.scd_position_data.scp_track_number; + *index = scd.scd_position_data.scp_index_number; + break; + + case AS_PLAY_PAUSED: + if (oldmode == WM_CDM_PLAYING || oldmode == WM_CDM_PAUSED) + { + *mode = WM_CDM_PAUSED; + goto dopos; + } + else + *mode = WM_CDM_STOPPED; + break; + + case AS_PLAY_COMPLETED: + *mode = WM_CDM_TRACK_DONE; /* waiting for next track. */ + break; + + case AS_NO_STATUS: + *mode = WM_CDM_STOPPED; + break; + default: + abort(); + } + + return (0); +} + +/* + * scale_volume(vol, max) + * + * Return a volume value suitable for passing to the CD-ROM drive. "vol" + * is a volume slider setting; "max" is the slider's maximum value. + * + * On Sun and DEC CD-ROM drives, the amount of sound coming out the jack + * increases much faster toward the top end of the volume scale than it + * does at the bottom. To make up for this, we make the volume scale look + * sort of logarithmic (actually an upside-down inverse square curve) so + * that the volume value passed to the drive changes less and less as you + * approach the maximum slider setting. The actual formula looks like + * + * (max^2 - (max - vol)^2) * (max_volume - min_volume) + * v = --------------------------------------------------- + min_volume + * max^2 + * + * If your system's volume settings aren't broken in this way, something + * like the following should work: + * + * return ((vol * (max_volume - min_volume)) / max + min_volume); + */ +scale_volume(vol, max) + int vol, max; +{ + return ((max * max - (max - vol) * (max - vol)) * + (max_volume - min_volume) / (max * max) + min_volume); +} + +/* + * Set the volume level for the left and right channels. Their values + * range from 0 to 100. + */ +int +gen_set_volume(d, left, right) + struct wm_drive *d; + int left, right; +{ + struct cd_playback pb; + struct cd_playback_status ps; + struct cd_playback_control pc; + + left = scale_volume(left, 100); + right = scale_volume(right, 100); + + bzero((char *)&pb, sizeof(pb)); + bzero((char *)&ps, sizeof(ps)); + bzero((char *)&pc, sizeof(pc)); + + pb.pb_alloc_length = sizeof(ps); + pb.pb_buffer = (caddr_t)&ps; + + if (ioctl(d->fd, CDROM_PLAYBACK_STATUS, &pb)) + return (-1); + + pc.pc_chan0_select = ps.ps_chan0_select; + pc.pc_chan0_volume = (left < CDROM_MIN_VOLUME) ? + CDROM_MIN_VOLUME : (left > CDROM_MAX_VOLUME) ? + CDROM_MAX_VOLUME : left; + pc.pc_chan1_select = ps.ps_chan1_select; + pc.pc_chan1_volume = (right < CDROM_MIN_VOLUME) ? + CDROM_MIN_VOLUME : (right > CDROM_MAX_VOLUME) ? + CDROM_MAX_VOLUME : right; + + pb.pb_alloc_length = sizeof(pc); + pb.pb_buffer = (caddr_t)&pc; + + if (ioctl(d->fd, CDROM_PLAYBACK_CONTROL, &pb)) + return (-1); + + return (0); +} + +/* + * Pause the CD. + */ +int +gen_pause(d) + struct wm_drive *d; +{ + return (ioctl(d->fd, CDROM_PAUSE_PLAY, 0)); +} + +/* + * Resume playing the CD (assuming it was paused.) + */ +int +gen_resume(d) + struct wm_drive *d; +{ + return (ioctl(d->fd, CDROM_RESUME_PLAY, 0)); +} + +/* + * Stop the CD. + */ +int +gen_stop(d) + struct wm_drive *d; +{ + return (ioctl(d->fd, SCSI_STOP_UNIT, 0)); +} + +/* + * Play the CD from one position to another (both in frames.) + */ +int +gen_play(d, start, end) + struct wm_drive *d; + int start, end; +{ + struct cd_play_audio_msf msf; + + msf.msf_starting_M_unit = start / (60*75); + msf.msf_starting_S_unit = (start % (60*75)) / 75; + msf.msf_starting_F_unit = start % 75; + msf.msf_ending_M_unit = end / (60*75); + msf.msf_ending_S_unit = (end % (60*75)) / 75; + msf.msf_ending_F_unit = end % 75; + + if (ioctl(d->fd, SCSI_START_UNIT)) + return (-1); + if (ioctl(d->fd, CDROM_PLAY_AUDIO_MSF, &msf)) + return (-2); + + return (0); +} + +/* + * Eject the current CD, if there is one. + */ +int +gen_eject(struct wm_drive *d) +{ + /* On some systems, we can check to see if the CD is mounted. */ + struct stat stbuf; + struct ustat ust; + + if (fstat(d->fd, &stbuf) != 0) + return (-2); + + /* Is this a mounted filesystem? */ + if (ustat(stbuf.st_rdev, &ust) == 0) + return (-3); + + return (ioctl(d->fd, CDROM_EJECT_CADDY, 0)); +} /* gen_eject() */ + +/*----------------------------------------* + * Close the CD tray + * + * Please edit and send changes to + * [email protected] + *----------------------------------------*/ + +int gen_closetray(struct wm_drive *d) +{ +#ifdef CAN_CLOSE + if(!close(d->fd)) + { + d->fd=-1; + return(wmcd_reopen(d)); + } else { + return(-1); + } +#else + /* Always succeed if the drive can't close */ + return(0); +#endif /* CAN_CLOSE */ +} /* gen_closetray() */ + + +/* + * unscale_volume(cd_vol, max) + * + * Given a value between min_volume and max_volume, return the volume slider + * value needed to achieve that value. + * + * Rather than perform floating-point calculations to reverse the above + * formula, we simply do a binary search of scale_volume()'s return values. + */ +static int +unscale_volume(cd_vol, max) + int cd_vol, max; +{ + int vol = 0, top = max, bot = 0, scaled; + + while (bot <= top) + { + vol = (top + bot) / 2; + scaled = scale_volume(vol, max); + if (cd_vol == scaled) + break; + if (cd_vol < scaled) + top = vol - 1; + else + bot = vol + 1; + } + + if (vol < 0) + vol = 0; + else if (vol > max) + vol = max; + + return (vol); +} + +/* + * Read the initial volume from the drive, if available. Each channel + * ranges from 0 to 100, with -1 indicating data not available. + */ +int +gen_get_volume(d, left, right) + struct wm_drive *d; + int *left, *right; +{ + struct cd_playback pb; + struct cd_playback_status ps; + + bzero((char *)&pb, sizeof(pb)); + bzero((char *)&ps, sizeof(ps)); + + pb.pb_alloc_length = sizeof(ps); + pb.pb_buffer = (caddr_t)&ps; + + if (d->fd >= 0) + { + if (ioctl(d->fd, CDROM_PLAYBACK_STATUS, &pb)) + *left = *right = -1; + else + { + *left = unscale_volume(ps.ps_chan0_volume, 100); + *right = unscale_volume(ps.ps_chan1_volume, 100); + } + } + else + *left = *right = -1; + + return (0); +} + +/* + * Send an arbitrary SCSI command to a device. + */ +int +wm_scsi(d, cdb, cdblen, retbuf, retbuflen, getreply) + struct wm_drive *d; + unsigned char *cdb; + int cdblen; + void *retbuf; + int retbuflen; + int getreply; +{ + /* ULTRIX doesn't have a SCSI passthrough interface, does it? */ + return (-1); +} + +/* + * fgetline() + * + * Simulate fgets, but joining continued lines in the output of uerf. + */ + +#define BUF_SIZE 85 /* Max length of a (real) line */ + +char * +fgetline(fp) +FILE *fp; +{ + static char *retval = NULL; + static char holdbuf[BUF_SIZE + 1]; + char tmp[BUF_SIZE + 1]; + char *stmp; + + if (!retval) { + retval = malloc(BUF_SIZE * 3); /* 3 lines can be joined */ + if (!retval) + return(NULL); + else + *retval = '\0'; + } + + if (*holdbuf) { + strcpy(retval, holdbuf); + retval[strlen(retval)-1] = '\0'; + memset(holdbuf, 0, BUF_SIZE+1); + } + while (fgets(tmp, BUF_SIZE, fp)) { + stmp = tmp + strspn(tmp, " \t"); + if (*stmp == '_') { /* Continuation line */ + retval[strlen(retval)-1] = '\0'; /* Trim off C/R */ + strcat(retval, stmp+1); + } else { + if (*retval) { + strcpy(holdbuf, tmp); + holdbuf[strlen(holdbuf)-1] = -1; + return retval; + } else { /* First line read, keep reading */ + strcat(retval, stmp); + retval[strlen(retval)-1] = '\0'; + } + } + } + return NULL; +} + +/* + * Open the CD device and figure out what kind of drive is attached. + */ +int +wmcd_open(d) + struct wm_drive *d; +{ + int fd; + static int warned = 0; + + if (d->fd >= 0) /* Device already open? */ + return (0); + + if (cd_device == NULL) + find_cdrom(); + + d->fd = open(cd_device, O_RDWR); + if (d->fd < 0) + { + if (errno == EACCES) + { + if (!warned) + { + fprintf(stderr, + "As root, please run\n\nchmod 666 %s\n\n%s\n", cd_device, + "to give yourself permission to access the CD-ROM device."); + warned++; + } + } + else if (errno != EINTR) + { + perror(cd_device); + exit(1); + } + + /* No CD in drive. */ + return (1); + } + + if (warned) + { + warned = 0; + fprintf(stderr, "Thank you.\n"); + } + + /* Now fill in the relevant parts of the wm_drive structure. */ + fd = d->fd; + *d = *(find_drive_struct("", "", "")); + d->fd = fd; + + (d->init)(d); + + return (0); +} /* wmcd_open() */ + +/* + * Re-Open the device if it is open. + */ +int +wmcd_reopen( struct wm_drive *d ) +{ + int status; + + do { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "wmcd_reopen "); + if (d->fd >= 0) /* Device really open? */ + { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "closes the device and "); + status = close( d->fd ); /* close it! */ + /* we know, that the file is closed, do we? */ + d->fd = -1; + } + wm_susleep( 1000 ); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "calls wmcd_open()\n"); + status = wmcd_open( d ); /* open it as usual */ + wm_susleep( 1000 ); + } while ( status != 0 ); + return status; +} /* wmcd_reopen() */ + +#endif diff --git a/ascd/libworkman/plat_sun.c b/ascd/libworkman/plat_sun.c new file mode 100644 index 0000000..784ae5f --- /dev/null +++ b/ascd/libworkman/plat_sun.c @@ -0,0 +1,1188 @@ +/* + * $Id: plat_sun.c,v 1.9 1999/05/28 03:35:54 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Sun-specific drive control routines. + */ + +#ifdef sun +static char plat_sun_id[] = "$Id: plat_sun.c,v 1.9 1999/05/28 03:35:54 dirk Exp $"; + +#include <errno.h> +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> +#include <string.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/ioctl.h> + +#include "include/wm_config.h" +#include "include/wm_helpers.h" +#include "include/wm_cdrom.h" + + +#include <ustat.h> +#include <unistd.h> +#include <signal.h> +#ifdef solbourne +# include <mfg/dklabel.h> +# include <mfg/dkio.h> +# include <sys/unistd.h> +# include <dev/srvar.h> +#else /* A real Sun */ +# ifdef SYSV +# include <poll.h> +# include <stdlib.h> +# include <sys/cdio.h> +# include <sys/socket.h> +# include <sys/scsi/impl/uscsi.h> +# include "include/wm_cdda.h" +# else +# include <sys/buf.h> +# include <sun/dkio.h> +# include <scsi/targets/srdef.h> +# include <scsi/impl/uscsi.h> +# include <scsi/generic/commands.h> +# endif +#endif + +#include "include/wm_struct.h" + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +int min_volume = 0; +int max_volume = 255; + +extern char *cd_device, *cddaslave_path; +extern int intermittent_dev; + +int cdda_slave = -1; + +int current_end; + +#if defined(SYSV) && defined(SIGTHAW) +#ifdef __GNUC__ +void sigthawinit(void) __attribute__ ((constructor)); +#else +#pragma init(sigthawinit) +#endif /* GNUC */ + +static int last_left, last_right; +static struct wm_drive *thecd; + +static void thawme(int sig) +{ + change_mode(NULL, WM_CDM_STOPPED, NULL); + codec_init(); + if( thecd ) + gen_set_volume(thecd, last_left, last_right); +} /* thawme */ + +void sigthawinit( void ) +{ + struct sigaction sa; + + sa.sa_handler = thawme; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + sigaction(SIGTHAW, &sa, NULL); +} + +#endif /* SYSV && SIGTHAW */ + +/* + * find_cdrom + * + * Determine the name of the CD-ROM device. + * + * Use the first of /vol/dev/aliases/cdrom0, /dev/rdsk/c0t6d0s2, and /dev/rsr0 + * that exists. (Check for /vol/dev/aliases, not cdrom0, since it won't be + * there if there's no CD in the drive.) This is done so a single SunOS 4.x + * binary can be used on any 4.x or higher Sun system. + */ +void +find_cdrom( void ) +{ + if (access("/vol/dev/aliases", X_OK) == 0) + { + /* Volume manager. Device might not be there. */ + intermittent_dev = 1; + + /* If vold is running us, it'll tell us the device name. */ + cd_device = getenv("VOLUME_DEVICE"); + if (cd_device == NULL) + cd_device = "/vol/dev/aliases/cdrom0"; + } + else if (access("/dev/rdsk/c0t6d0s2", F_OK) == 0) + { + /* Solaris 2.x w/o volume manager. */ + cd_device = "/dev/rdsk/c0t6d0s2"; + } + else if (access("/dev/rcd0", F_OK) == 0) + { + cd_device = "/dev/rcd0"; + } + else if (access("/dev/rsr0", F_OK) == 0) + cd_device = "/dev/rsr0"; + else + { + fprintf(stderr, "Couldn't find a CD device!\n"); + exit(1); + } +} + +/* + * Wait for an acknowledgement from the CDDA slave. + */ +static int +get_ack(int fd) +{ +#if defined(BUILD_CDDA) && defined(WMCDDA_DONE) /* { */ + struct cdda_block blk; + + do + if (read(fd, &blk, sizeof(blk)) <= 0) + return (0); + while (blk.status != WMCDDA_ACK); +#endif /* } */ + + return (1); +} + +/* + * Initialize the drive. A no-op for the generic driver. + */ +int +gen_init( struct wm_drive *d ) +{ + codec_init(); + return (0); +} + +/* + * Try to initialize the CDDA slave. Returns 0 on error. + */ +int +cdda_init( struct wm_drive *d ) +{ +#if defined(BUILD_CDDA) && defined(WMCDDA_DONE) /* { */ + int slavefds[2]; + + if (cdda_slave > -1) + return (1); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, slavefds)) + { + perror("socketpair"); + return (0); + } + + switch (fork()) { + case 0: + close(slavefds[0]); + dup2(slavefds[1], 1); + dup2(slavefds[1], 0); + close(slavefds[1]); + close(d->fd); + /* Try the default path first. */ + execl(cddaslave_path, cddaslave_path, cd_device, NULL); + /* Search $PATH if that didn't work. */ + execlp("cddaslave", "cddaslave", cd_device, NULL); + perror(cddaslave_path); + exit(1); + + case -1: + close(slavefds[0]); + close(slavefds[1]); + perror("fork"); + return (0); + } + + close(slavefds[1]); + cdda_slave = slavefds[0]; + + if (!get_ack(cdda_slave)) + { + cdda_slave = -1; + codec_start(); + return (0); + } + + return (1); + +#else /* BUILD_CDDA } { */ + /* + * If we're not building CDDA support, don't even bother trying. + */ + return (0); +#endif +} + +/* + * Turn off the CDDA slave. + */ +void +cdda_kill( struct wm_drive *d ) +{ + if (cdda_slave > -1) + { + write(cdda_slave, "Q", 1); + get_ack(cdda_slave); + wait(NULL); + cdda_slave = -1; + codec_start(); + } +} + +/* + * Get the number of tracks on the CD. + */ +int +gen_get_trackcount( struct wm_drive *d, int *tracks ) +{ + struct cdrom_tochdr hdr; + + if (ioctl(d->fd, CDROMREADTOCHDR, &hdr)) + return (-1); + + *tracks = hdr.cdth_trk1; + return (0); +} + +/* + * Get the start time and mode (data or audio) of a track. + */ +int +gen_get_trackinfo( struct wm_drive *d, int track, int *data, int *startframe) +{ + struct cdrom_tocentry entry; + + entry.cdte_track = track; + entry.cdte_format = CDROM_MSF; + + if (ioctl(d->fd, CDROMREADTOCENTRY, &entry)) + return (-1); + + *startframe = entry.cdte_addr.msf.minute * 60 * 75 + + entry.cdte_addr.msf.second * 75 + + entry.cdte_addr.msf.frame; + *data = entry.cdte_ctrl & CDROM_DATA_TRACK ? 1 : 0; + + return (0); +} + +/* + * Get the number of frames on the CD. + */ +int +gen_get_cdlen(struct wm_drive *d, int *frames ) +{ + int tmp; + + return (gen_get_trackinfo(d, CDROM_LEADOUT, &tmp, frames)); +} + + +/* Alarm signal handler. */ +static void do_nothing( int x ) { x++; } + +/* + * Get the current status of the drive: the current play mode, the absolute + * position from start of disc (in frames), and the current track and index + * numbers if the CD is playing or paused. + */ +int +gen_get_drive_status( struct wm_drive *d, + enum wm_cd_modes oldmode, + enum wm_cd_modes *mode, + int *pos, int *track, int *index ) +{ + struct cdrom_subchnl sc; + struct itimerval old_timer, new_timer; + struct sigaction old_sig, new_sig; + + /* If we can't get status, the CD is ejected, so default to that. */ + *mode = WM_CDM_EJECTED; + + /* Is the device open? */ + if (d->fd < 0) + { + switch (wmcd_open(d)) { + case -1: /* error */ + return (-1); + + case 1: /* retry */ + return (0); + } + } + +#if defined(BUILD_CDDA) && defined(WMCDDA_DONE) /* { */ + if ((oldmode == WM_CDM_PAUSED || oldmode == WM_CDM_PLAYING || oldmode == WM_CDM_STOPPED) && + cdda_slave > -1) + { + struct cdda_block blk; + struct pollfd fds; + int gotone = 0; + + fds.fd = cdda_slave; + fds.events = POLLRDNORM; + + *mode = oldmode; + + while (poll(&fds, 1, 0) > 0) + { + read(cdda_slave, &blk, sizeof(blk)); + gotone = 1; + } + + /* We only want to use the latest status report. */ + if (gotone) + { + if (blk.status == WMCDDA_PLAYED) + { + *track = blk.track; + *index = blk.index; + *pos = blk.minute * 60 * 75 + + blk.second * 75 + + blk.frame; + + *mode = WM_CDM_PLAYING; + } + else if (blk.status == WMCDDA_DONE) + *mode = WM_CDM_TRACK_DONE; + else if (blk.status == WMCDDA_STOPPED) + { + if (oldmode == WM_CDM_PLAYING || oldmode == WM_CDM_PAUSED) + *mode = WM_CDM_PAUSED; + else + *mode = WM_CDM_STOPPED; + } + else if (blk.status == WMCDDA_ERROR) + { + /* + * An error near the end of the CD probably + * just means we hit the end. + */ + *mode = WM_CDM_TRACK_DONE; + } + else if (blk.status == WMCDDA_EJECTED) + { + *mode = WM_CDM_EJECTED; + } + } + + return (0); + } +#endif /* } */ + + /* + * Solaris 2.2 hangs on this ioctl if someone else ejects the CD. + * So we schedule a signal to break out of the hang if the call + * takes an unreasonable amount of time. The signal handler and + * timer are restored immediately to avoid interfering with XView. + */ + if (intermittent_dev) + { + /* + * First clear out the timer so XView's signal doesn't happen + * while we're diddling with the signal handler. + */ + timerclear(&new_timer.it_interval); + timerclear(&new_timer.it_value); + setitimer(ITIMER_REAL, &new_timer, &old_timer); + + /* + * Now install the no-op signal handler. + */ + new_sig.sa_handler = do_nothing; + memset(&new_sig.sa_mask, 0, sizeof(new_sig.sa_mask)); + new_sig.sa_flags = 0; + if (sigaction(SIGALRM, &new_sig, &old_sig)) + perror("sigaction"); + + /* + * And finally, set the timer. + */ + new_timer.it_value.tv_sec = 2; + setitimer(ITIMER_REAL, &new_timer, NULL); + } + + sc.cdsc_format = CDROM_MSF; + + if (ioctl(d->fd, CDROMSUBCHNL, &sc)) + { + if (intermittent_dev) + { + sigaction(SIGALRM, &old_sig, NULL); + setitimer(ITIMER_REAL, &old_timer, NULL); + + /* If the device can disappear, let it do so. */ + close(d->fd); + d->fd = -1; + } + + return (0); + } + + if (intermittent_dev) + { + sigaction(SIGALRM, &old_sig, NULL); + setitimer(ITIMER_REAL, &old_timer, NULL); + } + + switch (sc.cdsc_audiostatus) { + case CDROM_AUDIO_PLAY: + *mode = WM_CDM_PLAYING; + *track = sc.cdsc_trk; + *index = sc.cdsc_ind; + *pos = sc.cdsc_absaddr.msf.minute * 60 * 75 + + sc.cdsc_absaddr.msf.second * 75 + + sc.cdsc_absaddr.msf.frame; + break; + + case CDROM_AUDIO_PAUSED: + case CDROM_AUDIO_INVALID: + case CDROM_AUDIO_NO_STATUS: + if (oldmode == WM_CDM_PLAYING || oldmode == WM_CDM_PAUSED) + { + *mode = WM_CDM_PAUSED; + *track = sc.cdsc_trk; + *index = sc.cdsc_ind; + *pos = sc.cdsc_absaddr.msf.minute * 60 * 75 + + sc.cdsc_absaddr.msf.second * 75 + + sc.cdsc_absaddr.msf.frame; + } + else + *mode = WM_CDM_STOPPED; + break; + + /* CD ejected manually during play. */ + case CDROM_AUDIO_ERROR: + break; + + case CDROM_AUDIO_COMPLETED: + *mode = WM_CDM_TRACK_DONE; /* waiting for next track. */ + break; + + default: + *mode = WM_CDM_UNKNOWN; + break; + } + + return (0); +} + +/* + * Set the volume level for the left and right channels. Their values + * range from 0 to 100. + */ +int +gen_set_volume( struct wm_drive *d, int left, int right ) +{ + struct cdrom_volctrl v; + +#if defined(SIGTHAW) && defined(SYSV) + last_left = left; + last_right = right; + thecd = d; +#endif + + if (cdda_slave > -1) + { + int bal, vol; + unsigned char cmd[2]; + + bal = (right - left) + 100; + bal *= 255; + bal /= 200; + if (right > left) + vol = right; + else + vol = left; + vol *= 255; + vol /= 100; + + cmd[0] = 'B'; + cmd[1] = bal; + write(cdda_slave, cmd, 2); + cmd[0] = 'V'; + cmd[1] = vol; + write(cdda_slave, cmd, 2); + /* + * Don't wait for the ack, or the user won't be able to drag + * the volume slider smoothly. + */ + + return (0); + } + + left = (left * (max_volume - min_volume)) / 100 + min_volume; + right = (right * (max_volume - min_volume)) / 100 + min_volume; + + v.channel0 = left < 0 ? 0 : left > 255 ? 255 : left; + v.channel1 = right < 0 ? 0 : right > 255 ? 255 : right; + + return (ioctl(d->fd, CDROMVOLCTRL, &v)); +} + +/* + * Pause the CD. + */ +int +gen_pause( struct wm_drive *d ) +{ + if (cdda_slave > -1) + { + int dummy, mode = WM_CDM_PLAYING; + + write(cdda_slave, "S", 1); + get_ack(cdda_slave); +/* while (mode != WM_CDM_PAUSED) + gen_get_drive_status(d, WM_CDM_PAUSED, &mode, &dummy, &dummy, + &dummy); +*/ + return (0); + } + + codec_stop(); + return (ioctl(d->fd, CDROMPAUSE)); +} + +/* + * Resume playing the CD (assuming it was paused.) + */ +int +gen_resume( struct wm_drive *d ) +{ + if (cdda_slave > -1) + return (1); + + codec_start(); + return (ioctl(d->fd, CDROMRESUME)); +} + +/* + * Stop the CD. + */ +int +gen_stop( struct wm_drive *d ) +{ + if (cdda_slave > -1) + { + write(cdda_slave, "S", 1); + get_ack(cdda_slave); + + /* + * The WMCDDA_STOPPED status message will be caught by + * gen_get_drive_status. + */ + + return (0); + } + codec_stop(); + return (ioctl(d->fd, CDROMSTOP)); +} + +/* + * Play the CD from one position to another. + * + * d Drive structure. + * start Frame to start playing at. + * end End of this chunk. + * realstart Beginning of this chunk (<= start) + */ +int +gen_play( struct wm_drive *d, int start, int end, int realstart) +{ + struct cdrom_msf msf; + unsigned char cmdbuf[10]; + + current_end = end; + + if (cdda_slave > -1) + { + cmdbuf[0] = 'P'; + cmdbuf[1] = start / (60 * 75); + cmdbuf[2] = (start % (60*75)) / 75; + cmdbuf[3] = start % 75; + cmdbuf[4] = end / (60*75); + cmdbuf[5] = (end % (60*75)) / 75; + cmdbuf[6] = end % 75; + cmdbuf[7] = realstart / (60 * 75); + cmdbuf[8] = (realstart % (60*75)) / 75; + cmdbuf[9] = realstart % 75; + + /* Write the play command and make sure the slave has it. */ + write(cdda_slave, cmdbuf, 10); + get_ack(cdda_slave); + + return (0); + } + + msf.cdmsf_min0 = start / (60*75); + msf.cdmsf_sec0 = (start % (60*75)) / 75; + msf.cdmsf_frame0 = start % 75; + msf.cdmsf_min1 = end / (60*75); + msf.cdmsf_sec1 = (end % (60*75)) / 75; + msf.cdmsf_frame1 = end % 75; + + codec_start(); + if (ioctl(d->fd, CDROMSTART)) + return (-1); + if (ioctl(d->fd, CDROMPLAYMSF, &msf)) + return (-2); + + return (0); +} + +/* + * Eject the current CD, if there is one. + */ +int +gen_eject( struct wm_drive *d ) +{ + struct stat stbuf; + struct ustat ust; + + if (fstat(d->fd, &stbuf) != 0) + return (-2); + + /* Is this a mounted filesystem? */ + if (ustat(stbuf.st_rdev, &ust) == 0) + return (-3); + + if (cdda_slave > -1) + { + write(cdda_slave, "S", 1); + get_ack(cdda_slave); + } + + if (ioctl(d->fd, CDROMEJECT)) + return (-1); + + /* Close the device if it needs to vanish. */ + if (intermittent_dev) + { + close(d->fd); + d->fd = -1; + /* Also remember to tell the cddaslave since volume + manager switches links around on us */ + if (cdda_slave > -1) + { + write(cdda_slave, "E", 1); + get_ack(cdda_slave); + } + } + + return (0); +} /* gen_eject() */ + +/*----------------------------------------* + * Close the CD tray + * + * Please edit and send changes to + * [email protected] + *----------------------------------------*/ + +int gen_closetray(struct wm_drive *d) +{ +#ifdef CAN_CLOSE + if(!close(d->fd)) + { + d->fd=-1; + return(wmcd_reopen(d)); + } else { + return(-1); + } +#else + /* Always succeed if the drive can't close */ + return(0); +#endif /* CAN_CLOSE */ +} /* gen_closetray() */ + + +/* + * Read the initial volume from the drive, if available. Each channel + * ranges from 0 to 100, with -1 indicating data not available. + */ +int +gen_get_volume( struct wm_drive *d, int *left, int *right ) +{ +#if defined(BUILD_CDDA) && defined(WMCDDA_DONE) /* { */ + struct cdda_block blk; + + if (cdda_slave > -1) + { + write(cdda_slave, "G", 1); + get_ack(cdda_slave); + read(cdda_slave, &blk, sizeof(blk)); + + *left = *right = (blk.volume * 100 + 254) / 255; + + if (blk.balance < 110) + *right = (((blk.volume * blk.balance + 127) / 128) * + 100 + 254) / 255; + else if (blk.balance > 146) + *left = (((blk.volume * (255 - blk.balance) + + 127) / 128) * 100 + 254) / 255; + + return (0); + } +#endif /* } */ + + *left = *right = -1; + + return (wm_scsi2_get_volume(d, left, right)); +} + +#ifdef BUILD_CDDA /* { */ + +/* + * Tell the CDDA slave to set the play direction. + */ +void +gen_set_direction( int newdir ) +{ + unsigned char buf[2]; + + if (cdda_slave > -1) + { + buf[0] = 'd'; + buf[1] = newdir; + write(cdda_slave, buf, 2); + get_ack(cdda_slave); + } +} + +/* + * Tell the CDDA slave to set the play speed. + */ +void +gen_set_speed( int speed ) +{ + unsigned char buf[2]; + + if (cdda_slave > -1) + { + buf[0] = 's'; + buf[1] = speed; + write(cdda_slave, buf, 2); + get_ack(cdda_slave); + } +} + +/* + * Tell the CDDA slave to set the loudness level. + */ +void +gen_set_loudness( int loud ) +{ + unsigned char buf[2]; + + if (cdda_slave > -1) + { + buf[0] = 'L'; + buf[1] = loud; + write(cdda_slave, buf, 2); + get_ack(cdda_slave); + } +} + +/* + * Tell the CDDA slave to start (or stop) saving to a file. + */ +void +gen_save( char *filename ) +{ + int len; + + if (filename == NULL || filename[0] == '\0') + len = 0; + else + len = strlen(filename); + write(cdda_slave, "F", 1); + write(cdda_slave, &len, sizeof(len)); + if (len) + write(cdda_slave, filename, len); + get_ack(cdda_slave); +} + +#endif /* BUILD_CDDA } */ + +#ifndef solbourne +/* + * Send an arbitrary SCSI command out the bus and optionally wait for + * a reply if "retbuf" isn't NULL. + */ +int +wm_scsi( struct wm_drive *d, + unsigned char *cdb, + int cdblen, void *retbuf, + int retbuflen, int getreply ) +{ + char x; + struct uscsi_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.uscsi_cdb = (void *) cdb; + cmd.uscsi_cdblen = cdblen; + cmd.uscsi_bufaddr = retbuf ? retbuf : (void *)&x; + cmd.uscsi_buflen = retbuf ? retbuflen : 0; + cmd.uscsi_flags = USCSI_ISOLATE | USCSI_SILENT; + if (getreply) + cmd.uscsi_flags |= USCSI_READ; + + if (ioctl(d->fd, USCSICMD, &cmd)) + return (-1); + + if (cmd.uscsi_status) + return (-1); + + return (0); +} +#else + +int wm_scsi() { return (-1); } + +#endif + +/* + * Open the CD device and figure out what kind of drive is attached. + */ +int +wmcd_open( struct wm_drive *d ) +{ + int fd; + static int warned = 0; + char vendor[32] = WM_STR_GENVENDOR; + char model[32] = WM_STR_GENMODEL; + char rev[32] = WM_STR_GENREV; + + if (cd_device == NULL) + find_cdrom(); + + if (d->fd >= 0) /* Device already open? */ + return (0); + + d->fd = open(cd_device, 0); + if (d->fd < 0) + { + /* Solaris 2.2 volume manager moves links around */ + if (errno == ENOENT && intermittent_dev) + return (1); + + if (errno == EACCES) + { + if (!warned) + { + char realname[MAXPATHLEN]; + + if (realpath(cd_device, realname) == NULL) + { + perror("realpath"); + return (-1); + } + + fprintf(stderr, + "As root, please run\n\nchmod 666 %s\n\n%s\n", realname, + "to give yourself permission to access the CD-ROM device."); + warned++; + } + } + else if (errno != ENXIO) + { + perror(cd_device); + exit(1); + } + + /* No CD in drive. */ + return (1); + } + + if (warned) + { + warned = 0; + fprintf(stderr, "Thank you.\n"); + } + + /* Now fill in the relevant parts of the wm_drive structure. */ + fd = d->fd; + + /* + * See if we can do digital audio. + */ + if (cdda_init(d)) + enable_cdda_controls(1); + + /* Can we figure out the drive type? */ + if (wm_scsi_get_drive_type(d, vendor, model, rev)) { + if (errno == EPERM) { + /* + * Solaris 2.4 seems to refuse to do USCSICMD ioctls + * when not running as root. SunOS 4.x allows it + * as an unprivileged user, though. + */ + fprintf(stderr, "Warning: WorkMan can't adapt itself to your drive unless it runs as root.\n"); + } else { + fprintf(stderr, "Warning: WorkMan couldn't determine drive type\n"); + } + *d = *(find_drive_struct("Generic", "drive type", "")); + } else { + *d = *(find_drive_struct(vendor, model, rev)); + } + + wm_drive_settype(vendor, model, rev); + d->fd = fd; + + (d->init)(d); + + return (0); +} /* wmcd_open() */ + +/* + * Re-Open the device if it is open. + */ +int +wmcd_reopen( struct wm_drive *d ) +{ + int status; + + do { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "wmcd_reopen "); + if (d->fd >= 0) /* Device really open? */ + { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "closes the device and "); + status = close( d->fd ); /* close it! */ + /* we know, that the file is closed, do we? */ + d->fd = -1; + } + wm_susleep( 1000 ); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "calls wmcd_open()\n"); + status = wmcd_open( d ); /* open it as usual */ + wm_susleep( 1000 ); + } while ( status != 0 ); + return status; +} /* wmcd_reopen() */ + + +/* + * The following code activates the internal CD audio passthrough on + * SPARCstation 5 systems (and possibly others.) + * + * Thanks to <[email protected]>, Roger Oscarsson <[email protected]> + * and Steve McKinty <> + * + * Most CD drives have a headphone socket on the front, but it + * is often more convenient to route the audio though the + * built-in audio device. That way the user can leave their + * headphones plugged-in to the base system, for use with + * other audio stuff like ShowMeTV + */ + +#ifdef CODEC /* { */ +#ifdef SYSV /* { */ + +# include <sys/ioctl.h> +# include <sys/audioio.h> +# include <stdlib.h> + +#else /* } { */ + +# include <sun/audioio.h> +# define AUDIO_DEV_SS5STYLE 5 +typedef int audio_device_t; + +#endif /* } */ +#endif /* } */ + +/* + * Don't do anything with /dev/audio if we can't set it to high quality. + * Also, don't do anything real if it's not Solaris. + */ +#if !defined(AUDIO_ENCODING_LINEAR) || !defined(CODEC) || !defined(SYSV) /* { */ +codec_init() { return 0; } +codec_start() { return 0; } +codec_stop() { return 0; } +#else + +#ifndef AUDIO_INTERNAL_CD_IN +#define AUDIO_INTERNAL_CD_IN 0x4 +#endif + +static char* devname = 0; +static char* ctlname = 0; +static int ctl_fd = -1; +static int port = AUDIO_LINE_IN; +int internal_audio = 1; + +codec_init( void ) +{ + register int i; + char* ctlname; + audio_info_t foo; + audio_device_t aud_dev; + + if (internal_audio == 0) + { + ctl_fd = -1; + return(0); + } + + if (!(devname = getenv("AUDIODEV"))) devname = "/dev/audio"; + ctlname = strcat(strcpy(malloc(strlen(devname) + 4), devname), "ctl"); + if ((ctl_fd = open(ctlname, O_WRONLY, 0)) < 0) { + perror(ctlname); + return -1; + } + if (ioctl(ctl_fd, AUDIO_GETDEV, &aud_dev) < 0) { + close(ctl_fd); + ctl_fd = -1; + return -1; + } +/* + * Instead of filtering the "OLD_SUN_AUDIO", try to find the new ones. + * Not sure if this is all correct. + */ +#ifdef SYSV + if (strcmp(aud_dev.name, "SUNW,CS4231") && + strcmp(aud_dev.name, "SUNW,sb16") && + strcmp(aud_dev.name, "SUNW,sbpro")) +#else + if (aud_dev != AUDIO_DEV_SS5STYLE) +#endif + { + close(ctl_fd); + ctl_fd = -1; + return 0; /* but it's okay */ + } + +/* + * Does the chosen device have an internal CD port? + * If so, use it. If not then try and use the + * Line In port. + */ + if (ioctl(ctl_fd, AUDIO_GETINFO, &foo) < 0) + { + perror("AUDIO_GETINFO"); + close(ctl_fd); + ctl_fd = -1; + return(-1); + } + if (foo.record.avail_ports & AUDIO_INTERNAL_CD_IN) + port = AUDIO_INTERNAL_CD_IN; + else + port = AUDIO_LINE_IN; + +/* + * now set it up to use it. See audio(7I) + */ + + AUDIO_INITINFO(&foo); + foo.record.port = port; + foo.record.balance = foo.play.balance = AUDIO_MID_BALANCE; +#ifdef BUILD_CDDA + if (cdda_slave > -1) + foo.monitor_gain = 0; + else +#endif + foo.monitor_gain = AUDIO_MAX_GAIN; +/* + * These next ones are tricky. The voulme will depend on the CD drive + * volume (set by the knob on the drive and/or by workman's volume + * control), the audio device record gain and the audio device + * play gain. For simplicity we set the latter two to something + * reasonable, but we don't force them to be reset if the user + * wants to change them. +*/ + foo.record.gain = (AUDIO_MAX_GAIN * 80) / 100; + foo.play.gain = (AUDIO_MAX_GAIN * 40) / 100; + + ioctl(ctl_fd, AUDIO_SETINFO, &foo); + return 0; +} + +static kick_codec( void ) { + audio_info_t foo; + int dev_fd; + int retval = 0; + +/* + * Open the audio device, not the control device. This + * will fail if someone else has taken it. + */ + + if ((dev_fd = open(devname, O_WRONLY|O_NDELAY, 0)) < 0) { + perror(devname); + return -1; + } + + AUDIO_INITINFO(&foo); + foo.record.port = port; + foo.monitor_gain = AUDIO_MAX_GAIN; + +/* These can only be set on the real device */ + foo.play.sample_rate = 44100; + foo.play.channels = 2; + foo.play.precision = 16; + foo.play.encoding = AUDIO_ENCODING_LINEAR; + + if ((retval = ioctl(dev_fd, AUDIO_SETINFO, &foo)) < 0) + perror(devname); + + close(dev_fd); + return retval; +} + +codec_start( void ) +{ +audio_info_t foo; + + if (ctl_fd < 0) + return 0; + + if (ioctl(ctl_fd, AUDIO_GETINFO, &foo) < 0) + return -1; + + if (foo.play.channels != 2) return kick_codec(); + if (foo.play.encoding != AUDIO_ENCODING_LINEAR) return kick_codec(); + if (foo.play.precision != 16) return kick_codec(); + if (foo.play.sample_rate != 44100) return kick_codec(); + + if (foo.record.channels != 2) return kick_codec(); + if (foo.record.encoding != AUDIO_ENCODING_LINEAR) return kick_codec(); + if (foo.record.precision != 16) return kick_codec(); + if (foo.record.sample_rate != 44100) return kick_codec(); + + if (foo.monitor_gain != AUDIO_MAX_GAIN) return kick_codec(); + if (foo.record.port != port) return kick_codec(); + + return 0; +} + +codec_stop( void ) { return 0; } + +#endif /* CODEC } */ +#endif /* sun } */ diff --git a/ascd/libworkman/plat_sun_audio.c b/ascd/libworkman/plat_sun_audio.c new file mode 100644 index 0000000..7d6a4bc --- /dev/null +++ b/ascd/libworkman/plat_sun_audio.c @@ -0,0 +1,489 @@ +/* + * $Id: plat_sun_audio.c,v 1.3 1999/03/07 08:36:41 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Sun (really Solaris) digital audio functions. + */ + +#include "include/wm_config.h" +#if defined(sun) && defined(SYSV) && defined(BUILD_CDDA) + +static char plat_sun_audio_id[] = "$Id: plat_sun_audio.c,v 1.3 1999/03/07 08:36:41 dirk Exp $"; + +#include "include/wm_cdda.h" + +/* types.h included by wmcdda.h */ + +#include <stdio.h> +#include <malloc.h> +#include <sys/ioctl.h> +#include <sys/audioio.h> +#include <sys/stropts.h> +#include <sys/time.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +/* + * Since there's a lag time between writing audio to the audio device and + * hearing it, we need to make sure the status indicators correlate to what's + * playing out the speaker. Luckily, Solaris gives us some audio + * synchronization facilities that make this pretty easy. + * + * We maintain a circular queue of status information. When we write some + * sound to the audio device, we put its status info into the queue. We write + * a marker into the audio stream; when the audio device driver encounters the + * marker, it increments a field in a status structure. When we see that + * field go up, we grab the next status structure from the queue and send it + * to the parent process. + * + * The minimum size of the queue depends on the latency of the audio stream. + */ +#define QSIZE 500 + +struct cdda_block queue[QSIZE]; +int qtail; +int qstart; + +/* + * We only send WMCDDA_PLAYED status messages upstream when the CD is supposed + * to be playing; this is used to keep track. + */ +extern int playing; + +static int aufd, aucfd; +static int raw_audio = 1; /* Can /dev/audio take 44.1KHz stereo? */ + +/* + * For fast linear-to-ulaw mapping, we use a lookup table that's generated + * at startup. + */ +unsigned char *ulawmap, linear_to_ulaw(); + +char *getenv(); + +/* + * Dummy signal handler so writes to /dev/audio will interrupt. + */ +static void +dummy( void ) +{ + signal(SIGALRM, dummy); +} + +/* + * Initialize the audio device. + */ +void +wmaudio_init( void ) +{ + audio_info_t info; + char *audiodev, *acdev; + int linval; + + audiodev = getenv("AUDIODEV"); + if (audiodev == NULL) + audiodev = "/dev/audio"; + + acdev = malloc(strlen(audiodev) + 4); + if (acdev == NULL) + { + perror("Can't allocate audio control filename"); + exit(1); + } + strcpy(acdev, audiodev); + strcat(acdev, "ctl"); + + aucfd = open(acdev, O_WRONLY, 0); + if (aucfd < 0) + { + perror(acdev); + exit(1); + } + free(acdev); + + aufd = open(audiodev, O_WRONLY, 0); + if (aufd < 0) + { + perror(audiodev); + exit(1); + } + + signal(SIGALRM, dummy); + + /* + * Try to set the device to CD-style audio; we can process it + * with the least CPU overhead. + */ + AUDIO_INITINFO(&info); + info.play.sample_rate = 44100; + info.play.channels = 2; + info.play.precision = 16; + info.play.encoding = AUDIO_ENCODING_LINEAR; + info.play.pause = 0; + info.record.pause = 0; + info.monitor_gain = 0; + + if (ioctl(aufd, AUDIO_SETINFO, &info) < 0) + if (errno == EINVAL) + { + /* + * Oh well, so much for that idea. + */ + AUDIO_INITINFO(&info); + info.play.sample_rate = 8000; + info.play.channels = 1; + info.play.precision = 8; + info.play.encoding = AUDIO_ENCODING_ULAW; + info.play.pause = 0; + info.record.pause = 0; + info.monitor_gain = 0; + if (ioctl(aufd, AUDIO_SETINFO, &info) < 0) + { + perror("Can't set up audio device"); + exit(1); + } + + /* + * Initialize the linear-to-ulaw mapping table. + */ + if (ulawmap == NULL) + ulawmap = malloc(65536); + if (ulawmap == NULL) + { + perror("malloc"); + exit(1); + } + for (linval = 0; linval < 65536; linval++) + ulawmap[linval] = linear_to_ulaw(linval-32768); + ulawmap += 32768; + raw_audio = 0; + } + else + { + perror(audiodev); + exit(1); + } +} + +/* + * Get ready to play some sound. + */ +void +wmaudio_ready( void ) +{ + audio_info_t info; + + /* + * Start at the correct queue position. + */ + if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) perror("AUDIO_GETINFO"); + qtail = info.play.eof % QSIZE; + qstart = qtail; + + queue[qtail].status = WMCDDA_OK; +} + +/* + * Stop the audio immediately. + */ +void +wmaudio_stop( void ) +{ + if (ioctl(aufd, I_FLUSH, FLUSHRW) < 0) + perror("flush"); +} + +/* + * Close the audio device. + */ +void +wmaudio_close( void ) +{ + wmaudio_stop(); + close(aufd); + close(aucfd); +} + +/* + * Set the volume level. + */ +void +wmaudio_volume(int level) +{ + audio_info_t info; + + AUDIO_INITINFO(&info); + if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) perror("AUDIO_GETINFO"); + info.play.gain = level; + if (ioctl(aucfd, AUDIO_SETINFO, &info) < 0) perror("AUDIO_SETINFO"); +} + +/* + * Set the balance level. + */ +void +wmaudio_balance(int level) +{ + audio_info_t info; + + AUDIO_INITINFO(&info); + if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) perror("AUDIO_GETINFO"); + level *= AUDIO_RIGHT_BALANCE; + info.play.balance = level / 255; + if (ioctl(aucfd, AUDIO_SETINFO, &info) < 0) perror("AUDIO_SETINFO"); +} + +/* + * Mark the most recent audio block on the queue as the last one. + */ +void +wmaudio_mark_last( void ) +{ + queue[qtail].status = WMCDDA_DONE; +} + +/* + * Figure out the most recent status information and send it upstream. + */ +int +wmaudio_send_status( void ) +{ + audio_info_t info; + int qhead; + + /* + * Now send the most current status information to our parent. + */ + if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) + perror("AUDIO_GETINFO"); + qhead = info.play.eof % QSIZE; + + if (qhead != qstart && playing) + { + int balance; + + if (queue[qhead].status != WMCDDA_DONE) + queue[qhead].status = WMCDDA_PLAYED; + queue[qhead].volume = info.play.gain; + queue[qhead].balance = (info.play.balance * 255) / + AUDIO_RIGHT_BALANCE; + + send_status(queue + qhead); + qstart = -1; + } + + return (queue[qhead].status == WMCDDA_DONE); +} + +/* + * Play some audio and pass a status message upstream, if applicable. + * Returns 0 on success. + */ +int +wmaudio_play(unsigned char *rawbuf, long buflen, struct cdda_block *blk) +{ + int i; + short *buf16; + int alarmcount = 0; + struct itimerval it; + + alarm(1); + + while (write(aufd, rawbuf, buflen) <= 0) + if (errno == EINTR) + { + if (! raw_audio && alarmcount++ < 5) + { + /* + * 8KHz /dev/audio blocks for several seconds + * waiting for its queue to drop below a low + * water mark. + */ + wmaudio_send_status(); + timerclear(&it.it_interval); + timerclear(&it.it_value); + it.it_value.tv_usec = 500000; + setitimer(ITIMER_REAL, &it, NULL); + continue; + } + +/* close(aufd); + close(aucfd); + wmaudio_init(); +*/ wmaudio_stop(); + alarm(2); + continue; + } + else + { + blk->status = WMCDDA_ERROR; + return (-1); + } + alarm(0); + + /* + * Mark this spot in the audio stream. + * + * Marks don't always succeed (if the audio buffer is empty + * this call will block forever) so do it asynchronously. + */ + fcntl(aufd, F_SETFL, O_NONBLOCK); + if (write(aufd, rawbuf, 0) < 0) + { + if (errno != EAGAIN) + perror("audio mark"); + } + else + qtail = (qtail + 1) % QSIZE; + + fcntl(aufd, F_SETFL, 0); + + queue[qtail] = *blk; + + if (wmaudio_send_status() < 0) + return (-1); + else + return (0); +} + +/* + * Get the current audio state. + */ +void +wmaudio_state(struct cdda_block *blk) +{ + audio_info_t info; + int balance; + + if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) + perror("AUDIO_GETINFO"); + blk->volume = info.play.gain; + blk->balance = (info.play.balance * 255) / AUDIO_RIGHT_BALANCE; +} + +/* +** This routine converts from linear to ulaw. +** +** Craig Reese: IDA/Supercomputing Research Center +** Joe Campbell: Department of Defense +** 29 September 1989 +** +** References: +** 1) CCITT Recommendation G.711 (very difficult to follow) +** 2) "A New Digital Technique for Implementation of Any +** Continuous PCM Companding Law," Villeret, Michel, +** et al. 1973 IEEE Int. Conf. on Communications, Vol 1, +** 1973, pg. 11.12-11.17 +** 3) MIL-STD-188-113,"Interoperability and Performance Standards +** for Analog-to_Digital Conversion Techniques," +** 17 February 1987 +** +** Input: Signed 16 bit linear sample +** Output: 8 bit ulaw sample +*/ +#define ZEROTRAP /* turn on the trap as per the MIL-STD */ +#define BIAS 0x84 /* define the add-in bias for 16 bit samples */ +#define CLIP 32635 + +unsigned char +linear_to_ulaw( sample ) +int sample; +{ + static int exp_lut[256] = {0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7}; + int sign, exponent, mantissa; + unsigned char ulawbyte; + + /* Get the sample into sign-magnitude. */ + sign = (sample >> 8) & 0x80; /* set aside the sign */ + if ( sign != 0 ) sample = -sample; /* get magnitude */ + if ( sample > CLIP ) sample = CLIP; /* clip the magnitude */ + + /* Convert from 16 bit linear to ulaw. */ + sample = sample + BIAS; + exponent = exp_lut[( sample >> 7 ) & 0xFF]; + mantissa = ( sample >> ( exponent + 3 ) ) & 0x0F; + ulawbyte = ~ ( sign | ( exponent << 4 ) | mantissa ); +#ifdef ZEROTRAP + if ( ulawbyte == 0 ) ulawbyte = 0x02; /* optional CCITT trap */ +#endif + + return ulawbyte; +} + +/* + * Downsample a block of CDDA data, if necessary, for playing out an old-style + * audio device. + */ +long +wmaudio_convert(unsigned char *rawbuf, long buflen, struct cdda_block *blk) +{ + short *buf16 = (short *)rawbuf; + int i, j, samples; + int mono_value; + unsigned char *rbend = rawbuf + buflen; + + /* Don't do anything if the audio device can take the raw values. */ + if (raw_audio) + return (buflen); + + for (i = 0; buf16 < (short *)(rbend); i++) + { + /* Downsampling to 8KHz is a little irregular. */ + samples = (i & 1) ? ((i % 20) ? 10 : 12) : 12; + + /* And unfortunately, we don't always end on a nice boundary. */ + if (buf16 + samples > (short *)(rbend)) + samples = ((short *)rbend) - buf16; + + /* + * No need to average all the values; taking the first one + * is sufficient and less CPU-intensive. But we do need to + * do both channels. + */ + mono_value = (buf16[0] + buf16[1]) / 2; + buf16 += samples; + rawbuf[i] = ulawmap[mono_value]; + } + + return (i); +} + +#endif /* ... && BUILD_CDDA */ diff --git a/ascd/libworkman/plat_sun_cdda.c b/ascd/libworkman/plat_sun_cdda.c new file mode 100644 index 0000000..4d9f758 --- /dev/null +++ b/ascd/libworkman/plat_sun_cdda.c @@ -0,0 +1,371 @@ +/* + * $Id: plat_sun_cdda.c,v 1.3 1999/03/07 08:36:41 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Sun (really Solaris) CDDA functions. + */ + +#include "include/wm_config.h" +#if defined(sun) && defined(SYSV) && defined(BUILD_CDDA) /* { */ + +static char plat_sun_cdda_id[] = "$Id: plat_sun_cdda.c,v 1.3 1999/03/07 08:36:41 dirk Exp $"; + + +#include "include/wm_cdda.h" +/* types.h and cdio.h are included by wmcdda.h */ + +#include <stdio.h> +#include <math.h> +#include <sys/ioctl.h> +#include <malloc.h> +#include <errno.h> + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +#define CDDABLKSIZE 2368 +#define SAMPLES_PER_BLK 588 + +/* Address of next block to read. */ +int current_position = 0; + +/* Address of first and last blocks to read. */ +int starting_position = 0; +int ending_position = 0; + +/* Playback direction. */ +int direction = 1; + +/* Number of blocks to read at once; initialize to the maximum. */ +/* (was 30. Set to 15 for INTeL. Maybe config option? */ +int numblocks = 15; + +/* + * This is the fastest way to convert from BCD to 8-bit. + */ +unsigned char unbcd[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,0,0,0,0,0, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0,0,0,0,0,0, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 0,0,0,0,0,0, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 0,0,0,0,0,0, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0,0,0,0,0,0, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 0,0,0,0,0,0, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 0,0,0,0,0,0, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 0,0,0,0,0,0, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 0,0,0,0,0,0, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +/* + * Initialize the CDDA data buffer and open the appropriate device. + * + * NOTE: We allocate twice as much space as we need to actually read a block; + * this lets us do audio manipulations without bothering to malloc a second + * buffer. + * + * Also, test to see if we can actually *do* CDDA on this drive; if not, we + * need to exit right away so the UI doesn't show the user any CDDA controls. + */ +int +wmcdda_init(char **bufadr, long *buflenadr, int init_fd, char *devname) +{ + struct cdrom_cdda cdda; + + *bufadr = malloc(numblocks * CDDABLKSIZE * 2); + if (*bufadr == NULL) + return (-1); + + *buflenadr = numblocks * CDDABLKSIZE; + + /*init_fd = open(devname, 0); + if (init_fd == -1) + init_fd = open("/dev/rdsk/c0t6d0s2", 0); + */ + init_fd = wmcdda_open(devname); + + if (init_fd > -1) + { + cdda.cdda_addr = 200; + cdda.cdda_length = 1; + cdda.cdda_data = *bufadr; + cdda.cdda_subcode = CDROM_DA_SUBQ; + + if (ioctl(init_fd, CDROMCDDA, &cdda) < 0) + { + close(init_fd); + init_fd = -1; + } + } + + return (init_fd); +} + +/* + * Try to open the CD device + */ +int +wmcdda_open(char *devname) +{ + int fd; + + fd = open(devname, 0); + if (fd == -1) + fd = open("/dev/rdsk/c0t6d0s2", 0); + + return(fd); +} + + +/* + * Close the CD-ROM device in preparation for exiting. + */ +void +wmcdda_close(int fd) +{ + close(fd); +} + +/* + * Set up for playing the CD. Actually this doesn't play a thing, just sets a + * couple variables so we'll know what to do when we're called. + */ +void +wmcdda_setup(int start, int end, int realstart) +{ + current_position = start - 150; + ending_position = end - 150; + starting_position = realstart - 150; + + /* + * Special case: don't start at the "end" of a track if we're + * playing backwards! + */ + if (direction == -1 && start == realstart) + current_position = ending_position - numblocks; +} + +/* + * Read some blocks from the CD. Stop if we hit the end of the current region. + * + * Returns number of bytes read, -1 on error, 0 if stopped for a benign reason. + */ +long +wmcdda_read(int fd, unsigned char *rawbuf, long buflen, + struct cdda_block *block) +{ + struct cdrom_cdda cdda; + int blk; + unsigned char *q; + extern int speed; + + if ((direction > 0 && current_position >= ending_position) || + (direction < 0 && current_position < starting_position)) + { + block->status = WMCDDA_DONE; + return (0); + } + + cdda.cdda_addr = current_position; + if (ending_position && current_position + numblocks > ending_position) + cdda.cdda_length = ending_position - current_position; + else + cdda.cdda_length = numblocks; + cdda.cdda_data = rawbuf; + cdda.cdda_subcode = CDROM_DA_SUBQ; + + if (ioctl(fd, CDROMCDDA, &cdda) < 0) + { + if (errno == ENXIO) /* CD ejected! */ + { + block->status = WMCDDA_EJECTED; + return (-1); + } + + if (current_position + numblocks > ending_position) + { + /* + * Hit the end of the CD, probably. + */ + block->status = WMCDDA_DONE; + return (0); + } + + /* Sometimes it fails once, dunno why */ + if (ioctl(fd, CDROMCDDA, &cdda) < 0) + { + if (ioctl(fd, CDROMCDDA, &cdda) < 0) + { + if (ioctl(fd, CDROMCDDA, &cdda) < 0) + { + perror("CDROMCDDA"); + block->status = WMCDDA_ERROR; + return (-1); + } + } + } + } + + if (speed > 148) + { + /* + * We want speed=148 to advance by cdda_length, but + * speed=256 to advance cdda_length * 4. + */ + current_position = current_position + + (cdda.cdda_length * direction * (speed - 112)) / 36; + } + else + current_position = current_position + + cdda.cdda_length * direction; + + for (blk = 0; blk < numblocks; blk++) + { + /* + * New valid Q-subchannel information? Update the block + * status. + */ + q = &rawbuf[blk * CDDABLKSIZE + SAMPLES_PER_BLK * 4]; + if (*q == 1) + { + block->status = WMCDDA_OK; + block->track = unbcd[q[1]]; + block->index = unbcd[q[2]]; + block->minute = unbcd[q[7]]; + block->second = unbcd[q[8]]; + block->frame = unbcd[q[9]]; + } + } + + return (cdda.cdda_length * CDDABLKSIZE); +} + +/* + * Normalize a bunch of CDDA data. Basically this means ripping out the + * Q subchannel data and doing byte-swapping, since the CD audio is in + * littleendian format. + * + * Scanning is handled here too. + * + * XXX - do byte swapping on Intel boxes? + */ +long +wmcdda_normalize(unsigned char *rawbuf, long buflen, struct cdda_block *block) +{ + int i, nextq; + int blocks = buflen / CDDABLKSIZE; + unsigned char *dest = rawbuf; + unsigned char tmp; + long *buf32 = (long *)rawbuf, tmp32; + +/* + * this was #ifndef LITTLEENDIAN + * in wmcdda it was called LITTLE_ENDIAN. Was this a flaw? + */ +#if WM_BIG_ENDIAN + if (blocks--) + for (i = 0; i < SAMPLES_PER_BLK * 2; i++) + { + /* Only need to use temp buffer on first block. */ + tmp = *rawbuf++; + *dest++ = *rawbuf++; + *dest++ = tmp; + } +#endif + + while (blocks--) + { + /* Skip over Q data. */ + rawbuf += 16; + + for (i = 0; i < SAMPLES_PER_BLK * 2; i++) + { +#if WM_LITTLE_ENDIAN + *dest++ = *rawbuf++; + *dest++ = *rawbuf++; +#else + *dest++ = rawbuf[1]; + *dest++ = rawbuf[0]; + rawbuf += 2; +#endif + } + } + + buflen -= ((buflen / CDDABLKSIZE) * 16); + + /* + * Reverse the data here if we're playing backwards. + * XXX - ideally this should be done above. + */ + if (direction < 0) + { + buflen /= 4; /* we can move 32 bits at a time. */ + + for (i = 0; i < buflen / 2; i++) + { + tmp32 = buf32[i]; + buf32[i] = buf32[buflen - i - 1]; + buf32[buflen - i - 1] = tmp32; + } + + buflen *= 4; + } + + return (buflen); +} + +/* + * Set the playback direction. + */ +void +wmcdda_direction(int newdir) +{ + if (newdir == 0) + { + numblocks = 20; + direction = 1; + } + else + { + numblocks = 30; + direction = -1; + } +} + +/* + * Do system-specific stuff to get ready to play at a particular speed. + */ +void +wmcdda_speed(int speed) +{ + if (speed > 128) + numblocks = 12; + else + numblocks = direction > 0 ? 20 : 30; +} + +#endif /* } */ diff --git a/ascd/libworkman/plat_svr4.c b/ascd/libworkman/plat_svr4.c new file mode 100644 index 0000000..8f01739 --- /dev/null +++ b/ascd/libworkman/plat_svr4.c @@ -0,0 +1,458 @@ +/* + * $Id: plat_svr4.c,v 1.7 1999/03/07 08:36:41 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * SVR4 specific. Much of this is similar to plat_hpux.c. + */ + +#if defined(SVR4) && !defined(sun) && !defined(__sun__) && !defined(__sony_news) + +static char plat_svr4_id[] = "$Id: plat_svr4.c,v 1.7 1999/03/07 08:36:41 dirk Exp $"; + +#include <sys/types.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <sys/mkdev.h> +#include <sys/stat.h> +#include <sys/sdi.h> +#include <sys/sdi_edt.h> +#include <sys/scsi.h> +#include <errno.h> + +#include "include/wm_config.h" +#include "include/wm_struct.h" + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + + +void *malloc(); +char *strchr(); + +int min_volume = 0; +int max_volume = 255; + +extern char *cd_device; + +/* + * Initialize the drive. A no-op for the generic driver. + */ +int +gen_init(d) + struct wm_drive *d; +{ + return (0); +} + +/* + * Get the number of tracks on the CD. + */ +int +gen_get_trackcount(d, tracks) + struct wm_drive *d; + int *tracks; +{ + return (wm_scsi2_get_trackcount(d, tracks)); +} + +/* + * Get the start time and mode (data or audio) of a track. + */ +int +gen_get_trackinfo(d, track, data, startframe) + struct wm_drive *d; + int track, *data, *startframe; +{ + return (wm_scsi2_get_trackinfo(d, track, data, startframe)); +} + +/* + * Get the number of frames on the CD. + */ +int +gen_get_cdlen(d, frames) + struct wm_drive *d; + int *frames; +{ + int tmp; + + return (wm_scsi2_get_cdlen(d, frames)); +} + +/* + * Get the current status of the drive: the current play mode, the absolute + * position from start of disc (in frames), and the current track and index + * numbers if the CD is playing or paused. + */ +int +gen_get_drive_status(d, oldmode, mode, pos, track, index) + struct wm_drive *d; + enum wm_cd_modes oldmode, *mode; + int *pos, *track, *index; +{ + return (wm_scsi2_get_drive_status(d, oldmode, mode, pos, track, index)); +} + +/* + * Set the volume level for the left and right channels. Their values + * range from 0 to 100. + */ +int +gen_set_volume(d, left, right) + struct wm_drive *d; + int left, right; +{ + return (wm_scsi2_set_volume(d, left, right)); +} + +/* + * Read the initial volume from the drive, if available. Each channel + * ranges from 0 to 100, with -1 indicating data not available. + */ +int +gen_get_volume(d, left, right) + struct wm_drive *d; + int *left, *right; +{ + return (wm_scsi2_get_volume(d, left, right)); +} + +/* + * Pause the CD. + */ +int +gen_pause(d) + struct wm_drive *d; +{ + return (wm_scsi2_pause(d)); +} + +/* + * Resume playing the CD (assuming it was paused.) + */ +int +gen_resume(d) + struct wm_drive *d; +{ + return (wm_scsi2_resume(d)); +} + +/* + * Stop the CD. + */ +int +gen_stop(d) + struct wm_drive *d; +{ + return (wm_scsi2_stop(d)); +} + +/* + * Play the CD from one position to another (both in frames.) + */ +int +gen_play(d, start, end) + struct wm_drive *d; + int start, end; +{ + return (wm_scsi2_play(d, start, end)); +} + +/* + * Eject the current CD, if there is one. + */ +int +gen_eject(struct wm_drive *d) +{ + return (wm_scsi2_eject(d)); +} /* gen_eject() */ + +/* + * Close the tray. + * please review scsi.c / wm_scsi2_closetray() + * and send changes to [email protected] + */ +int +gen_closetray( struct wm_drive *d ) +{ + return(wm_scsi2_closetray(d)); +} /* gen_closetray() */ + +static int +create_cdrom_node(char *dev_name) +{ + char pass_through[100]; + int file_des; + dev_t pass_thru_device; + int err; + int ccode; + + + strcpy(pass_through, dev_name); + strcat(pass_through, "p" ); + + if (setreuid(-1,0) < 0) + { + perror("setregid/setreuid/access"); + exit(1); + } + + ccode = access(pass_through, F_OK); + + if (ccode < 0) + { + if ((file_des = open(dev_name, O_RDONLY)) < 0) + { + perror("open cdrom devices failed"); + return -1; + } + + if (ioctl(file_des, B_GETDEV, &pass_thru_device) < 0) + { + perror("Call to get pass-through device number failed"); + return -1; + } + + (void)close(file_des); + + if (mknod(pass_through, (S_IFCHR | S_IREAD | S_IWRITE), + pass_thru_device) < 0) + { + perror("Unable to make pass-through node"); + return -1; + } + + if (chown(pass_through, 0 , 0) < 0) + { + perror("chown"); + return -1; + } + + if (chmod(pass_through, 0660 ) < 0) + { + perror("chmod"); + return -1; + } + } + + file_des = open( pass_through, O_RDWR); + err = errno; + + if ( (setreuid(-1,getuid()) < 0) || (setregid(-1,getgid()) < 0) ) + { + perror("setreuid/setregid"); + exit(1); + } + errno = err; + return file_des; +} + +/* + * Open the CD and figure out which kind of drive is attached. + */ +int +wmcd_open(d) + struct wm_drive *d; +{ + int fd, flag = 1; + static int warned = 0; + char vendor[32] = WM_STR_GENVENDOR; + char model[32] = WM_STR_GENMODEL; + char rev[32] = WM_STR_GENREV; + + if (d->fd >= 0) /* Device already open? */ + return (0); + + if (cd_device == NULL) + cd_device = DEFAULT_CD_DEVICE; + + d->fd = create_cdrom_node(cd_device); /* this will do open */ + + if (d->fd < 0) + { + if (errno == EACCES) + { + if (! warned) + { + fprintf(stderr,"Cannot access %s\n",cd_device); + warned++; + } + } + else if (errno != EINTR) + { + perror(cd_device); + exit(1); + } + + /* No CD in drive. (Is this true also for svr4 ? XXX ) */ + return (1); + } + + if (warned) + { + warned = 0; + fprintf(stderr, "Thank you.\n"); + } + + /* Now fill in the relevant parts of the wm_drive structure. */ + + fd = d->fd; + + if (wm_scsi_get_drive_type(d, vendor, model, rev) < 0) + { + perror("Cannot inquiry drive for it's type"); + exit(1); + } + *d = *(find_drive_struct(vendor, model, rev)); + wm_drive_settype(vendor, model, rev); + + d->fd = fd; + + return (0); +} /* wmcd_open() */ + +/* + * Re-Open the device if it is open. + */ +int +wmcd_reopen( struct wm_drive *d ) +{ + int status; + + do { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "wmcd_reopen "); + if (d->fd >= 0) /* Device really open? */ + { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "closes the device and "); + status = close( d->fd ); /* close it! */ + /* we know, that the file is closed, do we? */ + d->fd = -1; + } + wm_susleep( 1000 ); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "calls wmcd_open()\n"); + status = wmcd_open( d ); /* open it as usual */ + wm_susleep( 1000 ); + } while ( status != 0 ); + return status; +} /* wmcd_reopen() */ + + + + +/* + * Send a SCSI command out the bus. + */ +int +wm_scsi(d, xcdb, cdblen, retbuf, retbuflen, getreply) + struct wm_drive *d; + unsigned char *xcdb; + int cdblen; + int getreply; + char *retbuf; + int retbuflen; +{ + int ccode; + int file_des = d->fd; + int i,j; + unsigned char sense_buffer[ SENSE_SZ ]; + int errno_save; + + /* getreply == 1 is read, == 0 is write */ + + struct sb sb; + struct scs scs; + + sb.sb_type = ISCB_TYPE; + + sb.SCB.sc_comp_code = SDI_PROGRES; + sb.SCB.sc_int = NULL; + sb.SCB.sc_wd = 0; + sb.SCB.sc_dev.sa_major = 0; + sb.SCB.sc_dev.sa_minor = 0; + sb.SCB.sc_dev.sa_lun = 0; + sb.SCB.sc_dev.sa_exlun = 0; + sb.SCB.sc_status = 0; + sb.SCB.sc_link = (struct sb *) NULL; + sb.SCB.sc_resid = 0; + + sb.SCB.sc_cmdpt = (void *)xcdb; + sb.SCB.sc_cmdsz = cdblen; + + sb.SCB.sc_datapt = retbuf ; + sb.SCB.sc_datasz = retbuflen ; + + if (getreply == 1) + sb.SCB.sc_mode = SCB_READ; + else + sb.SCB.sc_mode = SCB_WRITE; + + sb.SCB.sc_time = 500; + + ccode = ioctl(file_des, SDI_SEND, &sb); + + if ( (sb.SCB.sc_comp_code != 0xd000000e ) || + ( sb.SCB.sc_status != 02) ) + return ccode; + + errno_save = errno; + + sb.SCB.sc_comp_code = SDI_PROGRES; + sb.SCB.sc_int = NULL; + sb.SCB.sc_wd = 0; + sb.SCB.sc_dev.sa_major = 0; + sb.SCB.sc_dev.sa_minor = 0; + sb.SCB.sc_dev.sa_lun = 0; + sb.SCB.sc_dev.sa_exlun = 0; + sb.SCB.sc_status = 0; + sb.SCB.sc_link = (struct sb *) NULL; + sb.SCB.sc_resid = 0; + + scs.ss_op = SS_REQSEN; + scs.ss_lun = 0; + scs.ss_addr1 = 0; + scs.ss_addr = 0; + scs.ss_len = SENSE_SZ; + scs.ss_cont = 0; + + sb.SCB.sc_cmdpt = SCS_AD(&scs); + sb.SCB.sc_cmdsz = SCS_SZ; + sb.SCB.sc_datapt = sense_buffer; + sb.SCB.sc_datasz = 18; + sb.SCB.sc_mode = SCB_READ; + sb.SCB.sc_time = 5000; + + if (ioctl(file_des, SDI_SEND, &sb) < 0) + { + fprintf(stderr,"Cannot read sense.\n"); + exit(-1); + } + + errno=errno_save; + return -1; +} + +#endif diff --git a/ascd/libworkman/plat_template.c b/ascd/libworkman/plat_template.c new file mode 100644 index 0000000..3e7467a --- /dev/null +++ b/ascd/libworkman/plat_template.c @@ -0,0 +1,269 @@ +/* + * $Id$ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + */ + + +#if [TEMPLATESYSTEM] + +static char plat_template_id[] = "$Id$" + + +#include "include/wm_config.h" +#include "include/wm_struct.h" + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +/* + * gen_init(); + * + */ + +int gen_init(struct wm_drive *d) +{ + return (0); +} + +/* + * gen_get_trackcount() + * + */ + +int gen_get_trackcount(struct wm_drive *d,int *tracks) +{ + return (0); +} + + +/* + * gen_get_cdlen() + * + */ + +int gen_get_cdlen(struct wm_drive *d,int *frames) +{ + return (0); +} + +/* + * gen_get_trackinfo() + * + */ + +int +gen_get_trackinfo(struct wm_drive *d,int track,int *data,int *startframe) +{ + return (0); +} + +/* + * gen_get_drive_status() + * + */ + +int gen_get_drive_status(struct wm_drive *d, + enum wm_cd_modes oldmode, + enum wm_cd_modes *mode, + int *pos, + int *track, + int *index) +{ + return (0); +} + + +int scale_volume(int vol,int max) +{ + return ((vol * (max_volume - min_volume)) / max + min_volume); +} + +int unscale_volume(int vol,int max) +{ + int n; + n = ( vol - min_volume ) * max_volume / (max - min_volume); + return (n <0)?0:n; +} + +/* + * gen_get_volume() + * + */ + +int gen_get_volume(struct wm_drive *d,int *left,int *right) +{ + return (0); +} + +/* + * gen_set_volume() + * + */ + +int gen_set_volume(struct wm_drive *d,int left,int right) +{ + return (0); +} + +/* + * gen_pause() + * + */ + +int gen_pause(struct wm_drive *d) +{ + return ioctl( 0 ); +} + +/* + * gen_resume + * + */ + +int gen_resume(struct wm_drive *d) +{ + return (0); +} + +/* + * gen_stop() + * + */ + +int gen_stop(struct wm_drive *d){ + return (0); +} + +/* + * gen_play() + * + */ + +int gen_play(struct wm_drive *d,int start,int end) +{ + return (0); +} + +/* + * gen_eject() + * + */ + +int gen_eject(struct wm_drive *d) +{ + return (0); +} + + +/*----------------------------------------* + * Close the CD tray + *----------------------------------------*/ +int gen_closetray(struct wm_drive *d) +{ +#ifdef CAN_CLOSE + if(!close(d->fd)) + { + d->fd=-1; + return(wmcd_reopen(d)); + } else { + return(-1); + } +#else + /* Always succeed if the drive can't close */ + return(0); +#endif /* CAN_CLOSE */ +} /* gen_closetray() */ + + +/* + * wm_scsi() + * + */ +int wm_scsi(struct wm_drive *d, + uchar_t *cdb, int cdblen,void *retbuf,int retbuflen,int getreply) +{ + return (0); +} + + +/* + * wmcd_open() + * + */ + +int wmcd_open(struct wm_drive *d) +{ + char vendor[32] = WM_STR_GENVENDOR; + char model[32] = WM_STR_GENMODEL; + char rev[32] = WM_STR_GENREV; + + int fd; + + if( ! d ){ + errno = EFAULT; + return -1; + } + + if(d->fd > -1) /* device already open? */ + return 0; + + if( cd_device == (char *)NULL ) + cd_device = DEFAULT_CD_DEVICE; + + /* open() goes here */ + + *d = *(find_drive_struct(vendor, model, rev)); + wm_drive_settype(vendor, model, rev); + + d->fd = fd; + d->init(d); + + return (0); +} /* wmcd_open() */ + +/* + * Re-Open the device if it is open. + */ +int +wmcd_reopen( struct wm_drive *d ) +{ + int status; + + do { + wm_lib_message(WM_MSG_LEVEL_DEBUG, "wmcd_reopen "); + if (d->fd >= 0) /* Device really open? */ + { + wm_lib_message(WM_MSG_LEVEL_DEBUG, "closes the device and "); + status = close( d->fd ); /* close it! */ + /* we know, that the file is closed, do we? */ + d->fd = -1; + } + wm_susleep( 1000 ); + wm_lib_message(WM_MSG_LEVEL_DEBUG, "calls wmcd_open()\n"); + status = wmcd_open( d ); /* open it as usual */ + wm_susleep( 1000 ); + } while ( status != 0 ); + return status; +} /* wmcd_reopen() */ + +#endif /* TEMPLATESYSTEM */ diff --git a/ascd/libworkman/plat_ultrix.c b/ascd/libworkman/plat_ultrix.c new file mode 100644 index 0000000..d74af38 --- /dev/null +++ b/ascd/libworkman/plat_ultrix.c @@ -0,0 +1,666 @@ +/* + * $Id: plat_ultrix.c,v 1.6 1999/03/07 08:36:41 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * ULTRIX 4.2 drive control routines. + */ + +#if defined(ultrix) || defined(__ultrix) + +static char plat_ultrix_id[] = "$Id: plat_ultrix.c,v 1.6 1999/03/07 08:36:41 dirk Exp $"; + +#include <errno.h> +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <ustat.h> +#include <string.h> +#include <sys/rzdisk.h> +#include <sys/cdrom.h> + +#include "include/wm_config.h" +#include "include/wm_struct.h" + +#define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM + +/* + * This structure will be filled with the TOC header and all entries. + * Ultrix doesn't seem to allow getting single TOC entries. + * - Chris Ross ([email protected]) + */ +struct cd_toc_header_and_entries { + struct cd_toc_header cdth; + struct cd_toc_entry cdte[CDROM_MAX_TRACK+1]; +}; + +void *malloc(); +char *strchr(); + +int min_volume = 128; +int max_volume = 255; + +extern char *cd_device; + +/* + * find_cdrom + * + * Determine the name of the CD-ROM device. + * + * Read through the boot records (via a call to uerf) and find the SCSI + * address of the CD-ROM. If the "CDROM" environment variable is set, + * use that instead. + */ +void +find_cdrom() +{ + char *data, *fgetline(); + FILE *uerf; + int fds[2]; + int pid; + + if ((cd_device = getenv("CDROM")) != NULL) + return; + + pipe(fds); + + if ((pid = fork()) == 0) { + close(fds[0]); + dup2(fds[1], 1); + execl("/etc/uerf", "uerf", "-R", "-r", "300", NULL); + execl("/usr/sbin/uerf", "uerf", "-R", "-r", "300", NULL); + _exit(1); + } else if (pid < 0) { + perror("fork"); + exit(1); + } + + close(fds[1]); + uerf = fdopen(fds[0], "r"); + + while (data = fgetline(uerf)) + if (strstr(data, "RRD42")) { + char *device; + + cd_device = (char *)malloc(sizeof("/dev/rrz##c")); + strcpy(cd_device, "/dev/r"); + device = strstr(data, "rz"); + device[(int)(strchr(device, ' ') - device)] = '\0'; + strcat(cd_device, device); + strcat(cd_device, "c"); + break; + } + + fclose(uerf); + + if (cd_device == NULL) { + fprintf(stderr, + "No cdrom (RRD42) is installed on this system\n"); + exit(1); + } + + kill(pid, 15); + (void)wait((int *)NULL); +} + +/* + * Initialize the drive. A no-op for the generic driver. + */ +int +gen_init(d) + struct wm_drive *d; +{ + return (0); +} + +/* + * Get the number of tracks on the CD. + */ +int +gen_get_trackcount(d, tracks) + struct wm_drive *d; + int *tracks; +{ + struct cd_toc_header hdr; + + if (ioctl(d->fd, CDROM_TOC_HEADER, &hdr)) + return (-1); + + *tracks = hdr.th_ending_track; + + return (0); +} + +/* + * Get the start time and mode (data or audio) of a track. + * + * XXX - this should get cached, but that means keeping track of ejects. + */ +int +gen_get_trackinfo(d, track, data, startframe) + struct wm_drive *d; + int track, *data, *startframe; +{ + struct cd_toc toc; + struct cd_toc_header hdr; + struct cd_toc_header_and_entries toc_buffer; + + if (ioctl(d->fd, CDROM_TOC_HEADER, &hdr)) + return (-1); + + bzero((char *)&toc_buffer, sizeof(toc_buffer)); + toc.toc_address_format = CDROM_MSF_FORMAT; + toc.toc_starting_track = 0; + toc.toc_alloc_length = (u_short)(((hdr.th_data_len1 << 8) + + hdr.th_data_len0) & 0xfff) + 2; + toc.toc_buffer = (caddr_t)&toc_buffer; + + if (ioctl(d->fd, CDROM_TOC_ENTRYS, &toc)) + return (-1); + + if (track == 0) + track = hdr.th_ending_track + 1; + + *data = (toc_buffer.cdte[track-1].te_control & CDROM_DATA_TRACK) ? 1:0; + *startframe = toc_buffer.cdte[track - 1].te_absaddr.msf.m_units*60*75 + + toc_buffer.cdte[track - 1].te_absaddr.msf.s_units * 75 + + toc_buffer.cdte[track - 1].te_absaddr.msf.f_units; + + return (0); +} + +/* + * Get the number of frames on the CD. + */ +int +gen_get_cdlen(d, frames) + struct wm_drive *d; + int *frames; +{ + int tmp; + + return (gen_get_trackinfo(d, 0, &tmp, frames)); +} + +/* + * Get the current status of the drive: the current play mode, the absolute + * position from start of disc (in frames), and the current track and index + * numbers if the CD is playing or paused. + */ +int +gen_get_drive_status(d, oldmode, mode, pos, track, index) + struct wm_drive *d; + enum wm_cd_modes oldmode, *mode; + int *pos, *track, *index; +{ + struct cd_sub_channel sc; + struct cd_subc_channel_data scd; + + /* If we can't get status, the CD is ejected, so default to that. */ + *mode = WM_CDM_EJECTED; + + sc.sch_address_format = CDROM_MSF_FORMAT; + sc.sch_data_format = CDROM_CURRENT_POSITION; + sc.sch_track_number = 0; + sc.sch_alloc_length = sizeof(scd); + sc.sch_buffer = (caddr_t)&scd; + + /* Is the device open? */ + if (d->fd < 0) + { + switch (wmcd_open(d)) { + case -1: /* error */ + return (-1); + + case 1: /* retry */ + return (0); + } + } + + if (ioctl(d->fd, CDROM_READ_SUBCHANNEL, &sc)) + return (0); /* ejected */ + + switch (scd.scd_header.sh_audio_status) { + case AS_PLAY_IN_PROGRESS: + *mode = WM_CDM_PLAYING; +dopos: + *pos = scd.scd_position_data.scp_absaddr.msf.m_units * 60 * 75 + + scd.scd_position_data.scp_absaddr.msf.s_units * 75 + + scd.scd_position_data.scp_absaddr.msf.f_units; + *track = scd.scd_position_data.scp_track_number; + *index = scd.scd_position_data.scp_index_number; + break; + + case AS_PLAY_PAUSED: + if (oldmode == WM_CDM_PLAYING || oldmode == WM_CDM_PAUSED) + { + *mode = WM_CDM_PAUSED; + goto dopos; + } + else + *mode = WM_CDM_STOPPED; + break; + + case AS_PLAY_COMPLETED: + *mode = WM_CDM_TRACK_DONE; /* waiting for next track. */ + break; + + case AS_NO_STATUS: + *mode = WM_CDM_STOPPED; + break; + } + + return (0); +} + +/* + * scale_volume(vol, max) + * + * Return a volume value suitable for passing to the CD-ROM drive. "vol" + * is a volume slider setting; "max" is the slider's maximum value. + * + * On Sun and DEC CD-ROM drives, the amount of sound coming out the jack + * increases much faster toward the top end of the volume scale than it + * does at the bottom. To make up for this, we make the volume scale look + * sort of logarithmic (actually an upside-down inverse square curve) so + * that the volume value passed to the drive changes less and less as you + * approach the maximum slider setting. The actual formula looks like + * + * (max^2 - (max - vol)^2) * (max_volume - min_volume) + * v = --------------------------------------------------- + min_volume + * max^2 + * + * If your system's volume settings aren't broken in this way, something + * like the following should work: + * + * return ((vol * (max_volume - min_volume)) / max + min_volume); + */ +scale_volume(vol, max) + int vol, max; +{ + return ((max * max - (max - vol) * (max - vol)) * + (max_volume - min_volume) / (max * max) + min_volume); +} + +/* + * Set the volume level for the left and right channels. Their values + * range from 0 to 100. + */ +int +gen_set_volume(d, left, right) + struct wm_drive *d; + int left, right; +{ + struct cd_playback pb; + struct cd_playback_status ps; + struct cd_playback_control pc; + + left = scale_volume(left, 100); + right = scale_volume(right, 100); + + bzero((char *)&pb, sizeof(pb)); + bzero((char *)&ps, sizeof(ps)); + bzero((char *)&pc, sizeof(pc)); + + pb.pb_alloc_length = sizeof(ps); + pb.pb_buffer = (caddr_t)&ps; + + if (ioctl(d->fd, CDROM_PLAYBACK_STATUS, &pb)) + return (-1); + + pc.pc_chan0_select = ps.ps_chan0_select; + pc.pc_chan0_volume = (left < CDROM_MIN_VOLUME) ? + CDROM_MIN_VOLUME : (left > CDROM_MAX_VOLUME) ? + CDROM_MAX_VOLUME : left; + pc.pc_chan1_select = ps.ps_chan1_select; + pc.pc_chan1_volume = (right < CDROM_MIN_VOLUME) ? + CDROM_MIN_VOLUME : (right > CDROM_MAX_VOLUME) ? + CDROM_MAX_VOLUME : right; + + pb.pb_alloc_length = sizeof(pc); + pb.pb_buffer = (caddr_t)&pc; + + if (ioctl(d->fd, CDROM_PLAYBACK_CONTROL, &pb)) + return (-1); + + return (0); +} + +/* + * Pause the CD. + */ +int +gen_pause(d) + struct wm_drive *d; +{ + return (ioctl(d->fd, CDROM_PAUSE_PLAY)); +} + +/* + * Resume playing the CD (assuming it was paused.) + */ +int +gen_resume(d) + struct wm_drive *d; +{ + return (ioctl(d->fd, CDROM_RESUME_PLAY)); +} + +/* + * Stop the CD. + */ +int +gen_stop(d) + struct wm_drive *d; +{ + return (ioctl(d->fd, SCSI_STOP_UNIT)); +} + +/* + * Play the CD from one position to another (both in frames.) + */ +int +gen_play(d, start, end) + struct wm_drive *d; + int start, end; +{ + struct cd_play_audio_msf msf; + + msf.msf_starting_M_unit = start / (60*75); + msf.msf_starting_S_unit = (start % (60*75)) / 75; + msf.msf_starting_F_unit = start % 75; + msf.msf_ending_M_unit = end / (60*75); + msf.msf_ending_S_unit = (end % (60*75)) / 75; + msf.msf_ending_F_unit = end % 75; + + if (ioctl(d->fd, SCSI_START_UNIT)) + return (-1); + if (ioctl(d->fd, CDROM_PLAY_MSF, &msf)) + return (-2); + + return (0); +} + +/* + * Eject the current CD, if there is one. + */ +int +gen_eject(struct wm_drive *d) +{ + /* On some systems, we can check to see if the CD is mounted. */ + struct stat stbuf; + struct ustat ust; + + if (fstat(d->fd, &stbuf) != 0) + return (-2); + + /* Is this a mounted filesystem? */ + if (ustat(stbuf.st_rdev, &ust) == 0) + return (-3); + + return (ioctl(d->fd, CDROM_EJECT_CADDY)); +} /* gen_eject() */ + +/*----------------------------------------* + * Close the CD tray + * + * Please edit and send changes to + * [email protected] + *----------------------------------------*/ + +int gen_closetray(struct wm_drive *d) +{ +#ifdef CAN_CLOSE + if(!close(d->fd)) + { + d->fd=-1; + return(wmcd_reopen(d)); + } else { + return(-1); + } +#else + /* Always succeed if the drive can't close */ + return(0); +#endif /* CAN_CLOSE */ +} /* gen_closetray() */ + + +/* + * unscale_volume(cd_vol, max) + * + * Given a value between min_volume and max_volume, return the volume slider + * value needed to achieve that value. + * + * Rather than perform floating-point calculations to reverse the above + * formula, we simply do a binary search of scale_volume()'s return values. + */ +static int +unscale_volume(cd_vol, max) + int cd_vol, max; +{ + int vol = 0, top = max, bot = 0, scaled; + + while (bot <= top) + { + vol = (top + bot) / 2; + scaled = scale_volume(vol, max); + if (cd_vol == scaled) + break; + if (cd_vol < scaled) + top = vol - 1; + else + bot = vol + 1; + } + + if (vol < 0) + vol = 0; + else if (vol > max) + vol = max; + + return (vol); +} + +/* + * Read the initial volume from the drive, if available. Each channel + * ranges from 0 to 100, with -1 indicating data not available. + */ +int +gen_get_volume(d, left, right) + struct wm_drive *d; + int *left, *right; +{ + struct cd_playback pb; + struct cd_playback_status ps; + + bzero((char *)&pb, sizeof(pb)); + bzero((char *)&ps, sizeof(ps)); + + pb.pb_alloc_length = sizeof(ps); + pb.pb_buffer = (caddr_t)&ps; + + if (d->fd >= 0) + { + if (ioctl(d->fd, CDROM_PLAYBACK_STATUS, &pb)) + *left = *right = -1; + else + { + *left = unscale_volume(ps.ps_chan0_volume, 100); + *right = unscale_volume(ps.ps_chan1_volume, 100); + } + } + else + *left = *right = -1; + + return (0); +} + +/* + * Send an arbitrary SCSI command to a device. + */ +int +wm_scsi(d, cdb, cdblen, retbuf, retbuflen, getreply) + struct wm_drive *d; + unsigned char *cdb; + int cdblen; + void *retbuf; + int retbuflen; + int getreply; +{ + /* ULTRIX doesn't have a SCSI passthrough interface, does it? */ + return (-1); +} + +/* + * fgetline() + * + * Simulate fgets, but joining continued lines in the output of uerf. + */ + +#define BUF_SIZE 85 /* Max length of a (real) line */ + +char * +fgetline(fp) +FILE *fp; +{ + static char *retval = NULL; + static char holdbuf[BUF_SIZE + 1]; + char tmp[BUF_SIZE + 1]; + char *stmp; + + if (!retval) { + retval = malloc(BUF_SIZE * 3); /* 3 lines can be joined */ + if (!retval) + return(NULL); + else + *retval = '\0'; + } + + if (*holdbuf) { + strcpy(retval, holdbuf); + retval[strlen(retval)-1] = '\0'; + memset(holdbuf, 0, BUF_SIZE+1); + } + while (fgets(tmp, BUF_SIZE, fp)) { + stmp = tmp + strspn(tmp, " \t"); + if (*stmp == '_') { /* Continuation line */ + retval[strlen(retval)-1] = '\0'; /* Trim off C/R */ + strcat(retval, stmp+1); + } else { + if (*retval) { + strcpy(holdbuf, tmp); + holdbuf[strlen(holdbuf)-1] = -1; + return retval; + } else { /* First line read, keep reading */ + strcat(retval, stmp); + retval[strlen(retval)-1] = '\0'; + } + } + } + return NULL; +} + +/* + * Open the CD device and figure out what kind of drive is attached. + */ +int +wmcd_open(d) + struct wm_drive *d; +{ + int fd; + static int warned = 0; + + if (d->fd >= 0) /* Device already open? */ + return (0); + + if (cd_device == NULL) + find_cdrom(); + + d->fd = open(cd_device, 0); + if (d->fd < 0) + { + if (errno == EACCES) + { + if (!warned) + { + fprintf(stderr, + "As root, please run\n\nchmod 666 %s\n\n%s\n", cd_device, + "to give yourself permission to access the CD-ROM device."); + warned++; + } + } + else if (errno != EINTR) + { + perror(cd_device); + exit(1); + } + + /* No CD in drive. */ + return (1); + } + + if (warned) + { + warned = 0; + fprintf(stderr, "Thank you.\n"); + } + + /* Now fill in the relevant parts of the wm_drive structure. */ + fd = d->fd; + *d = *(find_drive_struct("", "", "")); + d->fd = fd; + + (d->init)(d); + + return (0); +} /* wmcd_open() */ + +/* + * Re-Open the device if it is open. + */ +int +wmcd_reopen( struct wm_drive *d ) +{ + int status; + + do { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "wmcd_reopen "); + if (d->fd >= 0) /* Device really open? */ + { + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "closes the device and "); + status = close( d->fd ); /* close it! */ + /* we know, that the file is closed, do we? */ + d->fd = -1; + } + wm_susleep( 1000 ); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "calls wmcd_open()\n"); + status = wmcd_open( d ); /* open it as usual */ + wm_susleep( 1000 ); + } while ( status != 0 ); + return status; +} /* wmcd_reopen() */ + + + +#endif diff --git a/ascd/libworkman/scsi.c b/ascd/libworkman/scsi.c new file mode 100644 index 0000000..292f744 --- /dev/null +++ b/ascd/libworkman/scsi.c @@ -0,0 +1,531 @@ +/* + * $Id: scsi.c,v 1.8 1999/06/17 06:48:03 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Frontend functions for sending raw SCSI commands to the CD-ROM drive. + * These depend on wm_scsi(), which should be defined in each platform + * module. + */ + +static char scsi_id[] = "$Id: scsi.c,v 1.8 1999/06/17 06:48:03 dirk Exp $"; + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "include/wm_config.h" +#include "include/wm_struct.h" +#include "include/wm_scsi.h" +#include "include/wm_platform.h" +#include "include/wm_helpers.h" +#include "include/wm_cdrom.h" + +#define SCMD_INQUIRY 0x12 +#define SCMD_MODE_SELECT 0x15 +#define SCMD_MODE_SENSE 0x1a +#define SCMD_START_STOP 0x1b +#define SCMD_PREVENT 0x1e +#define SCMD_READ_SUBCHANNEL 0x42 +#define SCMD_READ_TOC 0x43 +#define SCMD_PLAY_AUDIO_MSF 0x47 +#define SCMD_PAUSE_RESUME 0x4b + +#define SUBQ_STATUS_INVALID 0x00 +#define SUBQ_STATUS_PLAY 0x11 +#define SUBQ_STATUS_PAUSE 0x12 +#define SUBQ_STATUS_DONE 0x13 +#define SUBQ_STATUS_ERROR 0x14 +#define SUBQ_STATUS_NONE 0x15 +#define SUBQ_STATUS_NO_DISC 0x17 /* Illegal, but Toshiba returns it. */ +#define SUBQ_ILLEGAL 0xff + +#define PAGE_AUDIO 0x0e +#define LEADOUT 0xaa + +#define WM_MSG_CLASS WM_MSG_CLASS_SCSI + +/* + * Send a SCSI command over the bus, with all the CDB bytes specified + * as unsigned char parameters. This doesn't use varargs because some + * systems have stdargs instead and the number of bytes in a CDB is + * limited to 12 anyway. + * + * d Drive structure + * buf Buffer for data, both sending and receiving + * len Size of buffer + * dir TRUE if the command expects data to be returned in the buffer. + * a0- CDB bytes. Either 6, 10, or 12 of them, depending on the command. + */ +/*VARARGS4*/ +int +sendscsi( struct wm_drive *d, void *buf, + unsigned int len, int dir, + unsigned char a0, unsigned char a1, + unsigned char a2, unsigned char a3, + unsigned char a4, unsigned char a5, + unsigned char a6, unsigned char a7, + unsigned char a8, unsigned char a9, + unsigned char a10, unsigned char a11 ) + +{ + int cdblen = 0; + unsigned char cdb[12]; + + cdb[0] = a0; + cdb[1] = a1; + cdb[2] = a2; + cdb[3] = a3; + cdb[4] = a4; + cdb[5] = a5; + + switch ((a0 >> 5) & 7) { + case 0: + cdblen = 6; + break; + + case 5: + cdb[10] = a10; + cdb[11] = a11; + cdblen = 12; + + case 1: + case 2: + case 6: /* assume 10-byte vendor-specific codes for now */ + cdb[6] = a6; + cdb[7] = a7; + cdb[8] = a8; + cdb[9] = a9; + if (! cdblen) + cdblen = 10; + break; + } + + return (wm_scsi(d, cdb, cdblen, buf, len, dir)); +} + +/* + * Send a MODE SENSE command and return the results (minus header cruft) + * in a user buffer. + * + * d Drive structure + * page Number of page to query (plus page control bits, if any) + * buf Result buffer + */ +int +wm_scsi_mode_sense( struct wm_drive *d, unsigned char page, unsigned char *buf ) +{ + unsigned char pagebuf[255]; + int status, i, len, offset; + + status = sendscsi(d, pagebuf, sizeof(pagebuf), 1, SCMD_MODE_SENSE, 0, + page, 0, sizeof(pagebuf), 0,0,0,0,0,0,0); + if (status < 0) + return (status); + + /* + * The first byte of the returned data is the transfer length. Then + * two more bytes and the length of whatever header blocks are in + * front of the page we want. + */ + len = pagebuf[0] - pagebuf[3] - 3; + offset = pagebuf[3] + 4; + for (i = 0; i < len; i++) + buf[i] = pagebuf[offset + i]; + + return (0); +} + +/* + * Send a MODE SELECT command. + * + * d Drive structure + * buf Page buffer (no need to put on block descriptors) + * len Size of page + */ +int +wm_scsi_mode_select(d, buf, len) + struct wm_drive *d; + unsigned char *buf; + unsigned char len; +{ + unsigned char pagebuf[255]; + int i; + + pagebuf[0] = pagebuf[1] = pagebuf[2] = pagebuf[3] = 0; + for (i = 0; i < (int) len; i++) + pagebuf[i + 4] = buf[i]; + + return (sendscsi(d, pagebuf, len + 4, 0, SCMD_MODE_SELECT, 0x10, 0, + 0, len + 4, 0,0,0,0,0,0,0)); +} + +/* + * Send an INQUIRY command to get the drive type. + * + * d Drive structure + * vendor Buffer for vendor name (8 bytes + null) + * model Buffer for model name (16 bytes + null) + * rev Buffer for revision level (4 bytes + null) + * + * The above string lengths apply to the SCSI INQUIRY command. The + * actual WorkMan drive structure reserves 31 bytes + NULL per entry. + * + * If the model name begins with "CD-ROM" and zero or more spaces, that will + * all be stripped off since it's just extra junk to WorkMan. + */ +int +wm_scsi_get_drive_type( struct wm_drive *d, char *vendor, + char *model, char *rev ) +{ +/* removed unsigned*/ + char *s, *t, buf[36]; + + wm_lib_message(WM_MSG_LEVEL_INFO|WM_MSG_CLASS, "Sending SCSI inquiry command..."); + if (sendscsi(d, buf, sizeof(buf), 1, SCMD_INQUIRY, 0, 0, 0, sizeof(buf), 0,0,0,0,0,0,0)) + { + sprintf( vendor, WM_STR_GENVENDOR); + sprintf( model, WM_STR_GENMODEL); + sprintf( rev, WM_STR_GENREV); + wm_lib_message(WM_MSG_LEVEL_ERROR|WM_MSG_CLASS, "\nSCSI inquiry command not supported by the hardware\n"); + return (WM_ERR_SCSI_INQUIRY_FAILED); + } + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "sent.\n"); + + memcpy(vendor, buf + 8, 8); + vendor[8] = '\0'; + memcpy(model, buf + 16, 16); + model[16] = '\0'; + memcpy(rev, buf + 32, 4); + rev[4] = '\0'; + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "SCSI Inquiry result: [%s|%s|%s]\n", vendor, model, rev); + + + /* Remove "CD-ROM " from the model. */ + if (! strncmp(model, "CD-ROM", 6)) + { + s = model + 6; + t = model; + while (*s == ' ' || *s == '\t') + s++; + while( (*t++ = *s++) ) + ; + } + wm_lib_message(WM_MSG_LEVEL_INFO|WM_MSG_CLASS, "Cooked data: %s %s rev. %s\n", vendor, model,rev); + return (0); +} /* wm_scsi_get_drive_type() */ + +/* + * Send a SCSI-2 PAUSE/RESUME command. "resume" is 1 to resume, 0 to pause. + */ +int +wm_scsi2_pause_resume(d, resume) + struct wm_drive *d; + int resume; +{ + return (sendscsi(d, NULL, 0, 0, SCMD_PAUSE_RESUME, 0, 0, 0, 0, 0, 0, + 0, resume ? 1 : 0, 0,0,0)); +} + +/* + * Send a SCSI-2 "prevent media removal" command. "prevent" is 1 to lock + * caddy in. + */ +int +wm_scsi2_prevent(d, prevent) + struct wm_drive *d; + int prevent; +{ + return (sendscsi(d, NULL, 0, 0, SCMD_PREVENT, 0, 0, 0, 0, 0, 0, + 0, prevent ? 1 : 0, 0,0,0)); +} + +/* + * Send a SCSI-2 PLAY AUDIO MSF command. Pass the starting and ending + * frame numbers. + */ +int +wm_scsi2_play(d, sframe, eframe) + struct wm_drive *d; + int sframe, eframe; +{ + return (sendscsi(d, NULL, 0, 0, SCMD_PLAY_AUDIO_MSF, 0, 0, + sframe / (60 * 75), (sframe / 75) % 60, sframe % 75, + eframe / (60 * 75), (eframe / 75) % 60, eframe % 75, + 0,0,0)); +} + +/* + * Send a SCSI-2 READ TOC command to get the data for a particular track. + * Fill in track information from the returned data. + */ +int +wm_scsi2_get_trackinfo(d, track, data, startframe) + struct wm_drive *d; + int track, *data, *startframe; +{ + unsigned char buf[12]; /* one track's worth of info */ + + if (sendscsi(d, buf, sizeof(buf), 1, SCMD_READ_TOC, 2, + 0, 0, 0, 0, track, sizeof(buf) / 256, + sizeof(buf) % 256, 0,0,0)) + return (-1); + + *data = buf[5] & 4 ? 1 : 0; + *startframe = buf[9] * 60 * 75 + buf[10] * 75 + buf[11]; + + return (0); +} + +/* + * Get the starting frame for the leadout area (which should be the same as + * the length of the disc as far as WorkMan is concerned). + */ +int +wm_scsi2_get_cdlen(d, frames) + struct wm_drive *d; + int *frames; +{ + int tmp; + + return (wm_scsi2_get_trackinfo(d, LEADOUT, &tmp, frames)); +} + +/* + * Get the current status of the drive by sending the appropriate SCSI-2 + * READ SUB-CHANNEL command. + */ +int +wm_scsi2_get_drive_status(d, oldmode, mode, pos, track, index) + struct wm_drive *d; + enum wm_cd_modes oldmode, *mode; + int *pos, *track, *index; +{ + unsigned char buf[48]; + + /* If we can't get status, the CD is ejected, so default to that. */ + *mode = WM_CDM_EJECTED; + + /* Is the device open? */ + if (d->fd < 0) + { +/* + * stupid somehow, but necessary this time + */ + switch( wmcd_open( d ) ) { + + case -1: /* error */ + return (-1); + + case 1: /* retry */ + return (0); + } + } + + /* If we can't read status, the CD has been ejected. */ + buf[1] = SUBQ_ILLEGAL; + if (sendscsi(d, buf, sizeof(buf), 1, SCMD_READ_SUBCHANNEL, 2, 64, 1, + 0, 0, 0, sizeof(buf) / 256, sizeof(buf) % 256, 0,0,0)) + return (0); + + switch (buf[1]) { + case SUBQ_STATUS_PLAY: + *mode = WM_CDM_PLAYING; + *track = buf[6]; + *index = buf[7]; + *pos = buf[9] * 60 * 75 + buf[10] * 75 + buf[11]; + break; + + case SUBQ_STATUS_PAUSE: + if (oldmode == WM_CDM_PLAYING || oldmode == WM_CDM_PAUSED) + { + *mode = WM_CDM_PAUSED; + *track = buf[6]; + *index = buf[7]; + *pos = buf[9] * 60 * 75 + + buf[10] * 75 + + buf[11]; + } + else + *mode = WM_CDM_STOPPED; + break; + + /* + * SUBQ_STATUS_DONE is sometimes returned when the CD is idle, + * even though the spec says it should only be returned when an + * audio play operation finishes. + */ + case SUBQ_STATUS_DONE: + case SUBQ_STATUS_NONE: + case SUBQ_STATUS_INVALID: + if (oldmode == WM_CDM_PLAYING) + *mode = WM_CDM_TRACK_DONE; + else + *mode = WM_CDM_STOPPED; + break; + + /* + * This usually means there's no disc in the drive. + */ + case SUBQ_STATUS_NO_DISC: + break; + + /* + * This usually means the user ejected the CD manually. + */ + case SUBQ_STATUS_ERROR: + break; + + case SUBQ_ILLEGAL: /* call didn't really succeed */ + break; + + default: + *mode = WM_CDM_UNKNOWN; +#ifdef DEBUG + if( getenv( "WORKMAN_DEBUG" ) != NULL ) + printf("wm_scsi2_get_drive_status: status is 0x%x\n", + buf[1]); +#endif + break; + } + + return (0); +} + +/* + * Get the number of tracks on the CD using the SCSI-2 READ TOC command. + */ +int +wm_scsi2_get_trackcount(d, tracks) + struct wm_drive *d; + int *tracks; +{ + unsigned char buf[4]; + + if (sendscsi(d, buf, sizeof(buf), 1, SCMD_READ_TOC, 0, + 0, 0, 0, 0, 0, sizeof(buf) / 256, + sizeof(buf) % 256, 0,0,0)) + return (-1); + + *tracks = buf[3] - buf[2] + 1; + return (0); +} + +/* + * Pause the CD. + */ +int +wm_scsi2_pause(d) + struct wm_drive *d; +{ + return (wm_scsi2_pause_resume(d, 0)); +} + +/* + * Resume playing after a pause. + */ +int +wm_scsi2_resume(d) + struct wm_drive *d; +{ + return (wm_scsi2_pause_resume(d, 1)); +} + +/* + * Stop playing the CD by sending a START STOP UNIT command. + */ +int +wm_scsi2_stop(d) + struct wm_drive *d; +{ + return (sendscsi(d, NULL, 0, 0, SCMD_START_STOP, 0, 0,0,0,0,0,0,0,0,0,0)); +} + +/* + * Eject the CD by sending a START STOP UNIT command. + */ +int +wm_scsi2_eject(struct wm_drive *d) +{ + /* Unlock the disc (possibly unnecessary). */ + if (wm_scsi2_prevent(d, 0)) + return (-1); + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "Issuing START_STOP for ejecting...\n"); + return (sendscsi(d, NULL, 0, 0, SCMD_START_STOP, 2, 0,0,0,0,0,0,0,0,0,0)); +} + +/* + * Something like a dummy. The SCSI-2 specs are too hard for me to + * understand here... + * + * If you have the correct command handy, please send the code to + * [email protected] + */ +int +wm_scsi2_closetray(struct wm_drive *d) +{ + wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "Issuing START_STOP for closing...\n"); + return (sendscsi(d, NULL, 0,0, SCMD_START_STOP, 2, 0,0,0,0,0,0,0,0,0,0)); +} + +/* + * Get the volume by doing a MODE SENSE command. + */ +int +wm_scsi2_get_volume(d, left, right) + struct wm_drive *d; + int *left, *right; +{ + unsigned char mode[16]; + + *left = *right = -1; + + /* Get the current audio parameters first. */ + if (wm_scsi_mode_sense(d, PAGE_AUDIO, mode)) + return (-1); + + *left = ((int) mode[9] * 100) / 255; + *right = ((int) mode[11] * 100) / 255; + + return (0); +} + +/* + * Set the volume by doing a MODE SELECT command. + */ +int +wm_scsi2_set_volume(d, left, right) + struct wm_drive *d; + int left, right; +{ + unsigned char mode[16]; + + /* Get the current audio parameters first. */ + if (wm_scsi_mode_sense(d, PAGE_AUDIO, mode)) + return (-1); + + /* Tweak the volume part of the parameters. */ + mode[9] = (left * 255) / 100; + mode[11] = (right * 255) / 100; + + /* And send them back to the drive. */ + return (wm_scsi_mode_select(d, mode, sizeof(mode))); +} diff --git a/ascd/libworkman/wm_helpers.c b/ascd/libworkman/wm_helpers.c new file mode 100644 index 0000000..c36e464 --- /dev/null +++ b/ascd/libworkman/wm_helpers.c @@ -0,0 +1,229 @@ +/* + * $Id: wm_helpers.c,v 1.7 1999/03/07 08:36:41 dirk Exp $ + * + * This file is part of WorkMan, the civilized CD player library + * (c) 1991-1997 by Steven Grimm (original author) + * (c) by Dirk Försterling (current 'author' = maintainer) + * The maintainer can be contacted by his e-mail address: + * [email protected] + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Some helpful functions... + * + */ + +static char wm_helpers_id[] = "$Id: wm_helpers.c,v 1.7 1999/03/07 08:36:41 dirk Exp $"; + +#include <stdio.h> +#include <string.h> +#include <malloc.h> +#include <errno.h> +#include <stdarg.h> +#include <sys/time.h> +#include "include/workman_defs.h" +#include "include/wm_config.h" +#include "include/wm_helpers.h" +#include "include/wm_struct.h" + +int wm_lib_verbosity = WM_MSG_LEVEL_NONE; + +/* + * Some seleced functions of version reporting follow... + */ + +int wm_libver_major( void ){return WM_LIBVER_MAJOR;} +int wm_libver_minor( void ){return WM_LIBVER_MINOR;} +int wm_libver_pl( void ){return WM_LIBVER_PL;} + +char *wm_libver_name( void ) +{ + char *s = NULL; + + wm_strmcat(&s, WM_LIBVER_NAME); + return s; +} /* wm_libver_name() */ + +char *wm_libver_number( void ) +{ + char *s = NULL; + + s = malloc(10); + /* this is not used very often, so don't care about speed...*/ + sprintf(s, "%d.%d.%d", wm_libver_major(), wm_libver_minor(), wm_libver_pl()); + return s; +} /* wm_libver_number() */ + +char *wm_libver_date( void ) +{ + char *s = NULL; + wm_strmcat(&s, __DATE__); + return s; +} /* wm_libver_date() */ + +char *wm_libver_string( void ) +{ + char *s = NULL; + + wm_strmcat( &s, wm_libver_name() ); + wm_strmcat( &s, " " ); + wm_strmcat( &s, wm_libver_number() ); + return s; +} /* wm_libver_string() */ + + +/* + * + * Now for some memory management... + * + */ + +/* Free some memory and set a pointer to null. */ +void freeup( char **x ) +{ + if (*x != NULL) + { + free(*x); + *x = NULL; + } +} /* freeup() */ + +/* Copy into a malloced string. */ +void +wm_strmcpy( char **t, char *s ) +{ + if (*t != NULL) + free(*t); + + *t = malloc(strlen(s) + 1); + if (*t == NULL) + { + perror("wm_strmcpy"); + exit(1); + } + + (void) strcpy(*t, s); +} /* wm_strmcpy() */ + +/* Add to a malloced string. */ +void +wm_strmcat( char **t, char *s) +{ + int len = strlen(s) + 1; + + if (*s == '\0') + return; + + if (*t != NULL) + { + len += strlen(*t); + *t = realloc(*t, len); + if (*t == NULL) + { + perror("strmcat"); + exit(1); + } + strcat(*t, s); + } + else + wm_strmcpy(t, s); +} /* wm_strmcat() */ + +/* Duplicate a string. Some systems have this in libc, but not all. */ +char * +wm_strdup( char *s ) +{ + char *new; + + new = malloc(strlen(s) + 1); + if (new) + strcpy(new, s); + return (new); +} /* wm_strdup() */ + + +/* + * set and get verbosity level. + */ +void wm_lib_set_verbosity( int level ) +{ + if( WM_MSG_LEVEL_NONE <= level <= WM_MSG_LEVEL_DEBUG ) + { + wm_lib_verbosity = level; + } +} /* wm_lib_set_verbosity */ + +int wm_lib_get_verbosity( void ) +{ + return wm_lib_verbosity; +} + +/* + * wm_lib_message(). + * + * any message that falls into allowed classes and has at least + * verbosity level wm_lib_verbosity & 0xf will be printed. + * + * Usage: + * + * wm_lib_message( WM_MSG_LEVEL | WM_MSG_CLASS, "format", contents); + * + * To simplify the usage, you may simply use WM_MSG_CLASS. It should be + * defined in each module to reflect the correct message class. + * + */ +void wm_lib_message( unsigned int level, char *fmt, ... ) +{ + va_list ap; + /* verbosity level */ + unsigned int vlevel = wm_lib_verbosity & 0xf; + /* allowed classes */ + unsigned int vclass = (level & WM_MSG_CLASS_ALL) & (wm_lib_verbosity & WM_MSG_CLASS_ALL); + + /* + * just give me the level + */ + level &= 0xf; + if(level <= WM_MSG_LEVEL_NONE) + { + fprintf(stderr, "LibWorkMan warning: A LibWorkMan programmer specified an invalid message level.\n"); + } + /* + * print it only if level and class are allowed. + */ + if( (level <= vlevel) && (vclass != 0) ) + { + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } +} /* wm_lib_message() */ + +/* + * Simulate usleep() using select(). + */ +int +wm_susleep( int usec ) +{ + struct timeval tv; + + timerclear(&tv); + tv.tv_sec = usec / 1000000; + tv.tv_usec = usec % 1000000; + return (select(0, NULL, NULL, NULL, &tv)); +} /* wm_susleep() */ + + diff --git a/ascd/version.h b/ascd/version.h new file mode 100644 index 0000000..d651757 --- /dev/null +++ b/ascd/version.h @@ -0,0 +1 @@ +#define VERSION "0.13.2" -- 2.7.4 -- To unsubscribe, send mail to [email protected].