diff options
-rw-r--r-- | INSTALL | 34 | ||||
-rw-r--r-- | Makefile | 62 | ||||
-rw-r--r-- | README | 184 | ||||
-rw-r--r-- | RegEdit.c | 1795 | ||||
-rw-r--r-- | RegEdit.h | 65 | ||||
-rw-r--r-- | RegEditI.h | 368 | ||||
-rw-r--r-- | autojuke.c | 344 | ||||
-rw-r--r-- | autojuke.conf | 8 | ||||
-rw-r--r-- | autojuke.man | 60 | ||||
-rw-r--r-- | chio.h | 160 | ||||
-rw-r--r-- | list.h | 166 | ||||
-rw-r--r-- | man.c | 116 | ||||
-rw-r--r-- | man.h | 4 | ||||
-rw-r--r-- | mover.c | 367 | ||||
-rw-r--r-- | mover.man | 105 | ||||
-rw-r--r-- | unload.c | 65 | ||||
-rw-r--r-- | unload.man | 27 | ||||
-rw-r--r-- | xmover.c | 777 | ||||
-rw-r--r-- | xmover.h | 125 | ||||
-rw-r--r-- | xmover.man | 53 |
20 files changed, 4885 insertions, 0 deletions
@@ -0,0 +1,34 @@ + +Compiling the package +--------------------- + +The driver itself is provided as patch. There are patches for both +2.2.x and 2.4.x kernels. Apply the patch, configure the kernel, +recompile. The 2.2.x version isn't maintained any more. The old patch +is still there, but the latest and greatest stuff is available for 2.4.x +only. + +A simple 'make' should compile the tools. After compiling you should +have the following files: + + mover - a small program for testing the driver / sending commands + to the changer + xmover - a X11 frontend for the driver -- needs Motif. + autojuke - a tool for using a changer with autofs. + unload - a even smaller program to send a eject to any SCSI device + load - (symlink to unload) sends a load instead of eject. + +man-pages for these utilities are available. Also have a look at the +README for some general informations about scsi changers and about the +driver. + +In the todo subdirectory is autojuke version with support for +double-sided media hacked in. Drawback is that volume tag support is +broken in this version. To be merged some day ... + +Have fun, + + Gerd + +-- +Gerd Knorr <kraxel@bytesex.org> @@ -0,0 +1,62 @@ +DESTDIR = +prefix = /usr/local + +etc = $(DESTDIR)/etc +bindir = $(DESTDIR)$(prefix)/bin +sbindir = $(DESTDIR)$(prefix)/sbin +mandir = $(DESTDIR)$(prefix)/share/man + +RELEASE := $(shell uname -r) +KINC := /lib/modules/$(RELEASE)/build/include +#KINC := /usr/src/linux/include + +XINC := /usr/X11R6/include +XLIB := /usr/X11R6/lib + +CC := gcc +OPTFLAGS:= -O2 +CFLAGS := -Wall -g $(OPTFLAGS) +#CFLAGS += -I$(KINC) +CFLAGS += -I$(XINC) -L$(XLIB) + +VERSION := 0.20 +PROGS := mover autojuke + +# poor man's autoconf :-) +MOTIF := $(shell test -d $(XINC)/Xm && echo "yes") +ifeq ($(MOTIF),yes) +PROGS += xmover +endif + +% : %.c + $(CC) $(CFLAGS) -o $@ $< + +all: $(PROGS) + +xmover: xmover.o man.o RegEdit.o + $(CC) $(CFLAGS) -o $@ $^ -lXm -lXt -lX11 + +xmover.o: xmover.c xmover.h + +xmover.h: xmover.ad + perl fallback.pl < $< > $@ + +load: unload + ln -s unload load + +clean: + rm -f *.o *~ $(PROGS) load + +install: $(PROGS) + mkdir -p $(etc) $(bindir) $(sbindir) + mkdir -p $(mandir)/man1 $(mandir)/man8 + install -s -m755 mover $(bindir) + install -m644 mover.man $(mandir)/man1/mover.1 + install -s -m755 autojuke $(sbindir) + install -m644 autojuke.man $(mandir)/man8/autojuke.8 + install -m644 autojuke.conf $(etc) +ifeq ($(MOTIF),yes) + install -s -m755 xmover $(bindir) + install -m644 xmover.man $(mandir)/man1/xmover.1 +endif + @@ -0,0 +1,184 @@ + +README for the SCSI media changer driver +======================================== + +This is a driver for SCSI Medium Changer devices, which are listed +with "Type: Medium Changer" in /proc/scsi/scsi. + +This is for *real* Jukeboxes. It is *not* supported to work with +common small CD-ROM changers, neither one-lun-per-slot SCSI changers +nor IDE drives. + +Userland tools available from: http://bytesex.org/changer.html + + +General Information +------------------- + +First some words about how changers work: A changer has 2 (possibly +more) SCSI ID's. One for the changer device which controls the robot, +and one for the device which actually reads and writes the data. The +later may be anything, a MOD, a CD-ROM, a tape or whatever. For the +changer device this is a "don't care", he *only* shuffles around the +media, nothing else. + + +The SCSI changer model is complex, compared to - for example - IDE-CD +changers. But it allows to handle nearly all possible cases. It knows +4 different types of changer elements: + + media transport - this one shuffles around the media, i.e. the + transport arm. Also known as "picker". + storage - a slot which can hold a media. + import/export - the same as above, but is accessable from outside, + i.e. there the operator (you !) can use this to + fill in and remove media from the changer. + Sometimes named "mailslot". + data transfer - this is the device which reads/writes, i.e. the + CD-ROM / Tape / whatever drive. + +None of these is limited to one: A huge Jukebox could have slots for +123 CD-ROM's, 5 CD-ROM readers (and therefore 6 SCSI ID's: the changer +and each CD-ROM) and 2 transport arms. No problem to handle. + + +How it is implemented +--------------------- + +I implemented the driver as character device driver with a NetBSD-like +ioctl interface. Just grabbed NetBSD's header file and one of the +other linux SCSI device drivers as starting point. The interface +should be source code compatible with NetBSD. So if there is any +software (anybody knows ???) which supports a BSDish changer driver, +it should work with this driver too. + +Over time a few more ioctls where added, volume tag support for example +wasn't covered by the NetBSD ioctl API. + + +Current State +------------- + +Support for more than one transport arm is not implemented yet (and +nobody asked for it so far...). + +I test and use the driver myself with a 35 slot cdrom jukebox from +Grundig. I got some reports telling it works ok with tape autoloaders +(Exabyte, HP and DEC). Some People use this driver with amanda. It +works fine with small (11 slots) and a huge (4 MOs, 88 slots) +magneto-optical Jukebox. Probably with lots of other changers too, most +(but not all :-) people mail me only if it does *not* work... + +I don't have any device lists, neither black-list nor white-list. Thus +it is quite useless to ask me whenever a specific device is supported or +not. In theory every changer device which supports the SCSI-2 media +changer command set should work out-of-the-box with this driver. If it +doesn't, it is a bug. Either within the driver or within the firmware +of the changer device. + + +Using it +-------- + +This is a character device with major number is 86, so use +"mknod /dev/sch0 c 86 0" to create the special file for the driver. + +If the module finds the changer, it prints some messages about the +device [ try "dmesg" if you don't see anything ] and should show up in +/proc/devices. If not.... some changers use ID ? / LUN 0 for the +device and ID ? / LUN 1 for the robot mechanism. But Linux does *not* +look for LUN's other than 0 as default, becauce there are to many +broken devices. So you can try: + + 1) echo "scsi add-single-device 0 0 ID 1" > /proc/scsi/scsi + (replace ID with the SCSI-ID of the device) + 2) boot the kernel with "max_scsi_luns=1" on the command line + (append="max_scsi_luns=1" in lilo.conf should do the trick) + + +Trouble? +-------- + +If you insmod the driver with "insmod debug=1", it will be verbose and +prints a lot of stuff to the syslog. Compiling the kernel with +CONFIG_SCSI_CONSTANTS=y improves the quality of the error messages alot +because the kernel will translate the error codes into human-readable +strings then. + +You can display these messages with the dmesg command (or check the +logfiles). If you email me some question becauce of a problem with the +driver, please include these messages. + + +Insmod options +-------------- + +debug=0/1 + Enable debug messages (see above, default: 0). + +verbose=0/1 + Be verbose (default: 1). + +init=0/1 + Send INITIALIZE ELEMENT STATUS command to the changer + at insmod time (default: 1). + +check_busy=0/1 + When moving media from/to data transfer elements, check + whenever the device is busy and refuse to move if so + (default: 1). + +timeout_init=<seconds> + timeout for the INITIALIZE ELEMENT STATUS command + (default: 3600). + +timeout_move=<seconds> + timeout for all other commands (default: 120). + +dt_id=<id1>,<id2>,... +dt_lun=<lun1>,<lun2>,... + These two allow to specify the SCSI ID and LUN for the data + transfer elements. You likely don't need this as the jukebox + should provide this information. But some devices don't ... + +vendor_firsts= +vendor_counts= +vendor_labels= + These insmod options can be used to tell the driver that there + are some vendor-specific element types. Grundig for example + does this. Some jukeboxes have a printer to label fresh burned + CDs, which is addressed as element 0xc000 (type 5). To tell the + driver about this vendor-specific element, use this: + $ insmod ch \ + vendor_firsts=0xc000 \ + vendor_counts=1 \ + vendor_labels=printer + All three insmod options accept up to four comma-separated + values, this way you can configure the element types 5-8. + You likely need the SCSI specs for the device in question to + find the correct values as they are not covered by the SCSI-2 + standard. + + +Credits +------- + +I wrote this driver using the famous mailing-patches-around-the-world +method. With (more or less) help from: + + Daniel Moehwald <moehwald@hdg.de> + Dane Jasper <dane@sonic.net> + R. Scott Bailey <sbailey@dsddi.eds.com> + Jonathan Corbet <corbet@atd.ucar.edu> + +Special thanks go to + Martin Kuehne <martin.kuehne@bnbt.de> +for a old, second-hand (but full functional) cdrom jukebox which I use +to develop/test driver and tools now. + +Have fun, + + Gerd + +-- +Gerd Knorr <kraxel@bytesex.org> diff --git a/RegEdit.c b/RegEdit.c new file mode 100644 index 0000000..fa01b88 --- /dev/null +++ b/RegEdit.c @@ -0,0 +1,1795 @@ +/* $XConsortium: RegEdit.c /main/5 1995/07/15 20:44:04 drk $ */ +/* + * @OPENGROUP_COPYRIGHT@ + * COPYRIGHT NOTICE + * Copyright (c) 1990, 1991, 1992, 1993 Open Software Foundation, Inc. + * Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group + * ALL RIGHTS RESERVED (MOTIF). See the file named COPYRIGHT.MOTIF for + * the full copyright text. + * + * This software is subject to an open license. It may only be + * used on, with or for operating systems which are themselves open + * source systems. You must contact The Open Group for a license + * allowing distribution and sublicensing of this software on, with, + * or for operating systems which are not Open Source programs. + * + * See http://www.opengroup.org/openmotif/license for full + * details of the license agreement. Any use, reproduction, or + * distribution of the program constitutes recipient's acceptance of + * this agreement. + * + * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS + * PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY + * WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY + * OR FITNESS FOR A PARTICULAR PURPOSE + * + * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT + * NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), 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 OR DISTRIBUTION OF THE PROGRAM OR THE + * EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + */ +/* + * HISTORY + */ +#include <stdio.h> +#include <Xm/XmP.h> +#include <X11/ShellP.h> +#include "RegEditI.h" + + +/* static forward. move from global in the original Editres code */ +static void _XEditResCheckMessages(); +static void _XEditResPutString8(); +static void _XEditResPut8(); +static void _XEditResPut16(); +static void _XEditResPut32(); +static void _XEditResPutWidgetInfo(); +static void _XEditResResetStream(); +static Boolean _XEditResGet8(); +static Boolean _XEditResGet16(); +static Boolean _XEditResGetSigned16(); +static Boolean _XEditResGet32(); +static Boolean _XEditResGetString8(); +static Boolean _XEditResGetWidgetInfo(); + +/* the only entry point here */ +void +XmdRegisterEditres(Widget toplevel) +{ + XtAddEventHandler(toplevel, (EventMask) 0, TRUE, + _XEditResCheckMessages, NULL); +} + + +/************************************************************ + * + * Dump the content of the R5 lib/Xmu/EditresCom.c module. + * just move global as static. + * + ************************************************************/ + +#define _XEditResPutBool _XEditResPut8 +#define _XEditResPutResourceType _XEditResPut8 + +/************************************************************ + * + * Local structure definitions. + * + ************************************************************/ + +typedef enum { BlockNone, BlockSetValues, BlockAll } EditresBlock; + +typedef struct _SetValuesEvent { + EditresCommand type; /* first field must be type. */ + WidgetInfo * widgets; + unsigned short num_entries; /* number of set values requests. */ + char * name; + char * res_type; + XtPointer value; + unsigned short value_len; +} SetValuesEvent; + +typedef struct _SVErrorInfo { + SetValuesEvent * event; + ProtocolStream * stream; + unsigned short * count; + WidgetInfo * entry; +} SVErrorInfo; + +typedef struct _FindChildEvent { + EditresCommand type; /* first field must be type. */ + WidgetInfo * widgets; + short x, y; +} FindChildEvent; + +typedef struct _GenericGetEvent { + EditresCommand type; /* first field must be type. */ + WidgetInfo * widgets; + unsigned short num_entries; /* number of set values requests. */ +} GenericGetEvent, GetResEvent, GetGeomEvent; + +/* + * Things that are common to all events. + */ + +typedef struct _AnyEvent { + EditresCommand type; /* first field must be type. */ + WidgetInfo * widgets; +} AnyEvent; + +/* + * The event union. + */ + +typedef union _EditresEvent { + AnyEvent any_event; + SetValuesEvent set_values_event; + GetResEvent get_resources_event; + GetGeomEvent get_geometry_event; + FindChildEvent find_child_event; +} EditresEvent; + +typedef struct _Globals { + EditresBlock block; + SVErrorInfo error_info; + ProtocolStream stream; + ProtocolStream * command_stream; /* command stream. */ +} Globals; + +#define CURRENT_PROTOCOL_VERSION 4L + +#define streq(a,b) (strcmp( (a), (b) ) == 0) + +static Atom res_editor_command, res_editor_protocol, client_value; + +static Globals globals; + +static void SendFailure(), SendCommand(), InsertWidget(), ExecuteCommand(); +static void FreeEvent(), ExecuteSetValues(), ExecuteGetGeometry(); +static void ExecuteGetResources(); + +static void GetCommand(); +static void LoadResources(); +static Boolean IsChild(); +static void DumpChildren(); +static char *DumpWidgets(), *DoSetValues(), *DoFindChild(); +static char *DoGetGeometry(), *DoGetResources(); + +/************************************************************ + * + * Resource Editor Communication Code + * + ************************************************************/ + +/* Function Name: _XEditResCheckMessages + * Description: This callback routine is set on all shell widgets, + * and checks to see if a client message event + * has come from the resource editor. + * Arguments: w - the shell widget. + * data - *** UNUSED *** + * event - The X Event that triggered this handler. + * cont - *** UNUSED ***. + * Returns: none. + */ + +/* ARGSUSED */ +static void +_XEditResCheckMessages(w, data, event, cont) +Widget w; +XtPointer data; +XEvent *event; +Boolean *cont; +{ + Time time; + ResIdent ident; + static Boolean first_time = FALSE; + static Atom res_editor, res_comm; + Display * dpy; + + if (event->type == ClientMessage) { + XClientMessageEvent * c_event = (XClientMessageEvent *) event; + dpy = XtDisplay(w); + + if (!first_time) { + first_time = TRUE; + res_editor = XInternAtom(dpy, EDITRES_NAME, False); + res_editor_command = XInternAtom(dpy, EDITRES_COMMAND_ATOM, False); + res_editor_protocol = XInternAtom(dpy, EDITRES_PROTOCOL_ATOM, + False); + + /* Used in later procedures. */ + client_value = XInternAtom(dpy, EDITRES_CLIENT_VALUE, False); + LoadResources(w); + } + + if ((c_event->message_type != res_editor) || + (c_event->format != EDITRES_SEND_EVENT_FORMAT)) + return; + + time = c_event->data.l[0]; + res_comm = c_event->data.l[1]; + ident = (ResIdent) c_event->data.l[2]; + if (c_event->data.l[3] != CURRENT_PROTOCOL_VERSION) { + _XEditResResetStream(&globals.stream); + _XEditResPut8(&globals.stream, CURRENT_PROTOCOL_VERSION); + SendCommand(w, res_comm, ident, ProtocolMismatch, &globals.stream); + return; + } + + XtGetSelectionValue(w, res_comm, res_editor_command, + GetCommand, (XtPointer) (long) ident, time); + } +} + +/* Function Name: BuildEvent + * Description: Takes the info out the protocol stream an constructs + * the proper event structure. + * Arguments: w - widget to own selection, in case of error. + * sel - selection to send error message beck in. + * data - the data for the request. + * ident - the id number we are looking for. + * length - length of request. + * Returns: the event, or NULL. + */ + +#define ERROR_MESSAGE ("Client: Improperly formatted protocol request") + +static EditresEvent * +BuildEvent(w, sel, data, ident, length) +Widget w; +Atom sel; +XtPointer data; +ResIdent ident; +unsigned long length; +{ + EditresEvent * event; + ProtocolStream alloc_stream, *stream; + unsigned char temp; + register unsigned int i; + + stream = &alloc_stream; /* easier to think of it this way... */ + + stream->current = stream->top = (unsigned char *) data; + stream->size = HEADER_SIZE; /* size of header. */ + + /* + * Retrieve the Header. + */ + + if (length < HEADER_SIZE) { + SendFailure(w, sel, ident, Failure, ERROR_MESSAGE); + return(NULL); + } + + (void) _XEditResGet8(stream, &temp); + if (temp != ident) /* Id's don't match, ignore request. */ + return(NULL); + + event = (EditresEvent *) XtCalloc(sizeof(EditresEvent), 1); + + (void) _XEditResGet8(stream, &temp); + event->any_event.type = (EditresCommand) temp; + (void) _XEditResGet32(stream, &(stream->size)); + stream->top = stream->current; /* reset stream to top of value.*/ + + /* + * Now retrieve the data segment. + */ + + switch(event->any_event.type) { + case SendWidgetTree: + break; /* no additional info */ + case SetValues: + { + SetValuesEvent * sv_event = (SetValuesEvent *) event; + + if ( !(_XEditResGetString8(stream, &(sv_event->name)) && + _XEditResGetString8(stream, &(sv_event->res_type)))) + { + goto done; + } + + /* + * Since we need the value length, we have to pull the + * value out by hand. + */ + + if (!_XEditResGet16(stream, &(sv_event->value_len))) + goto done; + + sv_event->value = XtMalloc(sizeof(char) * + (sv_event->value_len + 1)); + + for (i = 0; i < sv_event->value_len; i++) { + if (!_XEditResGet8(stream, + (unsigned char *) sv_event->value + i)) + { + goto done; + } + } + ((char*)sv_event->value)[i] = '\0'; /* NULL terminate that sucker. */ + + if (!_XEditResGet16(stream, &(sv_event->num_entries))) + goto done; + + sv_event->widgets = (WidgetInfo *) + XtCalloc(sizeof(WidgetInfo), sv_event->num_entries); + + for (i = 0; i < sv_event->num_entries; i++) { + if (!_XEditResGetWidgetInfo(stream, sv_event->widgets + i)) + goto done; + } + } + break; + case FindChild: + { + FindChildEvent * find_event = (FindChildEvent *) event; + + find_event->widgets = (WidgetInfo *) + XtCalloc(sizeof(WidgetInfo), 1); + + if (!(_XEditResGetWidgetInfo(stream, find_event->widgets) && + _XEditResGetSigned16(stream, &(find_event->x)) && + _XEditResGetSigned16(stream, &(find_event->y)))) + { + goto done; + } + + } + break; + case GetGeometry: + case GetResources: + { + GenericGetEvent * get_event = (GenericGetEvent *) event; + + if (!_XEditResGet16(stream, &(get_event->num_entries))) + goto done; + + get_event->widgets = (WidgetInfo *) + XtCalloc(sizeof(WidgetInfo), get_event->num_entries); + for (i = 0; i < get_event->num_entries; i++) { + if (!_XEditResGetWidgetInfo(stream, get_event->widgets + i)) + goto done; + } + } + break; + default: + { + char buf[BUFSIZ]; + + sprintf(buf, "Unknown Protocol request %d.",event->any_event.type); + SendFailure(w, sel, ident, buf); + return(NULL); + } + } + return(event); + + done: + + SendFailure(w, sel, ident, ERROR_MESSAGE); + FreeEvent(event); + return(NULL); +} + +/* Function Name: FreeEvent + * Description: Frees the event structure and any other pieces + * in it that need freeing. + * Arguments: event - the event to free. + * Returns: none. + */ + +static void +FreeEvent(event) +EditresEvent * event; +{ + if (event->any_event.widgets != NULL) { + XtFree((char *)event->any_event.widgets->ids); + XtFree((char *)event->any_event.widgets); + } + + if (event->any_event.type == SetValues) { + XtFree(event->set_values_event.name); /* XtFree does not free if */ + XtFree(event->set_values_event.res_type); /* value is NULL. */ + } + + XtFree((char *)event); +} + +/* Function Name: GetCommand + * Description: Gets the Command out of the selection asserted by the + * resource manager. + * Arguments: (See Xt XtConvertSelectionProc) + * data - contains the ident number for the command. + * Returns: none. + */ + +/* ARGSUSED */ +static void +GetCommand(w, data, selection, type, value, length, format) +Widget w; +XtPointer data, value; +Atom *selection, *type; +unsigned long *length; +int * format; +{ + ResIdent ident = (ResIdent) (long) data; + EditresEvent * event; + + if ( (*type != res_editor_protocol) || (*format != EDITRES_FORMAT) ) + return; + + if ((event = BuildEvent(w, *selection, value, ident, *length)) != NULL) { + ExecuteCommand(w, *selection, ident, event); + FreeEvent(event); + } +} + +/* Function Name: ExecuteCommand + * Description: Executes a command string received from the + * resource editor. + * Arguments: w - a widget. + * command - the command to execute. + * value - the associated with the command. + * Returns: none. + * + * NOTES: munges str + */ + +/* ARGSUSED */ +static void +ExecuteCommand(w, sel, ident, event) +Widget w; +Atom sel; +ResIdent ident; +EditresEvent * event; +{ + char * (*func)(); + char * str; + + if (globals.block == BlockAll) { + SendFailure(w, sel, ident, + "This client has blocked all Editres commands."); + return; + } + else if ((globals.block == BlockSetValues) && + (event->any_event.type == SetValues)) { + SendFailure(w, sel, ident, + "This client has blocked all SetValues requests."); + return; + } + + switch(event->any_event.type) { + case SendWidgetTree: + func = DumpWidgets; + break; + case SetValues: + func = DoSetValues; + break; + case FindChild: + func = DoFindChild; + break; + case GetGeometry: + func = DoGetGeometry; + break; + case GetResources: + func = DoGetResources; + break; + default: + { + char buf[BUFSIZ]; + sprintf(buf,"Unknown Protocol request %d.",event->any_event.type); + SendFailure(w, sel, ident, buf); + return; + } + } + + _XEditResResetStream(&globals.stream); + if ((str = (*func)(w, event, &globals.stream)) == NULL) + SendCommand(w, sel, ident, PartialSuccess, &globals.stream); + else { + SendFailure(w, sel, ident, str); + XtFree(str); + } +} + +/* Function Name: ConvertReturnCommand + * Description: Converts a selection. + * Arguments: w - the widget that owns the selection. + * selection - selection to convert. + * target - target type for this selection. + * type_ret - type of the selection. + * value_ret - selection value; + * length_ret - lenght of this selection. + * format_ret - the format the selection is in. + * Returns: True if conversion was sucessful. + */ + +/* ARGSUSED */ +static Boolean +ConvertReturnCommand(w, selection, target, + type_ret, value_ret, length_ret, format_ret) +Widget w; +Atom * selection, * target, * type_ret; +XtPointer *value_ret; +unsigned long * length_ret; +int * format_ret; +{ + /* + * I assume the intrinsics give me the correct selection back. + */ + + if ((*target != client_value)) + return(FALSE); + + *type_ret = res_editor_protocol; + *value_ret = (XtPointer) globals.command_stream->real_top; + *length_ret = globals.command_stream->size + HEADER_SIZE; + *format_ret = EDITRES_FORMAT; + + return(TRUE); +} + +/* Function Name: CommandDone + * Description: done with the selection. + * Arguments: *** UNUSED *** + * Returns: none. + */ + +/* ARGSUSED */ +static void +CommandDone(widget, selection, target) +Widget widget; +Atom *selection; +Atom *target; +{ + /* Keep the toolkit from automaticaly freeing the selection value */ +} + +/* Function Name: SendFailure + * Description: Sends a failure message. + * Arguments: w - the widget to own the selection. + * sel - the selection to assert. + * ident - the identifier. + * str - the error message. + * Returns: none. + */ + +static void +SendFailure(w, sel, ident, str) +Widget w; +Atom sel; +ResIdent ident; +char * str; +{ + _XEditResResetStream(&globals.stream); + _XEditResPutString8(&globals.stream, str); + SendCommand(w, sel, ident, Failure, &globals.stream); +} + +/* Function Name: BuildReturnPacket + * Description: Builds a return packet, given the data to send. + * Arguments: ident - the identifier. + * command - the command code. + * stream - the protocol stream. + * Returns: packet - the packet to send. + */ + +static XtPointer +BuildReturnPacket(ident, command, stream) +ResIdent ident; +EditresCommand command; +ProtocolStream * stream; +{ + long old_alloc, old_size; + unsigned char * old_current; + + /* + * We have cleverly keep enough space at the top of the header + * for the return protocol stream, so all we have to do is + * fill in the space. + */ + + /* + * Fool the insert routines into putting the header in the right + * place while being damn sure not to realloc (that would be very bad. + */ + + old_current = stream->current; + old_alloc = stream->alloc; + old_size = stream->size; + + stream->current = stream->real_top; + stream->alloc = stream->size + (2 * HEADER_SIZE); + + _XEditResPut8(stream, ident); + _XEditResPut8(stream, (unsigned char) command); + _XEditResPut32(stream, old_size); + + stream->alloc = old_alloc; + stream->current = old_current; + stream->size = old_size; + + return((XtPointer) stream->real_top); +} + +/* Function Name: SendCommand + * Description: Builds a return command line. + * Arguments: w - the widget to own the selection. + * sel - the selection to assert. + * ident - the identifier. + * command - the command code. + * stream - the protocol stream. + * Returns: none. + */ + +static void +SendCommand(w, sel, ident, command, stream) +Widget w; +Atom sel; +ResIdent ident; +EditresCommand command; +ProtocolStream * stream; +{ + BuildReturnPacket(ident, command, stream); + globals.command_stream = stream; + +/* + * I REALLY want to own the selection. Since this was not triggered + * by a user action, and I am the only one using this atom it is safe to + * use CurrentTime. + */ + + XtOwnSelection(w, sel, CurrentTime, + ConvertReturnCommand, NULL, CommandDone); +} + +/************************************************************ + * + * Generic Utility Functions. + * + ************************************************************/ + +/* Function Name: FindChildren + * Description: Retuns all children (popup, normal and otherwise) + * of this widget + * Arguments: parent - the parent widget. + * children - the list of children. + * normal - return normal children. + * popup - return popup children. + * Returns: the number of children. + */ + +static int +FindChildren(parent, children, normal, popup) +Widget parent, **children; +Boolean normal, popup; +{ + CompositeWidget cw = (CompositeWidget) parent; + int i, num_children, current = 0; + + num_children = 0; + + if (XtIsWidget(parent) && popup) + num_children += parent->core.num_popups; + + if (XtIsComposite(parent) && normal) + num_children += cw->composite.num_children; + + if (num_children == 0) { + *children = NULL; + return(0); + } + + *children =(Widget*) XtMalloc((Cardinal) sizeof(Widget) * num_children); + + if (XtIsComposite(parent) && normal) + for (i = 0; i < cw->composite.num_children; i++,current++) + (*children)[current] = cw->composite.children[i]; + + if (XtIsWidget(parent) && popup) + for ( i = 0; i < parent->core.num_popups; i++, current++) + (*children)[current] = parent->core.popup_list[i]; + + return(num_children); +} + +/* Function Name: IsChild + * Description: check to see of child is a child of parent. + * Arguments: top - the top of the tree. + * parent - the parent widget. + * child - the child. + * Returns: none. + */ + +static Boolean +IsChild(top, parent, child) +Widget top, parent, child; +{ + int i, num_children; + Widget * children; + + if (parent == NULL) + return(top == child); + + num_children = FindChildren(parent, &children, TRUE, TRUE); + + for (i = 0; i < num_children; i++) { + if (children[i] == child) { + XtFree((char *)children); + return(TRUE); + } + } + + XtFree((char *)children); + return(FALSE); +} + +/* Function Name: VerifyWidget + * Description: Makes sure all the widgets still exist. + * Arguments: w - any widget in the tree. + * info - the info about the widget to verify. + * Returns: an error message or NULL. + */ + +static char * +VerifyWidget(w, info) +Widget w; +WidgetInfo *info; +{ + Widget top; + + register int count; + register Widget parent; + register unsigned long * child; + + for (top = w; XtParent(top) != NULL; top = XtParent(top)) {} + + parent = NULL; + child = info->ids; + count = 0; + + while (TRUE) { + if (!IsChild(top, parent, (Widget) *child)) + return(XtNewString("This widget no longer exists in the client.")); + + if (++count == info->num_widgets) + break; + + parent = (Widget) *child++; + } + + info->real_widget = (Widget) *child; + return(NULL); +} + +/************************************************************ + * + * Code to Perform SetValues operations. + * + ************************************************************/ + + +/* Function Name: DoSetValues + * Description: performs the setvalues requested. + * Arguments: w - a widget in the tree. + * event - the event that caused this action. + * stream - the protocol stream to add. + * Returns: NULL. + */ + +static char * +DoSetValues(w, event, stream) +Widget w; +EditresEvent * event; +ProtocolStream * stream; +{ + char * str; + register unsigned i; + unsigned short count = 0; + SetValuesEvent * sv_event = (SetValuesEvent *) event; + + _XEditResPut16(stream, count); /* insert 0, will be overwritten later. */ + + for (i = 0 ; i < sv_event->num_entries; i++) { + if ((str = VerifyWidget(w, &(sv_event->widgets[i]))) != NULL) { + _XEditResPutWidgetInfo(stream, &(sv_event->widgets[i])); + _XEditResPutString8(stream, str); + XtFree(str); + count++; + } + else + ExecuteSetValues(sv_event->widgets[i].real_widget, + sv_event, sv_event->widgets + i, stream, &count); + } + + /* + * Overwrite the first 2 bytes with the real count. + */ + + *(stream->top) = count >> XER_NBBY; + *(stream->top + 1) = count; + return(NULL); +} + +/* Function Name: HandleToolkitErrors + * Description: Handles X Toolkit Errors. + * Arguments: name - name of the error. + * type - type of the error. + * class - class of the error. + * msg - the default message. + * params, num_params - the extra parameters for this message. + * Returns: none. + */ + +/* ARGSUSED */ +static void +HandleToolkitErrors(name, type, class, msg, params, num_params) +String name, type, class, msg, *params; +Cardinal * num_params; +{ + SVErrorInfo * info = &globals.error_info; + char buf[BUFSIZ]; + + if ( streq(name, "unknownType") ) + sprintf(buf, "The `%s' resource is not used by this widget.", + info->event->name); + else if ( streq(name, "noColormap") ) + sprintf(buf, msg, params[0]); + else if (streq(name, "conversionFailed") || streq(name, "conversionError")) + { + if (streq(info->event->value, XtRString)) + sprintf(buf, + "Could not convert the string '%s' for the `%s' resource.", + (char*)info->event->value, info->event->name); + else + sprintf(buf, "Could not convert the `%s' resource.", + info->event->name); + } + else + sprintf(buf, "Name: %s, Type: %s, Class: %s, Msg: %s", + name, type, class, msg); + + /* + * Insert this info into the protocol stream, and update the count. + */ + + (*(info->count))++; + _XEditResPutWidgetInfo(info->stream, info->entry); + _XEditResPutString8(info->stream, buf); +} + +/* Function Name: ExecuteSetValues + * Description: Performs a setvalues for a given command. + * Arguments: w - the widget to perform the set_values on. + * sv_event - the set values event. + * sv_info - the set_value info. + * Returns: none. + */ + +static void +ExecuteSetValues(w, sv_event, entry, stream, count) +Widget w; +SetValuesEvent * sv_event; +WidgetInfo * entry; +ProtocolStream * stream; +unsigned short * count; +{ + XtErrorMsgHandler old; + + SVErrorInfo * info = &globals.error_info; + info->event = sv_event; /* No data can be passed to */ + info->stream = stream; /* an error handler, so we */ + info->count = count; /* have to use a global, YUCK... */ + info->entry = entry; + + old = XtAppSetWarningMsgHandler(XtWidgetToApplicationContext(w), + HandleToolkitErrors); + + XtVaSetValues(w, XtVaTypedArg, + sv_event->name, sv_event->res_type, + sv_event->value, sv_event->value_len, + NULL); + + (void)XtAppSetWarningMsgHandler(XtWidgetToApplicationContext(w), old); +} + + +/************************************************************ + * + * Code for Creating and dumping widget tree. + * + ************************************************************/ + +/* Function Name: DumpWidgets + * Description: Given a widget it builds a protocol packet + * containing the entire widget heirarchy. + * Arguments: w - a widget in the tree. + * event - the event that caused this action. + * stream - the protocol stream to add. + * Returns: NULL + */ + +/* ARGSUSED */ +static char * +DumpWidgets(w, event, stream) +Widget w; +EditresEvent * event; /* UNUSED */ +ProtocolStream * stream; +{ + unsigned short count = 0; + + /* Find Tree's root. */ + for ( ; XtParent(w) != NULL; w = XtParent(w)) {} + + /* + * hold space for count, overwritten later. + */ + + _XEditResPut16(stream, (unsigned int) 0); + + DumpChildren(w, stream, &count); + + /* + * Overwrite the first 2 bytes with the real count. + */ + + *(stream->top) = count >> XER_NBBY; + *(stream->top + 1) = count; + return(NULL); +} + +/* Function Name: DumpChildren + * Description: Adds a child's name to the list. + * Arguments: w - the widget to dump. + * stream - the stream to dump to. + * count - number of dumps we have performed. + * Returns: none. + */ + +/* This is a trick/kludge. To make shared libraries happier (linking + * against Xmu but not linking against Xt, and apparently even work + * as we desire on SVR4, we need to avoid an explicit data reference + * to applicationShellWidgetClass. XtIsTopLevelShell is known + * (implementation dependent assumption!) to use a bit flag. So we + * go that far. Then, we test whether it is an applicationShellWidget + * class by looking for an explicit class name. Seems pretty safe. + */ +static Bool isApplicationShell(w) + Widget w; +{ + register WidgetClass c; + + if (!XtIsTopLevelShell(w)) + return False; + for (c = XtClass(w); c; c = c->core_class.superclass) { + if (!strcmp(c->core_class.class_name, "ApplicationShell")) + return True; + } + return False; +} + +static void +DumpChildren(w, stream, count) +Widget w; +ProtocolStream * stream; +unsigned short *count; +{ + int i, num_children; + Widget *children; + unsigned long window; + char * class; + + (*count)++; + + InsertWidget(stream, w); /* Insert the widget into the stream. */ + + _XEditResPutString8(stream, XtName(w)); /* Insert name */ + + if (isApplicationShell(w)) + class = ((ApplicationShellWidget) w)->application.class; + else + class = XtClass(w)->core_class.class_name; + + _XEditResPutString8(stream, class); /* Insert class */ + + if (XtIsWidget(w)) + if (XtIsRealized(w)) + window = XtWindow(w); + else + window = EDITRES_IS_UNREALIZED; + else + window = EDITRES_IS_OBJECT; + + _XEditResPut32(stream, window); /* Insert window id. */ + + /* + * Find children and recurse. + */ + + num_children = FindChildren(w, &children, TRUE, TRUE); + for (i = 0; i < num_children; i++) + DumpChildren(children[i], stream, count); + + XtFree((char *)children); +} + +/************************************************************ + * + * Code for getting the geometry of widgets. + * + ************************************************************/ + +/* Function Name: DoGetGeometry + * Description: retrieves the Geometry of each specified widget. + * Arguments: w - a widget in the tree. + * event - the event that caused this action. + * stream - the protocol stream to add. + * Returns: NULL + */ + +static char * +DoGetGeometry(w, event, stream) +Widget w; +EditresEvent * event; +ProtocolStream * stream; +{ + unsigned i; + char * str; + GetGeomEvent * geom_event = (GetGeomEvent *) event; + + _XEditResPut16(stream, geom_event->num_entries); + + for (i = 0 ; i < geom_event->num_entries; i++) { + + /* + * Send out the widget id. + */ + + _XEditResPutWidgetInfo(stream, &(geom_event->widgets[i])); + if ((str = VerifyWidget(w, &(geom_event->widgets[i]))) != NULL) { + _XEditResPutBool(stream, True); /* an error occured. */ + _XEditResPutString8(stream, str); /* set message. */ + XtFree(str); + } + else + ExecuteGetGeometry(geom_event->widgets[i].real_widget, stream); + } + return(NULL); +} + +/* Function Name: ExecuteGetGeometry + * Description: Gets the geometry for each widget specified. + * Arguments: w - the widget to get geom on. + * stream - stream to append to. + * Returns: True if no error occured. + */ + +static void +ExecuteGetGeometry(w, stream) +Widget w; +ProtocolStream * stream; +{ + int i; + Boolean mapped_when_man; + Dimension width, height, border_width; + Arg args[8]; + Cardinal num_args = 0; + Position x, y; + + if ( !XtIsRectObj(w) || (XtIsWidget(w) && !XtIsRealized(w)) ) { + _XEditResPutBool(stream, False); /* no error. */ + _XEditResPutBool(stream, False); /* not visable. */ + for (i = 0; i < 5; i++) /* fill in extra space with 0's. */ + _XEditResPut16(stream, 0); + return; + } + + XtSetArg(args[num_args], XtNwidth, &width); num_args++; + XtSetArg(args[num_args], XtNheight, &height); num_args++; + XtSetArg(args[num_args], XtNborderWidth, &border_width); num_args++; + XtSetArg(args[num_args], XtNmappedWhenManaged, &mapped_when_man); + num_args++; + XtGetValues(w, args, num_args); + + if (!(XtIsManaged(w) && mapped_when_man) && XtIsWidget(w)) { + XWindowAttributes attrs; + + /* + * The toolkit does not maintain mapping state, we have + * to go to the server. + */ + + if (XGetWindowAttributes(XtDisplay(w), XtWindow(w), &attrs) != 0) { + if (attrs.map_state != IsViewable) { + _XEditResPutBool(stream, False); /* no error. */ + _XEditResPutBool(stream, False); /* not visable. */ + for (i = 0; i < 5; i++) /* fill in extra space with 0's. */ + _XEditResPut16(stream, 0); + return; + } + } + else { + _XEditResPut8(stream, True); /* Error occured. */ + _XEditResPutString8(stream, "XGetWindowAttributes failed."); + return; + } + } + + XtTranslateCoords(w, -((int) border_width), -((int) border_width), &x, &y); + + _XEditResPutBool(stream, False); /* no error. */ + _XEditResPutBool(stream, True); /* Visable. */ + _XEditResPut16(stream, x); + _XEditResPut16(stream, y); + _XEditResPut16(stream, width); + _XEditResPut16(stream, height); + _XEditResPut16(stream, border_width); +} + +/************************************************************ + * + * Code for executing FindChild. + * + ************************************************************/ + +/* Function Name: PositionInChild + * Description: returns true if this location is in the child. + * Arguments: child - the child widget to check. + * x, y - location of point to check in the parent's + * coord space. + * Returns: TRUE if the position is in this child. + */ + +static Boolean +PositionInChild(child, x, y) +Widget child; +int x, y; +{ + Arg args[6]; + Cardinal num; + Dimension width, height, border_width; + Position child_x, child_y; + Boolean mapped_when_managed; + + if (!XtIsRectObj(child)) /* we must at least be a rect obj. */ + return(FALSE); + + num = 0; + XtSetArg(args[num], XtNmappedWhenManaged, &mapped_when_managed); num++; + XtSetArg(args[num], XtNwidth, &width); num++; + XtSetArg(args[num], XtNheight, &height); num++; + XtSetArg(args[num], XtNx, &child_x); num++; + XtSetArg(args[num], XtNy, &child_y); num++; + XtSetArg(args[num], XtNborderWidth, &border_width); num++; + XtGetValues(child, args, num); + + /* + * The only way we will know of the widget is mapped is to see if + * mapped when managed is True and this is a managed child. Otherwise + * we will have to ask the server if this window is mapped. + */ + + if (XtIsWidget(child) && !(mapped_when_managed && XtIsManaged(child)) ) { + XWindowAttributes attrs; + + if (XGetWindowAttributes(XtDisplay(child), + XtWindow(child), &attrs) != 0) { + /* oops */ + } + else if (attrs.map_state != IsViewable) + return(FALSE); + } + + return (x >= child_x) && + (x <= (child_x + (Position)width + 2 * (Position)border_width)) && + (y >= child_y) && + (y <= (child_y + (Position)height + 2 * (Position)border_width)); +} + +/* Function Name: _FindChild + * Description: Finds the child that actually contatians the point shown. + * Arguments: parent - a widget that is known to contain the point + * specified. + * x, y - The point in coordinates relative to the + * widget specified. + * Returns: none. + */ + +static Widget +_FindChild(parent, x, y) +Widget parent; +int x, y; +{ + Widget * children; + int i = FindChildren(parent, &children, TRUE, FALSE); + + while (i > 0) { + i--; + + if (PositionInChild(children[i], x, y)) { + Widget child = children[i]; + + XtFree((char *)children); + return(_FindChild(child, x - child->core.x, y - child->core.y)); + } + } + + XtFree((char *)children); + return(parent); +} + +/* Function Name: DoFindChild + * Description: finds the child that contains the location specified. + * Arguments: w - a widget in the tree. + * event - the event that caused this action. + * stream - the protocol stream to add. + * Returns: an allocated error message if something went horribly + * wrong and no set values were performed, else NULL. + */ + +static char * +DoFindChild(w, event, stream) +Widget w; +EditresEvent * event; +ProtocolStream * stream; +{ + char * str; + Widget parent, child; + Position parent_x, parent_y; + FindChildEvent * find_event = (FindChildEvent *) event; + + if ((str = VerifyWidget(w, find_event->widgets)) != NULL) + return(str); + + parent = find_event->widgets->real_widget; + + XtTranslateCoords(parent, (Position) 0, (Position) 0, + &parent_x, &parent_y); + + child = _FindChild(parent, find_event->x - (int) parent_x, + find_event->y - (int) parent_y); + + InsertWidget(stream, child); + return(NULL); +} + +/************************************************************ + * + * Procedures for performing GetResources. + * + ************************************************************/ + +/* Function Name: DoGetResources + * Description: Gets the Resources associated with the widgets passed. + * Arguments: w - a widget in the tree. + * event - the event that caused this action. + * stream - the protocol stream to add. + * Returns: NULL + */ + +static char * +DoGetResources(w, event, stream) +Widget w; +EditresEvent * event; +ProtocolStream * stream; +{ + unsigned int i; + char * str; + GetResEvent * res_event = (GetResEvent *) event; + + _XEditResPut16(stream, res_event->num_entries); /* number of replys */ + + for (i = 0 ; i < res_event->num_entries; i++) { + /* + * Send out the widget id. + */ + _XEditResPutWidgetInfo(stream, &(res_event->widgets[i])); + if ((str = VerifyWidget(w, &(res_event->widgets[i]))) != NULL) { + _XEditResPutBool(stream, True); /* an error occured. */ + _XEditResPutString8(stream, str); /* set message. */ + XtFree(str); + } + else { + _XEditResPutBool(stream, False); /* no error occured. */ + ExecuteGetResources(res_event->widgets[i].real_widget, + stream); + } + } + return(NULL); +} + +/* Function Name: ExecuteGetResources. + * Description: Gets the resources for any individual widget. + * Arguments: w - the widget to get resources on. + * stream - the protocol stream. + * Returns: none. + */ + +static void +ExecuteGetResources(w, stream) +Widget w; +ProtocolStream * stream; +{ + XtResourceList norm_list, cons_list; + Cardinal num_norm, num_cons; + register int i; + + /* + * Get Normal Resources. + */ + + XtGetResourceList(XtClass(w), &norm_list, &num_norm); + + if (XtParent(w) != NULL) + XtGetConstraintResourceList(XtClass(XtParent(w)),&cons_list,&num_cons); + else + num_cons = 0; + + _XEditResPut16(stream, num_norm + num_cons); /* how many resources. */ + + /* + * Insert all the normal resources. + */ + + for ( i = 0; i < (int) num_norm; i++) { + _XEditResPutResourceType(stream, NormalResource); + _XEditResPutString8(stream, norm_list[i].resource_name); + _XEditResPutString8(stream, norm_list[i].resource_class); + _XEditResPutString8(stream, norm_list[i].resource_type); + } + XtFree((char *) norm_list); + + /* + * Insert all the constraint resources. + */ + + if (num_cons > 0) { + for ( i = 0; i < (int) num_cons; i++) { + _XEditResPutResourceType(stream, ConstraintResource); + _XEditResPutString8(stream, cons_list[i].resource_name); + _XEditResPutString8(stream, cons_list[i].resource_class); + _XEditResPutString8(stream, cons_list[i].resource_type); + } + XtFree((char *) cons_list); + } +} + +/************************************************************ + * + * Code for inserting values into the protocol stream. + * + ************************************************************/ + +/* Function Name: InsertWidget + * Description: Inserts the full parent heirarchy of this + * widget into the protocol stream as a widget list. + * Arguments: stream - the protocol stream. + * w - the widget to insert. + * Returns: none + */ + +static void +InsertWidget(stream, w) +ProtocolStream * stream; +Widget w; +{ + Widget temp; + unsigned long * widget_list; + register int i, num_widgets; + + for (temp = w, i = 0; temp != 0; temp = XtParent(temp), i++) {} + + num_widgets = i; + widget_list = (unsigned long *) + XtMalloc(sizeof(unsigned long) * num_widgets); + + /* + * Put the widgets into the list. + * make sure that they are inserted in the list from parent -> child. + */ + + for (i--, temp = w; temp != NULL; temp = XtParent(temp), i--) + widget_list[i] = (unsigned long) temp; + + _XEditResPut16(stream, num_widgets); /* insert number of widgets. */ + for (i = 0; i < num_widgets; i++) /* insert Widgets themselves. */ + _XEditResPut32(stream, widget_list[i]); + + XtFree((char *)widget_list); +} + +/************************************************************ + * + * All of the following routines are public. + * + ************************************************************/ + +/* Function Name: _XEditResPutString8 + * Description: Inserts a string into the protocol stream. + * Arguments: stream - stream to insert string into. + * str - string to insert. + * Returns: none. + */ + +static void +_XEditResPutString8(stream, str) +ProtocolStream * stream; +char * str; +{ + int i, len = strlen(str); + + _XEditResPut16(stream, len); + for (i = 0 ; i < len ; i++, str++) + _XEditResPut8(stream, *str); +} + +/* Function Name: _XEditResPut8 + * Description: Inserts an 8 bit integer into the protocol stream. + * Arguments: stream - stream to insert string into. + * value - value to insert. + * Returns: none + */ + +static void +_XEditResPut8(stream, value) +ProtocolStream * stream; +unsigned int value; +{ + unsigned char temp; + + if (stream->size >= stream->alloc) { + stream->alloc += 100; + stream->real_top = (unsigned char *) XtRealloc( + (char *)stream->real_top, + stream->alloc + HEADER_SIZE); + stream->top = stream->real_top + HEADER_SIZE; + stream->current = stream->top + stream->size; + } + + temp = (unsigned char) (value & BYTE_MASK); + *((stream->current)++) = temp; + (stream->size)++; +} + +/* Function Name: _XEditResPut16 + * Description: Inserts a 16 bit integer into the protocol stream. + * Arguments: stream - stream to insert string into. + * value - value to insert. + * Returns: void + */ + +static void +_XEditResPut16(stream, value) +ProtocolStream * stream; +unsigned int value; +{ + _XEditResPut8(stream, (value >> XER_NBBY) & BYTE_MASK); + _XEditResPut8(stream, value & BYTE_MASK); +} + +/* Function Name: _XEditResPut32 + * Description: Inserts a 32 bit integer into the protocol stream. + * Arguments: stream - stream to insert string into. + * value - value to insert. + * Returns: void + */ + +static void +_XEditResPut32(stream, value) +ProtocolStream * stream; +unsigned long value; +{ + int i; + + for (i = 3; i >= 0; i--) + _XEditResPut8(stream, (value >> (XER_NBBY*i)) & BYTE_MASK); +} + +/* Function Name: _XEditResPutWidgetInfo + * Description: Inserts the widget info into the protocol stream. + * Arguments: stream - stream to insert widget info into. + * info - info to insert. + * Returns: none + */ + +static void +_XEditResPutWidgetInfo(stream, info) +ProtocolStream * stream; +WidgetInfo * info; +{ + unsigned int i; + + _XEditResPut16(stream, info->num_widgets); + for (i = 0; i < info->num_widgets; i++) + _XEditResPut32(stream, info->ids[i]); +} + +/************************************************************ + * + * Code for retrieving values from the protocol stream. + * + ************************************************************/ + +/* Function Name: _XEditResResetStream + * Description: resets the protocol stream + * Arguments: stream - the stream to reset. + * Returns: none. + */ + +static void +_XEditResResetStream(stream) +ProtocolStream * stream; +{ + stream->current = stream->top; + stream->size = 0; + if (stream->real_top == NULL) { + stream->real_top = (unsigned char *) XtRealloc( + (char *)stream->real_top, + stream->alloc + HEADER_SIZE); + stream->top = stream->real_top + HEADER_SIZE; + stream->current = stream->top + stream->size; + } +} + +/* + * NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE + * + * The only modified field if the "current" field. + * + * The only fields that must be set correctly are the "current", "top" + * and "size" fields. + */ + +/* Function Name: _XEditResGetg8 + * Description: Retrieves an unsigned 8 bit value + * from the protocol stream. + * Arguments: stream. + * val - a pointer to value to return. + * Returns: TRUE if sucessful. + */ + +static Boolean +_XEditResGet8(stream, val) +ProtocolStream * stream; +unsigned char * val; +{ + if (stream->size < (stream->current - stream->top)) + return(FALSE); + + *val = *((stream->current)++); + return(TRUE); +} + +/* Function Name: _XEditResGet16 + * Description: Retrieves an unsigned 16 bit value + * from the protocol stream. + * Arguments: stream. + * val - a pointer to value to return. + * Returns: TRUE if sucessful. + */ + +static Boolean +_XEditResGet16(stream, val) +ProtocolStream * stream; +unsigned short * val; +{ + unsigned char temp1, temp2; + + if ( !(_XEditResGet8(stream, &temp1) && _XEditResGet8(stream, &temp2)) ) + return(FALSE); + + *val = (((unsigned short) temp1 << XER_NBBY) + ((unsigned short) temp2)); + return(TRUE); +} + +/* Function Name: _XEditResGetSigned16 + * Description: Retrieves an signed 16 bit value from the protocol stream. + * Arguments: stream. + * val - a pointer to value to return. + * Returns: TRUE if sucessful. + */ + +static Boolean +_XEditResGetSigned16(stream, val) +ProtocolStream * stream; +short * val; +{ + unsigned char temp1, temp2; + + if ( !(_XEditResGet8(stream, &temp1) && _XEditResGet8(stream, &temp2)) ) + return(FALSE); + + if (temp1 & (1 << (XER_NBBY - 1))) { /* If the sign bit is active. */ + *val = -1; /* store all 1's */ + *val &= (temp1 << XER_NBBY); /* Now and in the MSB */ + *val &= temp2; /* and LSB */ + } + else + *val = (((unsigned short) temp1 << XER_NBBY) + ((unsigned short) temp2)); + + return(TRUE); +} + +/* Function Name: _XEditResGet32 + * Description: Retrieves an unsigned 32 bit value + * from the protocol stream. + * Arguments: stream. + * val - a pointer to value to return. + * Returns: TRUE if sucessful. + */ + +static Boolean +_XEditResGet32(stream, val) +ProtocolStream * stream; +unsigned long * val; +{ + unsigned short temp1, temp2; + + if ( !(_XEditResGet16(stream, &temp1) && _XEditResGet16(stream, &temp2)) ) + return(FALSE); + + *val = (((unsigned short) temp1 << (XER_NBBY * 2)) + + ((unsigned short) temp2)); + return(TRUE); +} + +/* Function Name: _XEditResGetString8 + * Description: Retrieves an 8 bit string value from the protocol stream. + * Arguments: stream - the protocol stream + * str - the string to retrieve. + * Returns: True if retrieval was successful. + */ + +static Boolean +_XEditResGetString8(stream, str) +ProtocolStream * stream; +char ** str; +{ + unsigned short len; + register unsigned i; + + if (!_XEditResGet16(stream, &len)) { + return(FALSE); + } + + *str = XtMalloc(sizeof(char) * (len + 1)); + + for (i = 0; i < len; i++) { + if (!_XEditResGet8(stream, (unsigned char *) *str + i)) { + XtFree(*str); + *str = NULL; + return(FALSE); + } + } + (*str)[i] = '\0'; /* NULL terminate that sucker. */ + return(TRUE); +} + +/* Function Name: _XEditResGetWidgetInfo + * Description: Retrieves the list of widgets that follow and stores + * them in the widget info structure provided. + * Arguments: stream - the protocol stream + * info - the widget info struct to store into. + * Returns: True if retrieval was successful. + */ + +static Boolean +_XEditResGetWidgetInfo(stream, info) +ProtocolStream * stream; +WidgetInfo * info; +{ + unsigned int i; + + if (!_XEditResGet16(stream, &(info->num_widgets))) + return(FALSE); + + info->ids = (unsigned long *) XtMalloc(sizeof(long) * (info->num_widgets)); + + for (i = 0; i < info->num_widgets; i++) { + if (!_XEditResGet32(stream, info->ids + i)) { + XtFree((char *)info->ids); + info->ids = NULL; + return(FALSE); + } + } + return(TRUE); +} + +/************************************************************ + * + * Code for Loading the EditresBlock resource. + * + ************************************************************/ + +/* Function Name: CvStringToBlock + * Description: Converts a string to an editres block value. + * Arguments: dpy - the display. + * args, num_args - **UNUSED ** + * from_val, to_val - value to convert, and where to put result + * converter_data - ** UNUSED ** + * Returns: TRUE if conversion was sucessful. + */ + +/* ARGSUSED */ +static Boolean +CvtStringToBlock(dpy, args, num_args, from_val, to_val, converter_data) +Display * dpy; +XrmValue * args; +Cardinal * num_args; +XrmValue * from_val, * to_val; +XtPointer * converter_data; +{ + char ptr[BUFSIZ]; + static EditresBlock block; + +/* XmuCopyISOLatin1Lowered(ptr, from_val->addr);*/ + + + if (streq(ptr, "none")) + block = BlockNone; + else if (streq(ptr, "setvalues")) + block = BlockSetValues; + else if (streq(ptr, "all")) + block = BlockAll; + else { + Cardinal num_params = 1; + String params[1]; + + params[0] = from_val->addr; + XtAppWarningMsg(XtDisplayToApplicationContext(dpy), + "CvtStringToBlock", "unknownValue", "EditresError", + "Could not convert string \"%s\" to EditresBlock.", + params, &num_params); + return(FALSE); + } + + if (to_val->addr != NULL) { + if (to_val->size < sizeof(EditresBlock)) { + to_val->size = sizeof(EditresBlock); + return(FALSE); + } + *(EditresBlock *)(to_val->addr) = block; + } + else + to_val->addr = (XtPointer) block; + + to_val->size = sizeof(EditresBlock); + return(TRUE); +} + +#define XtREditresBlock ("EditresBlock") + +/* Function Name: LoadResources + * Description: Loads a global resource the determines of this + * application should allow Editres requests. + * Arguments: w - any widget in the tree. + * Returns: none. + */ + +static void +LoadResources(w) +Widget w; +{ + static XtResource resources[] = { + {"editresBlock", "EditresBlock", XtREditresBlock, sizeof(EditresBlock), + XtOffsetOf(Globals, block), XtRImmediate, (XtPointer) BlockNone} + }; + + for (; XtParent(w) != NULL; w = XtParent(w)) {} + + XtAppSetTypeConverter(XtWidgetToApplicationContext(w), + XtRString, XtREditresBlock, CvtStringToBlock, + NULL, (Cardinal) 0, XtCacheAll, NULL); + + XtGetApplicationResources( w, (caddr_t) &globals, resources, + XtNumber(resources), NULL, (Cardinal) 0); +} + + diff --git a/RegEdit.h b/RegEdit.h new file mode 100644 index 0000000..5274151 --- /dev/null +++ b/RegEdit.h @@ -0,0 +1,65 @@ +/* $XConsortium: RegEdit.h /main/5 1995/07/15 20:44:09 drk $ */ +/* + * @OPENGROUP_COPYRIGHT@ + * COPYRIGHT NOTICE + * Copyright (c) 1990, 1991, 1992, 1993 Open Software Foundation, Inc. + * Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group + * ALL RIGHTS RESERVED (MOTIF). See the file named COPYRIGHT.MOTIF for + * the full copyright text. + * + * This software is subject to an open license. It may only be + * used on, with or for operating systems which are themselves open + * source systems. You must contact The Open Group for a license + * allowing distribution and sublicensing of this software on, with, + * or for operating systems which are not Open Source programs. + * + * See http://www.opengroup.org/openmotif/license for full + * details of the license agreement. Any use, reproduction, or + * distribution of the program constitutes recipient's acceptance of + * this agreement. + * + * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS + * PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY + * WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY + * OR FITNESS FOR A PARTICULAR PURPOSE + * + * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT + * NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), 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 OR DISTRIBUTION OF THE PROGRAM OR THE + * EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + */ +/* + * HISTORY + */ + + /* Ensure that the file be included only once. */ +#ifndef _XmdRegEdit_h +#define _XmdRegEdit_h + +#include <Xm/Xm.h> + +/* Allow for C++ compilation. */ +#ifdef __cplusplus +extern "C" { +#endif + + +extern void XmdRegisterEditres(Widget toplevel); + +/* Allow for C++ compilation. */ +#ifdef __cplusplus +} /* Close scope of 'extern "C"' declaration which encloses file. */ +#endif + + +/* Ensure that the file be included only once. */ +#endif /* _XmdRegEdit_h */ +/* DON'T ADD ANYTHING AFTER THIS #endif */ + diff --git a/RegEditI.h b/RegEditI.h new file mode 100644 index 0000000..4608593 --- /dev/null +++ b/RegEditI.h @@ -0,0 +1,368 @@ +/* + * @OPENGROUP_COPYRIGHT@ + * COPYRIGHT NOTICE + * Copyright (c) 1990, 1991, 1992, 1993 Open Software Foundation, Inc. + * Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group + * ALL RIGHTS RESERVED (MOTIF). See the file named COPYRIGHT.MOTIF for + * the full copyright text. + * + * This software is subject to an open license. It may only be + * used on, with or for operating systems which are themselves open + * source systems. You must contact The Open Group for a license + * allowing distribution and sublicensing of this software on, with, + * or for operating systems which are not Open Source programs. + * + * See http://www.opengroup.org/openmotif/license for full + * details of the license agreement. Any use, reproduction, or + * distribution of the program constitutes recipient's acceptance of + * this agreement. + * + * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS + * PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY + * WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY + * OR FITNESS FOR A PARTICULAR PURPOSE + * + * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT + * NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), 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 OR DISTRIBUTION OF THE PROGRAM OR THE + * EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + */ +/* + * HISTORY + */ + + +/* $XConsortium: RegEditI.h /main/4 1995/07/14 10:05:01 drk $ */ + +/* + +Copyright (c) 1989 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + +*/ + +/* + * Author: Chris D. Peterson, MIT X Consortium + */ + +/************************************************************ + + The Editres Protocol + + + The Client message sent to the application is: + + ATOM = "ResEditor" --- RES_EDITOR_NAME + + FORMAT = 32 --- RES_EDIT_SEND_EVENT_FORMAT + + l[0] = timestamp + l[1] = command atom name + l[2] = ident of command. + l[3] = protocol version number to use. + + + + The binary protocol has the following format: + + Card8: 8-bit unsingned integer + Card16: 16-bit unsingned integer + Card32: 32-bit unsingned integer + Int16: 16-bit signed integer + Window: 32-bit value + Widget: 32-bit value + String8: ListOfCard8 + + [a][b][c] represent an exclusive list of choices. + + All widgets are passed as a list of widgets, containing the + full instance heirarch of this widget. The hierarchy is ordered + from parent to child. Thus the first element of each list is + the root of the widget tree (this makes verifying that the widget + still exists, MUCH faster). + + ListOfFoo comprises a list of things in the following format: + + number: Card16 + <number> things: ???? + + This is a synchronous protocol, every request MUST be followed by a + reply. + + Request: + + Serial Number: Card8 + Op Code: Card8 - { SendWidgetTree = 0, + SetValues = 1, + GetResources = 2, + GetGeometry = 3, + FindChild = 4, + GetValues = 5 } + Length: Card32 + Data: + + Reply: + + Serial Number: Card8 + Type: Card8 - { Formatted = 0, + Unformatted = 1, + ProtocolMismatch = 2 + } + Length: Card32 + + + Byte Order: + + All Fields are MSB -> LSB + + Data: + + Formatted: + + The data contains the reply information for the request as + specified below if the reply type is "Formatted". The return + values for the other reply types are shown below. + + Unformatted: + + Message: String8 + + ProtocolMismatch: + + RequestedVersion: Card8 + +------------------------------------------------------------ + + SendWidgetTree: + + ---> + + Number of Entries: Card16 + Entry: + widget: ListOfWidgets + name: String8 + class: String8 + window: Card32 + toolkit: String8 + + Send Widget Tree returns the toolkit type, and a fuly specified list + of widgets for each widget in the tree. This is enough information + to completely reconstruct the entire widget heirarchy. + + The window return value contains the Xid of the window currently + used by this widget. If the widget is unrealized then 0 is returned, + and if widget is a non-windowed object a value of 2 is returned. + + SetValues: + + name: String8 + type: String8 + value: String8 + Number of Entries: Card16 + Entry: + widget: ListOfWidgets + + ---> + + Number of Entries: Card16 + Entry: + widget: ListOfWidgets + message: String8 + + SetValues will allow the same resource to be set on a number of + widgets. This function will return an error message if the SetValues + request caused an Xt error. + + GetValues: + + names: ListOfString8 + widget: Widget + + ---> + novalues: ListOfCard16 + values: ListOfString8 + + GetValues will allow a number of resource values to be read + on a particular widget. The request specifies the names of + the resources wanted and the widget id these resources are + from. The reply returns a list of indices from the requests + name list of resources for which a value can not be returned. + It also returns a list of returned values, in the order of the + requests names list, skipping those indices present in novalues. + + GetResources: + + Number of Entries: Card16 + Entry + widget: ListOfWidgets: + + ----> + + Number of Entries: Card16 + Entry + Widget: ListOfWidgets: + Error: Bool + + [ Message: String 8 ] + [ Number of Resources: Card16 + Resource: + Kind: {normal, constraint} + Name: String8 + Class: String8 + Type: String8 ] + + GetResource retrieves the kind, name, class and type for every + widget passed to it. If an error occured with the resource fetch + Error will be set to True for the given widget and a message + is returned rather than the resource info. + + GetGeometry: + + Number of Entries: Card16 + Entry + Widget: ListOfWidgets: + + ----> + + Number of Entries: Card16 + Entry + Widget: ListOfWidgets: + Error: Bool + + [ message: String 8 ] + [ mapped: Boolean + X: Int16 + Y: Int16 + Width: Card16 + Height: Card16 + BorderWidth: Card16 ] + + GetGeometry retreives the mapping state, x, y, width, height + and border width for each widget specified. If an error occured + with the geometry fetch "Error" will be set to True for the given + widget and a message is returned rather than the geometry info. + X an Y corrospond to the root coordinates of the upper left corner + of the widget (outside the window border). + + FindChild: + + Widget: ListOfWidgets + X: Int16 + Y: Int16 + + ---> + + Widget: ListOfWidgets + + Find Child returns a descendent of the widget specified that + is at the root coordinates specified. + + NOTE: + + The returned widget is undefined if the point is contained in + two or more mapped widgets, or in two overlapping Rect objs. + + GetValues: + + names: ListOfString8 + widget: Widget + + ---> + + values: ListOfString8 + + GetValues will allow a number of resource values to be read + on a particular widget. Currently only InterViews 3.0.1 Styles + and their attributes are supported. In addition, the current + user interface only supports the return of 1 resource. The ability + to specify and return multiple resources is defined for future editres + interfaces where some or all of a widgets resource values are returned + and displayed at once. + + +************************************************************/ + +#include <X11/Intrinsic.h> +#include <X11/Xfuncproto.h> + +#define XER_NBBY 8 /* number of bits in a byte */ +#define BYTE_MASK 255 + +#define HEADER_SIZE 6 + +#define EDITRES_IS_OBJECT 2 +#define EDITRES_IS_UNREALIZED 0 + +/* + * Format for atoms. + */ + +#define EDITRES_FORMAT 8 +#define EDITRES_SEND_EVENT_FORMAT 32 + +/* + * Atoms + */ + +#define EDITRES_NAME "Editres" +#define EDITRES_COMMAND_ATOM "EditresCommand" +#define EDITRES_COMM_ATOM "EditresComm" +#define EDITRES_CLIENT_VALUE "EditresClientVal" +#define EDITRES_PROTOCOL_ATOM "EditresProtocol" + +typedef enum { SendWidgetTree = 0, + SetValues = 1, + GetResources = 2, + GetGeometry = 3, + FindChild = 4, + GetValues = 5 + } EditresCommand; + +typedef enum {NormalResource = 0, ConstraintResource = 1} ResourceType; + +/* + * The type of a resource identifier. + */ + +typedef unsigned char ResIdent; + +typedef enum {PartialSuccess= 0, Failure= 1, ProtocolMismatch= 2} EditResError; + +typedef struct _WidgetInfo { + unsigned short num_widgets; + unsigned long * ids; + Widget real_widget; +} WidgetInfo; + +typedef struct _ProtocolStream { + unsigned long size, alloc; + unsigned char *real_top, *top, *current; +} ProtocolStream; + diff --git a/autojuke.c b/autojuke.c new file mode 100644 index 0000000..c27f135 --- /dev/null +++ b/autojuke.c @@ -0,0 +1,344 @@ +/* + * program "map-file" for linux autofs + * + * usage: /usr/sbin/automount /jukebox program /usr/sbin/autojuke + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <asm/types.h> +#include <linux/cdrom.h> + +#include "chio.h" + +#define PROG "autojuke" +#define CHANGER "/dev/sch0" +#define CONFIG "/etc/autojuke.conf" + +#define DEBUG 0 /* set to 1 for debug output */ + +/* ---------------------------------------------------------------------- */ + +int +check_mtab(int n, char **devs) +{ + int i,res; + FILE *fp; + struct stat st; + char line[79],dev[32]; + int *fl; + + /* check for lock file */ + for (i = 0; i < 5; i++) { + if (-1 == stat("/etc/mtab~",&st)) + break; + sleep(1); + } + if (5 == i) { + fprintf(stderr,PROG ": /etc/mtab locked\n"); + exit(1); + } + + if (NULL == (fp = fopen("/etc/mtab", "r"))) { + perror(PROG ": open /etc/mtab"); + exit(1); + } + fl = malloc(n*sizeof(int)); + memset(fl,0,n*sizeof(int)); + while (NULL != fgets(line,79,fp)) { + sscanf(line," %31s",dev); + for (i = 0; i < n; i++) { + if (0 == strcmp(dev,devs[i])) + fl[i] = 1; + } + } + res = -1; + for (i = 0; i < n; i++) { + if (0 == fl[i]) + res = i; + } + free(fl); + fclose(fp); + return res; +} + +static int cdrom_tray(char *dev, int eject) +{ + int fd = -1; + + if (-1 == (fd = open(dev,O_RDONLY | O_NONBLOCK))) + goto err; + if (eject) { + if (-1 == ioctl(fd,CDROMEJECT,NULL)) + goto err; + } else { + if (-1 == ioctl(fd,CDROMCLOSETRAY,NULL)) + goto err; + } + close(fd); + return 0; + + err: + if (-1 != fd) + close(fd); + return -1; +} + +int +main(int argc, char *argv[]) +{ + struct changer_params params; + struct changer_get_element st; + struct changer_get_element *dt; + struct changer_move move; + char **devs,line[80],mopt[80],*h; + int fd,i,slot,drive; + int try_umount = 0; + int eject_cdrom = 0; + FILE *fp; + + if (argc != 2) { + fprintf(stderr,"Read the f*** manual!\n\n*plonk*\n\n"); + exit(1); + } + + if (-1 == (fd = open(CHANGER,O_RDONLY))) { + perror(PROG ": open " CHANGER); + exit(1); + } + + /* read changer status information */ + if (ioctl(fd,CHIOGPARAMS,¶ms)) { + perror(PROG ": ioctl CHIOGPARAMS"); + exit(1); + } +#if DEBUG + fprintf(stderr,PROG ": %d drives, %d slots\n", + params.cp_ndrives,params.cp_nslots); +#endif + + dt = malloc(sizeof(struct changer_get_element)*params.cp_ndrives); + for (i = 0; i < params.cp_ndrives; i++) { + dt[i].cge_type=CHET_DT; + dt[i].cge_unit=i; + if (ioctl(fd,CHIOGELEM,dt+i)) { + fprintf(stderr,PROG ":ioctl CHIOGELEM mt %d: %s\n", + i,strerror(errno)); + exit(1); + } + if (NULL != (h = strchr(dt[i].cge_pvoltag,' '))) *h = '\0'; + if (NULL != (h = strchr(dt[i].cge_avoltag,' '))) *h = '\0'; + } + + /* read config file */ + if (NULL == (fp = fopen(CONFIG, "r"))) { + perror(PROG ": open " CONFIG); + exit(1); + } + devs = malloc(sizeof(char*)*params.cp_ndrives); + for (i = 0; NULL != fgets(line,79,fp);) { + if (line[0] == '#' || line[0] == '\n') + continue; + if (sscanf(line,"try_umount= %d",&try_umount)) + continue; + if (sscanf(line,"eject_cdrom= %d",&eject_cdrom)) + continue; + if (line==strstr(line,"mopt=")) { + strncpy(mopt,line+5,79); + mopt[strlen(mopt)-1]='\0'; + continue; + } + + devs[i] = malloc(32); + if (!sscanf(line,"dev= %31s",devs[i])) { + fprintf(stderr,PROG ": config syntax error: %s",line); + exit(1); + } +#if DEBUG + fprintf(stderr,PROG ": drive %d: %s\n",i,devs[i]); +#endif + i++; + } + fclose(fp); + if (i != params.cp_ndrives) { + fprintf(stderr, + PROG ": number of drives (%d) does'nt match the\n" + PROG ": number of dev=... lines (%d) in " CONFIG "\n", + params.cp_ndrives,i); + exit(1); + } + + /* everything fine so far, lets check what key we got... */ + for (i = 0; argv[1][i];i++) + if (!isdigit(argv[1][i])) + break; + if (!argv[1][i]) { + /* we got a number */ + slot = atoi(argv[1]); + if (slot < 0 || slot >= params.cp_nslots) { + fprintf(stderr,PROG ": slot %d is out of range\n",slot); + exit(1); + } + + /* check drives */ + for (drive = -1, i = 0; i < params.cp_ndrives; i++) { + if ((dt[i].cge_status & CESTATUS_FULL) && + (dt[i].cge_flags & CGE_SRC) && + (dt[i].cge_srctype == CHET_ST) && + (dt[i].cge_srcunit == slot)) { + drive = i; + break; + } + } + + if (-1 != drive) { + /* Ha! requested media is still in a drive. Nothing to do :-) */ + printf("%s\t:%s\n",mopt,devs[drive]); + exit(0); + } + + st.cge_type=CHET_ST; + st.cge_unit=slot; + if (ioctl(fd,CHIOGELEM,&st)) { + fprintf(stderr,PROG ": ioctl CHIOGELEM st %d: %s\n", + slot,strerror(errno)); + exit(1); + } + + if (!(st.cge_status & CESTATUS_FULL)) { + fprintf(stderr,PROG ": slot %d has no media\n",slot); + exit(1); + } + } else { + + /* no number -- assume volume tag */ + + /* check drives */ + for (drive = -1, i = 0; i < params.cp_ndrives; i++) { + if ((dt[i].cge_status & CESTATUS_FULL) && + (dt[i].cge_flags & CGE_PVOLTAG) && + (0 == strcmp(argv[1],dt[i].cge_pvoltag))) { + drive = i; + break; + } + } + + if (-1 != drive) { + /* Ha! requested media is still in a drive. Nothing to do :-) */ + printf("%s\t:%s\n",mopt,devs[drive]); + exit(0); + } + + /* Find the tag */ + st.cge_type=CHET_ST; + for (slot = -1, i = 0; i < params.cp_nslots; i++) { + st.cge_unit=i; + if (ioctl(fd,CHIOGELEM,&st)) { + fprintf(stderr,PROG ": ioctl CHIOGELEM st %d: %s\n", + i,strerror(errno)); + exit(1); + } + + if (NULL != (h = strchr(st.cge_pvoltag,' '))) *h = '\0'; + if (NULL != (h = strchr(st.cge_avoltag,' '))) *h = '\0'; + + if ((st.cge_status & CESTATUS_FULL) && + (st.cge_flags & CGE_PVOLTAG) && + (0 == strcmp(argv[1],st.cge_pvoltag))) { + slot = i; + break; + } + } + + if (-1 == slot) { + fprintf(stderr,PROG ": volume tag %s not found\n",argv[1]); + exit(1); + } + } + + /* ok, requested media found, check where to put it now */ + + /* look for a empty drive first... */ + for (i = 0, drive = -1; i < params.cp_ndrives; i++) { + if (!(dt[i].cge_status & CESTATUS_FULL)) { + drive = i; + break; + } + } + + if (-1 == drive) + /* failing that, look for a unmounted drive */ + drive = check_mtab(params.cp_ndrives,devs); + + if (-1 == drive && try_umount) { + /* still no success -- tell automount to umount idle mount-points */ +#if DEBUG + fprintf(stderr,PROG ": sending SIGUSR1 to automount\n"); +#endif + kill(SIGUSR1,getppid()); + sleep(1); /* XXX */ + drive = check_mtab(params.cp_ndrives,devs); + } + + if (-1 == drive) { + /* bad luck */ + fprintf(stderr,PROG ": all drives busy, sorry\n"); + exit(1); + } + + if (eject_cdrom) { + if (0 != cdrom_tray(devs[drive],1)) + exit(1); + } + + if (dt[drive].cge_status & CESTATUS_FULL) { + /* Hmm, full. Ok, put back the old media ... */ + if (!(dt[drive].cge_flags & CGE_SRC)) { + fprintf(stderr,PROG ": source element for drive %d unknown\n", + drive); + goto close_error_exit; + } + memset(&move,0,sizeof(struct changer_move)); + move.cm_totype = dt[drive].cge_srctype; + move.cm_tounit = dt[drive].cge_srcunit; + move.cm_fromtype = CHET_DT; + move.cm_fromunit = drive; + if (ioctl(fd,CHIOMOVE,&move)) { + fprintf(stderr,PROG ": ioctl MOVE (unload): %s\n", + strerror(errno)); + goto close_error_exit; + } + } + + memset(&move,0,sizeof(struct changer_move)); + move.cm_totype = CHET_DT; + move.cm_tounit = drive; + move.cm_fromtype = CHET_ST; + move.cm_fromunit = slot; + if (ioctl(fd,CHIOMOVE,&move)) { + fprintf(stderr,PROG ": ioctl MOVE (load): %s\n", + strerror(errno)); + goto close_error_exit; + } + + if (eject_cdrom) + cdrom_tray(devs[drive],0); + + printf("%s\t:%s\n",mopt,devs[drive]); + exit(0); + + close_error_exit: + if (eject_cdrom) + cdrom_tray(devs[drive],0); + exit(1); +} diff --git a/autojuke.conf b/autojuke.conf new file mode 100644 index 0000000..d60cf28 --- /dev/null +++ b/autojuke.conf @@ -0,0 +1,8 @@ +# bool (0/1): try to umount by sending SIGUSR1 to automount +try_umount=1 + +# mount options for autofs, see autofs(5) +mopt=-fstype=auto + +# one line per device +dev=/dev/??? diff --git a/autojuke.man b/autojuke.man new file mode 100644 index 0000000..8df8208 --- /dev/null +++ b/autojuke.man @@ -0,0 +1,60 @@ +.TH autojuke 8 "(c) 1997-2001 Gerd Knorr" +.SH NAME +autojuke - a tool to access jukeboxes with autofs +.SH SYNOPSIS +/usr/sbin/automount /jukebox program /usr/sbin/autojuke +.SH DESCRIPTION +.B autojuke +is a "program map" (in autofs speak). You have to create a config +file (/etc/autojuke.conf) for it first (or copy & edit the sample +file), see below for a description of the syntax. +.P +After starting autofs you should be able to change & mount media just +by accessing /jukebox/<slot-nr> or by accessing +/jukebox/<volume-tag>. Error messages show up in the syslog +(/var/log/messages). +.SH CONFIG FILE +Syntax is very simple: The config file has just some "variable = value" +lines. These are the variables: +.TP +.B try_umount=0|1 +It is possible to signal the automound daemon (with SIGUSR1) it should +umount idle devices. If you set this to 1, autojuke will try this if +there is no idle drive. You probably want to use either this feature +or a short timeout value for automount. +.TP +.B mopts=... +Specify mount options (fs type for example), autojuke passes them to +the automount daemon. Check out the automount manpage for details. +.TP +.B dev=special file +specifies the special file(s) (/dev/something) of the data transfer +elements. There must be exactly one line per data transfer element. +autojuke uses this to look for idle drives (using /etc/mtab) and to +tell automount which device it should mount. Order is important, the +first line should address "d0", etc... +.SH NOTES +Volume tags work only if the changer supports them. +Double-sided media is not supported (yet). +.P +For CD-ROM's it is faster and cheaper these days if you copy images to +a big hard disk and mount the images using the loop device. This also +allows easy parallel access to different CD's at the same time. +.SH AUTHOR +Gerd Knorr <kraxel@bytesex.org> +.SH COPYRIGHT +Copyright (C) 2000 Gerd Knorr <kraxel@bytesex.org> +.P +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. +.P +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. +.P +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. @@ -0,0 +1,160 @@ +/* + * ioctl interface for the scsi media changer driver + */ + +/* changer element types */ +#define CHET_MT 0 /* media transport element (robot) */ +#define CHET_ST 1 /* storage element (media slots) */ +#define CHET_IE 2 /* import/export element */ +#define CHET_DT 3 /* data transfer element (tape/cdrom/whatever) */ +#define CHET_V1 4 /* vendor specific #1 */ +#define CHET_V2 5 /* vendor specific #2 */ +#define CHET_V3 6 /* vendor specific #3 */ +#define CHET_V4 7 /* vendor specific #4 */ + + +/* + * CHIOGPARAMS + * query changer properties + * + * CHIOVGPARAMS + * query vendor-specific element types + * + * accessing elements works by specifing type and unit of the element. + * for eample, storage elements are addressed with type = CHET_ST and + * unit = 0 .. cp_nslots-1 + * + */ +struct changer_params { + int cp_curpicker; /* current transport element */ + int cp_npickers; /* number of transport elements (CHET_MT) */ + int cp_nslots; /* number of storage elements (CHET_ST) */ + int cp_nportals; /* number of import/export elements (CHET_IE) */ + int cp_ndrives; /* number of data transfer elements (CHET_DT) */ +}; +struct changer_vendor_params { + int cvp_n1; /* number of vendor specific elems (CHET_V1) */ + char cvp_label1[16]; + int cvp_n2; /* number of vendor specific elems (CHET_V2) */ + char cvp_label2[16]; + int cvp_n3; /* number of vendor specific elems (CHET_V3) */ + char cvp_label3[16]; + int cvp_n4; /* number of vendor specific elems (CHET_V4) */ + char cvp_label4[16]; + int reserved[8]; +}; + + +/* + * CHIOMOVE + * move a medium from one element to another + */ +struct changer_move { + int cm_fromtype; /* type/unit of source element */ + int cm_fromunit; + int cm_totype; /* type/unit of destination element */ + int cm_tounit; + int cm_flags; +}; +#define CM_INVERT 1 /* flag: rotate media (for double-sided like MOD) */ + + +/* + * CHIOEXCHANGE + * move one medium from element #1 to element #2, + * and another one from element #2 to element #3. + * element #1 and #3 are allowed to be identical. + */ +struct changer_exchange { + int ce_srctype; /* type/unit of element #1 */ + int ce_srcunit; + int ce_fdsttype; /* type/unit of element #2 */ + int ce_fdstunit; + int ce_sdsttype; /* type/unit of element #3 */ + int ce_sdstunit; + int ce_flags; +}; +#define CE_INVERT1 1 +#define CE_INVERT2 2 + + +/* + * CHIOPOSITION + * move the transport element (robot arm) to a specific element. + */ +struct changer_position { + int cp_type; + int cp_unit; + int cp_flags; +}; +#define CP_INVERT 1 + + +/* + * CHIOGSTATUS + * get element status for all elements of a specific type + */ +struct changer_element_status { + int ces_type; + unsigned char *ces_data; +}; +#define CESTATUS_FULL 0x01 /* full */ +#define CESTATUS_IMPEXP 0x02 /* media was imported (inserted by sysop) */ +#define CESTATUS_EXCEPT 0x04 /* error condition */ +#define CESTATUS_ACCESS 0x08 /* access allowed */ +#define CESTATUS_EXENAB 0x10 /* element can export media */ +#define CESTATUS_INENAB 0x20 /* element can import media */ + + +/* + * CHIOGELEM + * get more detailed status informtion for a single element + */ +struct changer_get_element { + int cge_type; /* type/unit */ + int cge_unit; + int cge_status; /* status */ + int cge_errno; /* errno */ + int cge_srctype; /* source element of the last move/exchange */ + int cge_srcunit; + int cge_id; /* scsi id (for data transfer elements) */ + int cge_lun; /* scsi lun (for data transfer elements) */ + char cge_pvoltag[36]; /* primary volume tag */ + char cge_avoltag[36]; /* alternate volume tag */ + int cge_flags; +}; +/* flags */ +#define CGE_ERRNO 0x01 /* errno available */ +#define CGE_INVERT 0x02 /* media inverted */ +#define CGE_SRC 0x04 /* media src available */ +#define CGE_IDLUN 0x08 /* ID+LUN available */ +#define CGE_PVOLTAG 0x10 /* primary volume tag available */ +#define CGE_AVOLTAG 0x20 /* alternate volume tag available */ + + +/* + * CHIOSVOLTAG + * set volume tag + */ +struct changer_set_voltag { + int csv_type; /* type/unit */ + int csv_unit; + char csv_voltag[36]; /* volume tag */ + int csv_flags; +}; +#define CSV_PVOLTAG 0x01 /* primary volume tag */ +#define CSV_AVOLTAG 0x02 /* alternate volume tag */ +#define CSV_CLEARTAG 0x04 /* clear volume tag */ + +/* ioctls */ +#define CHIOMOVE _IOW('c', 1,struct changer_move) +#define CHIOEXCHANGE _IOW('c', 2,struct changer_exchange) +#define CHIOPOSITION _IOW('c', 3,struct changer_position) +#define CHIOGPICKER _IOR('c', 4,int) /* not impl. */ +#define CHIOSPICKER _IOW('c', 5,int) /* not impl. */ +#define CHIOGPARAMS _IOR('c', 6,struct changer_params) +#define CHIOGSTATUS _IOW('c', 8,struct changer_element_status) +#define CHIOGELEM _IOW('c',16,struct changer_get_element) +#define CHIOINITELEM _IO('c',17) +#define CHIOSVOLTAG _IOW('c',18,struct changer_set_voltag) +#define CHIOGVPARAMS _IOR('c',19,struct changer_vendor_params) @@ -0,0 +1,166 @@ +/* + * Simple doubly linked list implementation. + * -- shameless stolen from the linux kernel sources + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_add(struct list_head * new, + struct list_head * prev, + struct list_head * next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static __inline__ void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static __inline__ void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_del(struct list_head * prev, + struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is in an undefined state. + */ +static __inline__ void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static __inline__ void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static __inline__ int list_empty(struct list_head *head) +{ + return head->next == head; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static __inline__ void list_splice(struct list_head *list, struct list_head *head) +{ + struct list_head *first = list->next; + + if (first != list) { + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_prev - iterate over a list in reverse order + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev, prefetch(pos->prev); pos != (head); \ + pos = pos->prev, prefetch(pos->prev)) + @@ -0,0 +1,116 @@ +/* + * motif-based man page renderer + * (c) 2001 Gerd Knorr <kraxel@bytesex.org> + * + */ + +#include <stdio.h> + +#include <X11/Xlib.h> +#include <X11/Intrinsic.h> +#include <Xm/Xm.h> +#include <Xm/Form.h> +#include <Xm/Label.h> +#include <Xm/RowColumn.h> +#include <Xm/PushB.h> +#include <Xm/ScrolledW.h> +#include <Xm/SelectioB.h> + +#include "RegEdit.h" +#include "man.h" + +/*----------------------------------------------------------------------*/ + +#define MAN_UNDEF 0 +#define MAN_NORMAL 1 +#define MAN_BOLD 2 +#define MAN_UNDERLINE 3 + +static XmStringTag man_tags[] = { + NULL, NULL, "bold", "underline" +}; + +static void +man_destroy(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + XtDestroyWidget(clientdata); +} + +void +man(Widget parent, char *page) +{ + Widget dlg,view,label; + XmString xmpage,xmchunk; + char line[1024],chunk[256]; + int s,d,cur,last; + FILE *fp; + + /* build dialog */ + dlg = XmCreatePromptDialog(parent,"man",NULL,0); + XmdRegisterEditres(XtParent(dlg)); + XtUnmanageChild(XmSelectionBoxGetChild(dlg,XmDIALOG_SELECTION_LABEL)); + XtUnmanageChild(XmSelectionBoxGetChild(dlg,XmDIALOG_HELP_BUTTON)); + XtUnmanageChild(XmSelectionBoxGetChild(dlg,XmDIALOG_CANCEL_BUTTON)); + XtUnmanageChild(XmSelectionBoxGetChild(dlg,XmDIALOG_TEXT)); + XtAddCallback(dlg,XmNokCallback,man_destroy,dlg); + view = XmCreateScrolledWindow(dlg,"view",NULL,0); + XtManageChild(view); + label = XtVaCreateManagedWidget("label", xmLabelWidgetClass,view, NULL); + XtManageChild(dlg); + + /* fetch page and render into XmString */ + sprintf(line,"man %s 2>/dev/null",page); + fp = popen(line,"r"); + xmpage = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT, NULL); + while (NULL != fgets(line,sizeof(line)-1,fp)) { + last = MAN_UNDEF; + for (s = 0, d = 0; line[s] != '\0';) { + /* check current char */ + cur = MAN_NORMAL; + if (line[s+1] == '\010' && line[s] == line[s+2]) + cur = MAN_BOLD; + if (line[s] == '_' && line[s+1] == '\010') + cur = MAN_UNDERLINE; + /* add chunk if completed */ + if (MAN_UNDEF != last && cur != last) { + xmchunk = XmStringGenerate(chunk,NULL,XmMULTIBYTE_TEXT, + man_tags[last]); + xmpage = XmStringConcatAndFree(xmpage,xmchunk); + d = 0; + } + /* add char to chunk */ + switch (cur) { + case MAN_BOLD: + case MAN_UNDERLINE: + s += 2; + case MAN_NORMAL: + chunk[d++] = line[s++]; + break; + } + chunk[d] = '\0'; + last = cur; + } + /* add last chunk for line */ + xmchunk = XmStringGenerate(chunk, NULL, XmMULTIBYTE_TEXT, + man_tags[last]); + xmpage = XmStringConcatAndFree(xmpage,xmchunk); + } + XtVaSetValues(label,XmNlabelString,xmpage,NULL); + XmStringFree(xmpage); + fclose(fp); +} + +void +man_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + char *page = clientdata; + man(widget,page); +} + +void +man_action(Widget widget, XEvent *event, + String *params, Cardinal *num_params) +{ + if (*num_params > 0) + man(widget,params[0]); +} @@ -0,0 +1,4 @@ +void man(Widget parent, char *page); +void man_cb(Widget widget, XtPointer clientdata, XtPointer call_data); +void man_action(Widget widget, XEvent *event, + String *params, Cardinal *num_params); @@ -0,0 +1,367 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/ioctl.h> + +#include "chio.h" + +#define CHANGER "/dev/sch0" + +/* ---------------------------------------------------------------------- */ + +struct changer_params params; +struct changer_vendor_params vparams; + +int +parse_arg(char *arg, int *type, int *nr) +{ + char t; + + if (NULL == arg) + return -1; + if (2 != sscanf(arg,"%c%i",&t,nr)) + return -1; + switch (t) { + case 'm': + case 'M': *type = CHET_MT; break; + case 's': + case 'S': *type = CHET_ST; break; + case 'e': + case 'E': *type = CHET_IE; break; + case 'd': + case 'D': *type = CHET_DT; break; + case 'v': + case 'V': *type = CHET_V1; break; + case 'w': + case 'W': *type = CHET_V2; break; + case 'x': + case 'X': *type = CHET_V3; break; + case 'y': + case 'Y': *type = CHET_V4; break; + default: + return -1; + } + return 0; +} + +static void +print_stat(int fd, int type, int count) +{ + struct changer_element_status ces; + struct changer_get_element elinfo; + int i,rc; + + switch (type) { + case CHET_MT: printf("medium transport\n"); break; + case CHET_ST: printf("storage\n"); break; + case CHET_IE: printf("import/export\n"); break; + case CHET_DT: printf("data transfer\n"); break; + case CHET_V1: + printf("vendor specific #1 [%s]\n",vparams.cvp_label1); + break; + case CHET_V2: + printf("vendor specific #2 [%s]\n",vparams.cvp_label2); + break; + case CHET_V3: + printf("vendor specific #3 [%s]\n",vparams.cvp_label3); + break; + case CHET_V4: + printf("vendor specific #4 [%s]\n",vparams.cvp_label4); + break; + } + + ces.ces_type = type; + ces.ces_data = malloc(count); + rc = ioctl(fd,CHIOGSTATUS,&ces); + if (rc) { + fprintf(stderr,"ioctl failed (GSTATUS): %s\n",strerror(errno)); + exit(1); + } + for (i = 0; i < count; i++) { + printf(" %2d: ",i); + if (ces.ces_data[i] & CESTATUS_INENAB) printf("inenab "); + if (ces.ces_data[i] & CESTATUS_EXENAB) printf("exenab "); + if (ces.ces_data[i] & CESTATUS_ACCESS) printf("access "); + if (ces.ces_data[i] & CESTATUS_EXCEPT) printf("except "); + if (ces.ces_data[i] & CESTATUS_IMPEXP) printf("impexp "); + if (ces.ces_data[i] & CESTATUS_FULL) printf("full "); + + memset(&elinfo,0,sizeof(elinfo)); + elinfo.cge_type = type; + elinfo.cge_unit = i; + + rc = ioctl(fd,CHIOGELEM,&elinfo); + if (rc) { + fprintf(stderr,"ioctl failed (GELEM): %s\n",strerror(errno)); + exit(1); + } + + if (elinfo.cge_flags & CGE_PVOLTAG && strlen(elinfo.cge_pvoltag) > 0 ) { + printf("\ttag: '%s'",elinfo.cge_pvoltag); + } else + if (ces.ces_data[i] & CESTATUS_FULL) + printf("\t(no volume tag)"); + + + printf("\n"); + } + free(ces.ces_data); +} + +/* ---------------------------------------------------------------------- */ + +int +main(int argc, char *argv[]) +{ + int fd,rc; + + if (-1 == (fd = open(CHANGER,O_RDONLY))) { + perror("open"); + exit(1); + } + + rc = ioctl(fd,CHIOGPARAMS,¶ms); + if (rc) { + fprintf(stderr,"ioctl failed (GPARAMS): %s\n",strerror(errno)); + exit(1); + } else { + printf(CHANGER ": mt=%i st=%i ie=%i dt=%i\n", + params.cp_npickers,params.cp_nslots, + params.cp_nportals,params.cp_ndrives); + } + rc = ioctl(fd,CHIOGVPARAMS,&vparams); + if (rc) { + fprintf(stderr,"ioctl failed (VPARAMS): %s\n",strerror(errno)); + } else { + if (vparams.cvp_n1) + printf(CHANGER ": v1=%d [%s]\n",vparams.cvp_n1,vparams.cvp_label1); + if (vparams.cvp_n2) + printf(CHANGER ": v2=%d [%s]\n",vparams.cvp_n2,vparams.cvp_label2); + if (vparams.cvp_n3) + printf(CHANGER ": v3=%d [%s]\n",vparams.cvp_n3,vparams.cvp_label3); + if (vparams.cvp_n4) + printf(CHANGER ": v4=%d [%s]\n",vparams.cvp_n4,vparams.cvp_label4); + } + + if (argc == 1 || 0 == strcasecmp(argv[1],"status")) { + /* no args, so print some status informations */ + print_stat(fd,CHET_MT,params.cp_npickers); + print_stat(fd,CHET_ST,params.cp_nslots); + print_stat(fd,CHET_IE,params.cp_nportals); + print_stat(fd,CHET_DT,params.cp_ndrives); + if (vparams.cvp_n1) + print_stat(fd,CHET_V1,vparams.cvp_n1); + if (vparams.cvp_n2) + print_stat(fd,CHET_V2,vparams.cvp_n2); + if (vparams.cvp_n3) + print_stat(fd,CHET_V3,vparams.cvp_n3); + if (vparams.cvp_n4) + print_stat(fd,CHET_V4,vparams.cvp_n4); + exit(0); + } + + if (0 == strcasecmp(argv[1],"pos")) { + struct changer_position pos; + + memset(&pos,0,sizeof(pos)); + if (-1 == parse_arg(argv[2],&pos.cp_type,&pos.cp_unit)) { + fprintf(stderr,"slot arg parse error (POSITION)\n"); + exit(1); + } + if (argc > 3) + pos.cp_flags = atoi(argv[3]); + rc = ioctl(fd,CHIOPOSITION,&pos); + if (rc) { + fprintf(stderr,"ioctl failed (POSITION): %s\n", + strerror(errno)); + exit(1); + } + + } else if (0 == strcasecmp(argv[1],"load") || + 0 == strcasecmp(argv[1],"unload")) { + struct changer_move move; + + memset(&move,0,sizeof(move)); + if (0 == strcasecmp(argv[1],"load")) { + move.cm_fromtype = CHET_ST; + move.cm_fromunit = atoi(argv[2]); + move.cm_totype = CHET_DT; + move.cm_tounit = 0; + } else { + if (argc > 2) { + move.cm_totype = CHET_ST; + move.cm_tounit = atoi(argv[2]); + } else { + struct changer_get_element elinfo; + memset(&elinfo,0,sizeof(elinfo)); + elinfo.cge_type = CHET_DT; + elinfo.cge_unit = 0; + rc = ioctl(fd,CHIOGELEM,&elinfo); + if (rc) { + fprintf(stderr,"ioctl failed (GELEM): %s\n", + strerror(errno)); + exit(1); + } + if (!(elinfo.cge_flags & CGE_SRC)) { + fprintf(stderr,"element source info not available\n"); + exit(1); + } + move.cm_totype = elinfo.cge_srctype; + move.cm_tounit = elinfo.cge_srcunit; + if (elinfo.cge_flags & CGE_INVERT) + move.cm_flags |= CE_INVERT1; + } + move.cm_fromtype = CHET_DT; + move.cm_fromunit = 0; + } + rc = ioctl(fd,CHIOMOVE,&move); + if (rc) { + fprintf(stderr,"ioctl failed (MOVE): %s\n", + strerror(errno)); + exit(1); + } + + } else if (0 == strcasecmp(argv[1],"mv")) { + struct changer_move move; + + memset(&move,0,sizeof(move)); + if (-1 == parse_arg(argv[2],&move.cm_fromtype,&move.cm_fromunit) || + -1 == parse_arg(argv[3],&move.cm_totype, &move.cm_tounit )) { + fprintf(stderr,"slot arg parse error (MOVE)\n"); + exit(1); + } + if (argc > 4) + move.cm_flags = atoi(argv[4]); + rc = ioctl(fd,CHIOMOVE,&move); + if (rc) { + fprintf(stderr,"ioctl failed (MOVE): %s\n", + strerror(errno)); + exit(1); + } + + } else if (0 == strcasecmp(argv[1],"ex")) { + struct changer_exchange xchg; + + memset(&xchg,0,sizeof(xchg)); + if (-1 == parse_arg(argv[2],&xchg.ce_srctype, &xchg.ce_srcunit ) || + -1 == parse_arg(argv[3],&xchg.ce_fdsttype,&xchg.ce_fdstunit) || + -1 == parse_arg(argv[4],&xchg.ce_sdsttype,&xchg.ce_sdstunit)) { + fprintf(stderr,"slot arg parse error (EXCHANGE)\n"); + exit(1); + } + if (argc > 5) + xchg.ce_flags = atoi(argv[5]); + rc = ioctl(fd,CHIOEXCHANGE,&xchg); + if (rc) { + fprintf(stderr,"ioctl failed (EXCHANGE): %s\n", + strerror(errno)); + exit(1); + } + + } else if (0 == strcasecmp(argv[1],"info")) { + struct changer_get_element elinfo; + + memset(&elinfo,0,sizeof(elinfo)); + if (-1 == parse_arg(argv[2],&elinfo.cge_type, &elinfo.cge_unit)) { + fprintf(stderr,"slot arg parse error (INFO)\n"); + exit(1); + } + rc = ioctl(fd,CHIOGELEM,&elinfo); + if (rc) { + fprintf(stderr,"ioctl failed (INFO): %s\n", + strerror(errno)); + exit(1); + } + + printf("status : "); + if (elinfo.cge_status & CESTATUS_INENAB) printf("inenab "); + if (elinfo.cge_status & CESTATUS_EXENAB) printf("exenab "); + if (elinfo.cge_status & CESTATUS_ACCESS) printf("access "); + if (elinfo.cge_status & CESTATUS_EXCEPT) printf("except "); + if (elinfo.cge_status & CESTATUS_IMPEXP) printf("impexp "); + if (elinfo.cge_status & CESTATUS_FULL) printf("full "); + printf("\n"); + + if (elinfo.cge_flags & CGE_ERRNO) { + printf("error : %s\n",strerror(elinfo.cge_errno)); + } + if (elinfo.cge_flags & CGE_SRC) { + printf("source : "); + switch (elinfo.cge_srctype) { + case CHET_MT: printf("m"); break; + case CHET_ST: printf("s"); break; + case CHET_IE: printf("e"); break; + case CHET_DT: printf("d"); break; + case CHET_V1: printf("v"); break; + case CHET_V2: printf("w"); break; + case CHET_V3: printf("x"); break; + case CHET_V4: printf("y"); break; + } + printf("%d",elinfo.cge_srcunit); + if (elinfo.cge_flags & CGE_INVERT) + printf(" (rotated)"); + printf("\n"); + } + if (elinfo.cge_flags & CGE_IDLUN) { + printf("scsi id : %d\n",elinfo.cge_id); + printf("scsi lun: %d\n",elinfo.cge_lun); + } + if (elinfo.cge_flags & CGE_PVOLTAG) { + printf("pvoltag : %32.32s\n",elinfo.cge_pvoltag); + } + if (elinfo.cge_flags & CGE_AVOLTAG) { + printf("avoltag : %32.32s\n",elinfo.cge_avoltag); + } + + } else if (0 == strcasecmp(argv[1],"init")) { + rc = ioctl(fd,CHIOINITELEM,NULL); + if (rc) { + fprintf(stderr,"ioctl failed (INITELEM): %s\n", + strerror(errno)); + exit(1); + } + + } else if (0 == strcasecmp(argv[1],"settag") || + 0 == strcasecmp(argv[1],"cleartag")) { + struct changer_set_voltag tag; + + memset(&tag,0,sizeof(tag)); + if (-1 == parse_arg(argv[2],&tag.csv_type,&tag.csv_unit)) { + fprintf(stderr,"slot arg parse error (SVOLTAG)\n"); + exit(1); + } + if (argc <= 3) { + fprintf(stderr,"no type (pri | alt) given\n"); + exit(1); + } + if (0 == strcasecmp(argv[3],"pri")) + tag.csv_flags |= CSV_PVOLTAG; + if (0 == strcasecmp(argv[3],"alt")) + tag.csv_flags |= CSV_AVOLTAG; + if (0 == strcasecmp(argv[1],"cleartag")) { + tag.csv_flags |= CSV_CLEARTAG; + } else { + if (argc <= 4) { + fprintf(stderr,"no volume tag given\n"); + exit(1); + } + strncpy(tag.csv_voltag,argv[4],32); + } + rc = ioctl(fd,CHIOSVOLTAG,&tag); + if (rc) { + fprintf(stderr,"ioctl failed (SVOLTAG): %s\n", + strerror(errno)); + exit(1); + } + + } else { + printf("%s: unknown argument\n",argv[1]); + exit(1); + } + + close(fd); + exit(0); +} diff --git a/mover.man b/mover.man new file mode 100644 index 0000000..6bde895 --- /dev/null +++ b/mover.man @@ -0,0 +1,105 @@ +.TH mover 1 "(c) 1997-2002 Gerd Knorr" +.SH NAME +mover - utility to control scsi media changers +.SH SYNOPSIS +.B mover [ command ] +.SH DESCRIPTION +.B mover +is a command line utility for talking to the scsi media changer +driver. +.P +If you start it without any arguments, it will print the status bits +and volume tags (if available) for each changer slot. The status bits +are: +.TP +.B full +set if the element holds a media +.TP +.B access +set if the changer can or is allowed to access the element. If this +bit is not set for the data transfer element, you'll probably have to +use eject the media first. +.TP +.B except +exception - something went really wrong. +.TP +.B inenab +set if the element can import media +.TP +.B exenab +set if the element can export media +.TP +.B impexp +set if the media in the element has been inserted from outside +.P +The last three are used for import/export elements only. +.SH COMMANDS +.TP +.B mover mv <src> <dest> [<rotate>] +This command moves the media from element <src> to element <dest>. +<rotate> can be 0 (default if not specified) or 1. If it is set to 1, +the media will be rotated. That's important for double-sided media +(some MO changers for example). +.br +<src> and <dest> are a letter/number combination. The letter is the +element type (t,s,e or d) and the number is the number of the element, +numbering start with zero. "s4" is slot #5, "d0" the read/write +device, ... Hope you get the idea. +.TP +.B mover ex <src> <dest1> <dest2> [<rotate>] +This command moves media1 from <src> to <dest1>, and media2 from +<dest1> to <dest2>. The scsi standard allows <src> and <dest2> to +address the same element (i.e. exchange <src> and <dest1>). <rotate> +can have the values 0, 1, 2 and 3 here. "1" will rotate media1, "2" +will rotate media2, "3" will rotate both. +.br +The exchange command is optional for SCSI changer devices, so this +might not work if the hardware does not support it. +.TP +.B mover pos <elem> [rotate] +This positions the transport arm in front of element <elem>. This +command is optional too. +.TP +.B mover load <nr> +Move media from storage element <nr> into the first device. This is +just a easy-to-remember shortcut for "mover mv s<nr> d0". +.TP +.B mover unload [<nr>] +Reverse of load, i.e. it moves the media from the first drive to the +storage slot <nr>. The <nr> argument is optional, if it is missing +the media will be moved back to the slot where it was taken from. +This depends on a optional feature too, therefore the command might +not work without the slot number. +.TP +.B mover info <elem> +prints detailed status informations about a element. +.TP +.B mover init +initialize element status (usually makes the changer scan all +elements, may take some time). +.TP +.B mover settag <elem> [ pri | alt ] <tag> +set primary / alternate volume tag for <elem> to <tag>. +.TP +.B mover cleartag <elem> [ pri | alt ] <side> +clear primary / alternate volume tag for <elem>. +.SH SEE ALSO +xmover(1) +.SH AUTHOR +Gerd Knorr <kraxel@bytesex.org> +.SH COPYRIGHT +Copyright (C) 1997-2002 Gerd Knorr <kraxel@bytesex.org> +.P +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. +.P +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. +.P +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. diff --git a/unload.c b/unload.c new file mode 100644 index 0000000..a1005c2 --- /dev/null +++ b/unload.c @@ -0,0 +1,65 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/ioctl.h> + +#include "scsi/scsi.h" +#include "scsi/scsi_ioctl.h" + +#define DEFAULT_DEVICE "/dev/sda" + +typedef struct scsi_ioctl_command { + unsigned int inlen; + unsigned int outlen; + unsigned char data[0]; +} Scsi_Ioctl_Command; + + +int +main(int argc, char *argv[]) +{ + int fd; + char *prog, device[256], request[256], *h; + Scsi_Ioctl_Command *sic = (Scsi_Ioctl_Command*)request; + + h = strrchr(argv[0],'/'); + prog = h ? h+1 : argv[0]; + + if (argc > 1) { + strcpy(device,argv[1]); + } else { + strcpy(device,DEFAULT_DEVICE); + } + + if (-1 == (fd = open(device, O_RDONLY))) { + fprintf(stderr,"%s: can't open %s: %s\n",prog,device, + sys_errlist[errno]); + exit(1); + } + + if (-1 == ioctl(fd,SCSI_IOCTL_DOORUNLOCK,sic)) { + perror("ioctl (unlock)"); + exit(1); + } + + memset(request,0,256); + sic->inlen = 0; + sic->outlen = 0; + sic->data[0] = START_STOP; + sic->data[1] = 0; + sic->data[2] = 0; + sic->data[3] = 0; + sic->data[4] = (0 == strcmp(prog,"unload")) ? 2 : 3; + sic->data[5] = 0; + + if (-1 == ioctl(fd,SCSI_IOCTL_SEND_COMMAND,sic)) { + perror("ioctl (eject)"); + exit(1); + } + + close(fd); + exit(0); +} diff --git a/unload.man b/unload.man new file mode 100644 index 0000000..ff23984 --- /dev/null +++ b/unload.man @@ -0,0 +1,27 @@ +.TH unload 8 +.SH NAME +unload - load/unload scsi media +.SH SYNOPSIS +.B load <device> +.B unload <device> +.SH DESCRIPTION +The unload program can be used to unload (eject) and load any +removeable SCSI media. It takes a device as argument, unlocks it and +sends a START STOP command. Without argument it uses the compiled-in +default device (there is a #define in the source to change this it you +want). It unloads if the program name is "unload", and loads else. +.P +unload can eject everything: CD-ROM, MOD, ZIP, whatelse. Even for tape +libraries this is useful: Most (all?) of them load the next tape in +order if you send a unload command to the tape device. +.P +It requires root permissions, as the SCSI_IOCTL_SEND_COMMAND ioctl is +allowed for root only (for obvious reasons). +.SH WARNING +It is a quick & dirty hack. It does no sanity checks. It does not ask +stupid questions. It simply does what you have asked for. You can +eject mounted media with it, so use it with care. You have been warned! +.SH AUTHOR +anonymous :-) +.SH COPYRIGHT +This tool is public domain. diff --git a/xmover.c b/xmover.c new file mode 100644 index 0000000..241f0a7 --- /dev/null +++ b/xmover.c @@ -0,0 +1,777 @@ +/* + * xmover -- X11 frontend for scsi media changers + * + * (c) 2002 Gerd Knorr <kraxel@bytesex.org> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/ioctl.h> + +#include <X11/Xlib.h> +#include <X11/Intrinsic.h> +#include <X11/Xatom.h> +#include <X11/cursorfont.h> +#include <Xm/Xm.h> +#include <Xm/Form.h> +#include <Xm/Label.h> +#include <Xm/RowColumn.h> +#include <Xm/CascadeB.h> +#include <Xm/PushB.h> +#include <Xm/DrawingA.h> +#include <Xm/Protocols.h> +#include <Xm/Separator.h> +#include <Xm/Frame.h> +#include <Xm/ScrolledW.h> +#include <Xm/MessageB.h> +#include <Xm/SelectioB.h> +#include <Xm/TransferP.h> +#include <Xm/Text.h> + +#include "chio.h" + +#include "RegEdit.h" +#include "list.h" +#include "man.h" + +/* --------------------------------------------------------------------- */ + +XtAppContext app_context; + +static String fallback_ressources[] = { +#include "xmover.h" + NULL +}; + +struct ARGS { + char *device; + int help; + int debug; +} args; + +XtResource args_desc[] = { + /* name, class, type, size, offset, default_type, default_addr */ + { + /* Strings */ + "device", + XtCString, XtRString, sizeof(char*), + XtOffset(struct ARGS*,device), + XtRString, "/dev/sch0", + },{ + /* Integer */ + "help", + XtCValue, XtRInt, sizeof(int), + XtOffset(struct ARGS*,help), + XtRString, "0" + },{ + "debug", + XtCValue, XtRInt, sizeof(int), + XtOffset(struct ARGS*,debug), + XtRString, "0" + } +}; +const int args_count = XtNumber(args_desc); + +XrmOptionDescRec opt_desc[] = { + { "-c", "device", XrmoptionSepArg, NULL }, + { "-device", "device", XrmoptionSepArg, NULL }, + + { "-debug", "debug", XrmoptionNoArg, "1" }, + { "-h", "help", XrmoptionNoArg, "1" }, + { "-help", "help", XrmoptionNoArg, "1" }, + { "--help", "help", XrmoptionNoArg, "1" }, +}; +const int opt_count = (sizeof(opt_desc)/sizeof(XrmOptionDescRec)); + +/* prototypes */ +static void elem_convert(Widget, XtPointer, XtPointer); +static void elem_destination(Widget, XtPointer, XtPointer); + +/* --------------------------------------------------------------------- */ +/* atoms */ + +static Atom XA_WM_DELETE_WINDOW; + +static Atom XA_TARGETS; +static Atom _MOTIF_EXPORT_TARGETS; + +static Atom _XMOVER_TYPEUNIT; +static Atom _XMOVER_REFRESH; + +static Atom targets[2]; +static Cardinal ntargets; + +void init_atoms(Display *dpy) +{ + XA_WM_DELETE_WINDOW = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + + XA_TARGETS = XInternAtom(dpy, "TARGETS", False); + _MOTIF_EXPORT_TARGETS = XInternAtom(dpy, "_MOTIF_EXPORT_TARGETS", False); + + _XMOVER_TYPEUNIT = XInternAtom(dpy, "_XMOVER_TYPEUNIT", False); + _XMOVER_REFRESH = XInternAtom(dpy, "_XMOVER_REFRESH", False); + + targets[ntargets++] = _XMOVER_TYPEUNIT; + targets[ntargets++] = _XMOVER_REFRESH; +} + +/* ---------------------------------------------------------------------- */ +/* redirect stderr to error dialog box */ + +struct stderr_handler { + Widget box; + XmString str; + int pipe; + XtInputId id; +}; + +static void +stderr_input(XtPointer clientdata, int *src, XtInputId *id) +{ + struct stderr_handler *h = clientdata; + XmString item; + Widget label; + char buf[1024]; + int rc; + + rc = read(h->pipe,buf,sizeof(buf)-1); + if (rc <= 0) { + /* Oops */ + XtRemoveInput(h->id); + close(h->pipe); + XtDestroyWidget(h->box); + free(h); + } + buf[rc] = 0; + item = XmStringGenerate(buf, NULL, XmMULTIBYTE_TEXT,NULL); + h->str = XmStringConcatAndFree(h->str,item); + label = XmMessageBoxGetChild(h->box,XmDIALOG_MESSAGE_LABEL); + XtVaSetValues(label,XmNlabelString,h->str,NULL); + XtManageChild(h->box); +}; + +static void +stderr_ok_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + struct stderr_handler *h = clientdata; + + XmStringFree(h->str); + h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL); + XtUnmanageChild(h->box); +} + +static void +stderr_close_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + struct stderr_handler *h = clientdata; + + XmStringFree(h->str); + h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL); +} + +static void +stderr_init(Widget parent) +{ + struct stderr_handler *h; + int p[2]; + + h = malloc(sizeof(*h)); + memset(h,0,sizeof(*h)); + h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL); + h->box = XmCreateErrorDialog(parent,"errbox",NULL,0); + XtUnmanageChild(XmMessageBoxGetChild(h->box,XmDIALOG_HELP_BUTTON)); + XtUnmanageChild(XmMessageBoxGetChild(h->box,XmDIALOG_CANCEL_BUTTON)); + XtAddCallback(h->box,XmNokCallback,stderr_ok_cb,h); + XtAddCallback(XtParent(h->box),XmNpopdownCallback,stderr_close_cb,h); + pipe(p); + dup2(p[1],2); + close(p[1]); + h->pipe = p[0]; + h->id = XtAppAddInput(app_context,h->pipe,(XtPointer)XtInputReadMask, + stderr_input,h); +} + +/* ---------------------------------------------------------------------- */ +/* pointer games */ + +struct ptr_list { + struct list_head list; + Widget widget; +}; +static struct list_head ptr_wins; +static Cursor ptr_idle; +static Cursor ptr_busy; + +void ptr_register(Widget widget) +{ + struct ptr_list *item; + + if (XtWindow(widget)) + XDefineCursor(XtDisplay(widget), XtWindow(widget),ptr_idle); + item = malloc(sizeof(*item)); + memset(item,0,sizeof(*item)); + item->widget = widget; + list_add_tail(&item->list,&ptr_wins); +} + +void ptr_unregister(Widget widget) +{ + struct list_head *item; + struct ptr_list *win,*del; + + del = NULL; + list_for_each(item,&ptr_wins) { + win = list_entry(item, struct ptr_list, list); + if (win->widget == widget) + del = win; + } + if (del) + list_del(&del->list); +} + +void ptr_set(Cursor ptr) +{ + struct list_head *item; + struct ptr_list *win; + Display *dpy = NULL; + + list_for_each(item,&ptr_wins) { + win = list_entry(item, struct ptr_list, list); + if (!XtWindow(win->widget)) + continue; + dpy = XtDisplay(win->widget); + XDefineCursor(dpy, XtWindow(win->widget),ptr); + } + if (dpy) + XSync(dpy,False); +} + +void ptr_init(Widget shell) +{ + XColor white,red,dummy; + Colormap cmap = DefaultColormapOfScreen(XtScreen(shell)); + + INIT_LIST_HEAD(&ptr_wins); + ptr_idle = XCreateFontCursor(XtDisplay(shell),XC_left_ptr); + ptr_busy = XCreateFontCursor(XtDisplay(shell),XC_watch); + if (XAllocNamedColor(XtDisplay(shell),cmap,"white",&white,&dummy) && + XAllocNamedColor(XtDisplay(shell),cmap,"red",&red,&dummy)) { + XRecolorCursor(XtDisplay(shell),ptr_idle,&red,&white); + XRecolorCursor(XtDisplay(shell),ptr_busy,&red,&white); + } +} + +/* --------------------------------------------------------------------- */ +/* handle changer elements */ + +struct changer_elem { + struct list_head list; + int type; + int unit; + Widget draw,label; + Widget menu,mref,mtag,msrc; + struct changer_get_element info; +}; + +static int fd; +static struct changer_params params; +static struct list_head elems; + +static struct changer_elem* +elem_by_typeunit(int type, int unit) +{ + struct list_head *item; + struct changer_elem *elem; + + list_for_each(item,&elems) { + elem = list_entry(item, struct changer_elem, list); + if (elem->type == type && + elem->unit == unit) + return elem; + } + return NULL; +} + +static void update_elem(struct changer_elem *elem) +{ + XmString item,str = NULL; + char buf[64]; + int err; + + /* query */ + elem->info.cge_type = elem->type; + elem->info.cge_unit = elem->unit; + err = ioctl(fd,CHIOGELEM,&elem->info); + if (-1 == err) { + fprintf(stderr,"CHIOGELEM(%d/%d): %s\n", + elem->type,elem->unit,strerror(errno)); + } + + /* build XmString */ + sprintf(buf,"#%03d",elem->unit); + item = XmStringGenerate(buf,NULL,XmMULTIBYTE_TEXT,NULL); + str = XmStringConcatAndFree(str,item); + if (elem->info.cge_flags & CGE_PVOLTAG) { + sprintf(buf," [%-32.32s]",elem->info.cge_pvoltag); + item = XmStringGenerate(buf,NULL,XmMULTIBYTE_TEXT,"voltag"); + str = XmStringConcatAndFree(str,item); + } + if (elem->info.cge_status & CESTATUS_FULL) { + item = XmStringGenerate(" full",NULL,XmMULTIBYTE_TEXT,"full"); + str = XmStringConcatAndFree(str,item); + } + if (elem->info.cge_status & CESTATUS_EXCEPT) { + item = XmStringGenerate(" error",NULL,XmMULTIBYTE_TEXT,"error"); + str = XmStringConcatAndFree(str,item); + } + + /* commit */ + XtVaSetValues(elem->label,XmNlabelString,str,NULL); + XmStringFree(str); +} + +static void +elem_tag_done_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + XmSelectionBoxCallbackStruct *cd = call_data; + struct changer_elem *elem = clientdata; + Widget text; + char *str; + + if (args.debug) + fprintf(stderr,"elem_tag_done_cb\n"); + if (cd->reason == XmCR_OK) { + struct changer_set_voltag tag; + text = XmSelectionBoxGetChild(widget,XmDIALOG_TEXT); + str = XmTextGetString(text); + memset(&tag,0,sizeof(tag)); + tag.csv_type = elem->type; + tag.csv_unit = elem->unit; + tag.csv_flags = CSV_PVOLTAG; + if (strlen(str) > 0) + strncpy(tag.csv_voltag,str,32); + else + tag.csv_flags |= CSV_CLEARTAG; + ptr_set(ptr_busy); + if (-1 == ioctl(fd,CHIOSVOLTAG,&tag)) + perror("CHIOSVOLTAG"); + update_elem(elem); + ptr_set(ptr_idle); + } + XtDestroyWidget(XtParent(widget)); +} + +static void +elem_tag_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + struct changer_elem *elem = clientdata; + Widget box,text; + + if (args.debug) + fprintf(stderr,"elem_tag_cb\n"); + box = XmCreatePromptDialog(elem->draw,"tag",NULL,0); + text = XmSelectionBoxGetChild(box,XmDIALOG_TEXT); + if (elem->info.cge_flags & CGE_PVOLTAG) + XmTextSetString(text,elem->info.cge_pvoltag); + + XtAddCallback(box,XmNokCallback,elem_tag_done_cb,elem); + XtAddCallback(box,XmNcancelCallback,elem_tag_done_cb,elem); + XmdRegisterEditres(XtParent(box)); + XtManageChild(box); +} + +static void +elem_src_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + struct changer_elem *elem = clientdata; + struct changer_elem *src; + struct changer_move move; + + if (args.debug) + fprintf(stderr,"elem_src_cb\n"); + src = elem_by_typeunit(elem->info.cge_srctype,elem->info.cge_srcunit); + memset(&move,0,sizeof(move)); + move.cm_fromtype = elem->type; + move.cm_fromunit = elem->unit; + move.cm_totype = elem->info.cge_srctype; + move.cm_tounit = elem->info.cge_srcunit; + ptr_set(ptr_busy); + if (-1 == ioctl(fd,CHIOMOVE,&move)) + perror("CHIOMOVE"); + update_elem(elem); + update_elem(src); + ptr_set(ptr_idle); +} + +static void +elem_refresh_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + struct changer_elem *elem = clientdata; + + ptr_set(ptr_busy); + update_elem(elem); + ptr_set(ptr_idle); +} + +static void +elem_menu_eh(Widget widget, XtPointer clientdata, XEvent *event, Boolean *cont) +{ + struct changer_elem *elem = clientdata; + Boolean tag,src; + + if (args.debug) + fprintf(stderr,"elem_menu_eh\n"); + tag = (elem->info.cge_status & CESTATUS_FULL) ? True : False; + XtVaSetValues(elem->mtag,XmNsensitive,tag,NULL); + if (elem->msrc) { + src = (elem->info.cge_status & CESTATUS_FULL) && + (elem->info.cge_flags & CGE_SRC); + XtVaSetValues(elem->msrc,XmNsensitive,src,NULL); + } + XmMenuPosition(elem->menu,(XButtonPressedEvent*)event); + XtManageChild(elem->menu); +} + +static struct changer_elem* +add_elem(Widget parent, int type, int unit) +{ + struct changer_elem *elem; + Cardinal n = 0; + Arg args[4]; + char name[32]; + + elem = malloc(sizeof(*elem)); + memset(elem,0,sizeof(*elem)); + sprintf(name,"%d/%d",type,unit); + elem->type = type; + elem->unit = unit; + elem->draw = XtVaCreateManagedWidget("d",xmDrawingAreaWidgetClass,parent, + NULL); + elem->label = XtVaCreateManagedWidget(name,xmLabelWidgetClass,elem->draw, + NULL); + update_elem(elem); + + /* dnd */ + XtAddCallback(elem->label,XmNconvertCallback,elem_convert,elem); + XtAddCallback(elem->draw,XmNdestinationCallback,elem_destination,elem); + XtSetArg(args[n], XmNimportTargets, targets); n++; + XtSetArg(args[n], XmNnumImportTargets, ntargets); n++; + XmeDropSink(elem->draw,args,n); + + /* context menu */ + elem->menu = XmCreatePopupMenu(elem->draw,"menu",NULL,0); + XtAddEventHandler(elem->draw,ButtonPressMask,False,elem_menu_eh,elem); + elem->mref = XtVaCreateManagedWidget("ref",xmPushButtonWidgetClass, + elem->menu,NULL); + XtAddCallback(elem->mref,XmNactivateCallback,elem_refresh_cb,elem); + elem->mtag = XtVaCreateManagedWidget("tag",xmPushButtonWidgetClass, + elem->menu,NULL); + XtAddCallback(elem->mtag,XmNactivateCallback,elem_tag_cb,elem); + if (elem->type == CHET_DT) { + elem->msrc = XtVaCreateManagedWidget("src",xmPushButtonWidgetClass, + elem->menu,NULL); + XtAddCallback(elem->msrc,XmNactivateCallback,elem_src_cb,elem); + } + + list_add_tail(&elem->list,&elems); + return elem; +} + +/* --------------------------------------------------------------------- */ +/* drag'n'drop stuff */ + +static void +elem_convert(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + XmConvertCallbackStruct *ccs = call_data; + struct changer_elem *elem = clientdata; + unsigned long *largs; + char *name; + Atom *targs; + int n; + + if (args.debug) { + name = XGetAtomName(XtDisplay(widget),ccs->target); + fprintf(stderr,"elem_convert %s\n",name); + XFree(name); + } + + if ((ccs->target == XA_TARGETS) || + (ccs->target == _MOTIF_EXPORT_TARGETS)) { + targs = (Atom*)XtMalloc(sizeof(Atom)*8); + n = 0; + targs[n++] = _XMOVER_TYPEUNIT; + targs[n++] = _XMOVER_REFRESH; + ccs->value = targs; + ccs->length = n; + ccs->type = XA_ATOM; + ccs->format = 32; + ccs->status = XmCONVERT_MERGE; + } + if (ccs->target == _XMOVER_TYPEUNIT) { + largs = (unsigned long*)XtMalloc(sizeof(*largs)); + largs[0] = (elem->type << 16) | elem->unit; + ccs->value = largs; + ccs->length = 1; + ccs->type = XA_INTEGER; + ccs->format = 32; + ccs->status = XmCONVERT_DONE; + } + if (ccs->target == _XMOVER_REFRESH) { + ptr_set(ptr_busy); + update_elem(elem); + ptr_set(ptr_idle); + ccs->value = NULL; + ccs->length = 0; + ccs->type = XA_INTEGER; + ccs->format = 32; + ccs->status = XmCONVERT_DONE; + } +} + +static void +elem_transfer(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + XmSelectionCallbackStruct *scs = call_data; + struct changer_elem *elem = clientdata; + unsigned long *ldata = scs->value; + XmTransferStatus status = XmTRANSFER_DONE_SUCCEED; + Time time = XtLastTimestampProcessed(XtDisplay(widget)); + int pending,i; + char *name; + + if (args.debug) { + name = XGetAtomName(XtDisplay(widget),scs->target); + fprintf(stderr,"elem_transfer %s\n",name); + XFree(name); + } + + pending = scs->remaining; + if (scs->target == XA_TARGETS) { + for (i = 0; i < scs->length; i++) { + if (ldata[i] == _XMOVER_TYPEUNIT) { + XmTransferValue(scs->transfer_id, ldata[i], elem_transfer, + elem, time); + pending++; + } + } + if (pending == scs->remaining) { + /* no target found */ + status = XmTRANSFER_DONE_FAIL; + } + } + if (scs->target == _XMOVER_TYPEUNIT) { + struct changer_move move; + + if (args.debug) + fprintf(stderr,"move: %ld/%ld => %d/%d\n", + ldata[0] >> 16, ldata[0] & 0xffff, + elem->type, elem->unit); + memset(&move,0,sizeof(move)); + move.cm_fromtype = ldata[0] >> 16; + move.cm_fromunit = ldata[0] & 0xffff; + move.cm_totype = elem->type; + move.cm_tounit = elem->unit; + ptr_set(ptr_busy); + if (move.cm_fromtype != move.cm_totype || + move.cm_fromunit != move.cm_tounit) + if (-1 == ioctl(fd,CHIOMOVE,&move)) + perror("CHIOMOVE"); + update_elem(elem); + ptr_set(ptr_idle); + XmTransferValue(scs->transfer_id, _XMOVER_REFRESH, elem_transfer, + elem, time); + pending++; + } + XFree(scs->value); + if (1 == pending) + XmTransferDone(scs->transfer_id, status); +} + +static void +elem_destination(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + XmDestinationCallbackStruct *dcs = call_data; + struct changer_elem *elem = clientdata; + Time time = XtLastTimestampProcessed(XtDisplay(widget)); + + if (args.debug) + fprintf(stderr,"elem_destination\n"); + XmTransferValue(dcs->transfer_id, XA_TARGETS, elem_transfer, elem, time); +} + +/* --------------------------------------------------------------------- */ +/* gui + basic callbacks */ + +void +destroy_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + XtDestroyWidget(clientdata); +} + +static void +about_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + Widget msgbox; + + msgbox = XmCreateInformationDialog(widget,"aboutbox",NULL,0); + XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_HELP_BUTTON)); + XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_CANCEL_BUTTON)); + XtAddCallback(msgbox,XmNokCallback,destroy_cb,msgbox); + XtManageChild(msgbox); +} + +static void +close_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + Widget shell = clientdata; + XtDestroyWidget(shell); + exit(0); +} + +static void +refresh_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + struct list_head *item; + struct changer_elem *elem; + + ptr_set(ptr_busy); + list_for_each(item,&elems) { + elem = list_entry(item, struct changer_elem, list); + update_elem(elem); + } + ptr_set(ptr_idle); +} + +static void create_widgets(Widget shell) +{ + Widget form,menubar,menu,push,frame,scroll,rowcol; + int i; + + /* shell stuff */ + XmdRegisterEditres(shell); + XmAddWMProtocolCallback(shell,XA_WM_DELETE_WINDOW,close_cb,shell); + + /* form container */ + form = XtVaCreateManagedWidget("form", xmFormWidgetClass, shell, + NULL); + + /* menu- & toolbar */ + menubar = XmCreateMenuBar(form,"bar",NULL,0); + XtManageChild(menubar); + + /* menu -- file */ + menu = XmCreatePulldownMenu(menubar,"fileM",NULL,0); + XtVaCreateManagedWidget("file",xmCascadeButtonWidgetClass,menubar, + XmNsubMenuId,menu,NULL); + push = XtVaCreateManagedWidget("quit",xmPushButtonWidgetClass,menu,NULL); + XtAddCallback(push,XmNactivateCallback,close_cb,shell); + + /* menu -- changer */ + menu = XmCreatePulldownMenu(menubar,"changerM",NULL,0); + XtVaCreateManagedWidget("changer",xmCascadeButtonWidgetClass,menubar, + XmNsubMenuId,menu,NULL); + push = XtVaCreateManagedWidget("refresh",xmPushButtonWidgetClass,menu,NULL); + XtAddCallback(push,XmNactivateCallback,refresh_cb,NULL); + + /* menu - help */ + menu = XmCreatePulldownMenu(menubar,"helpM",NULL,0); + push = XtVaCreateManagedWidget("help",xmCascadeButtonWidgetClass,menubar, + XmNsubMenuId,menu,NULL); + XtVaSetValues(menubar,XmNmenuHelpWidget,push,NULL); + push = XtVaCreateManagedWidget("man",xmPushButtonWidgetClass,menu,NULL); + XtAddCallback(push,XmNactivateCallback,man_cb,"xmover"); + XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL); + push = XtVaCreateManagedWidget("about",xmPushButtonWidgetClass,menu,NULL); + XtAddCallback(push,XmNactivateCallback,about_cb,NULL); + + /* ie elems */ + frame = XtVaCreateManagedWidget("ie", xmFrameWidgetClass, form, NULL); + XtVaCreateManagedWidget("label",xmLabelWidgetClass,frame,NULL); + rowcol = XtVaCreateManagedWidget("rc",xmRowColumnWidgetClass, + frame,NULL); + for (i = 0; i < params.cp_nportals; i++) + add_elem(rowcol, CHET_IE, i); + + /* dt elems */ + frame = XtVaCreateManagedWidget("dt", xmFrameWidgetClass, form, NULL); + XtVaCreateManagedWidget("label",xmLabelWidgetClass,frame,NULL); + rowcol = XtVaCreateManagedWidget("rc",xmRowColumnWidgetClass, + frame,NULL); + for (i = 0; i < params.cp_ndrives; i++) + add_elem(rowcol, CHET_DT, i); + + /* st elems */ + frame = XtVaCreateManagedWidget("st", xmFrameWidgetClass, form, NULL); + XtVaCreateManagedWidget("label",xmLabelWidgetClass,frame,NULL); + scroll = XmCreateScrolledWindow(frame,"scroll",NULL,0); + XtManageChild(scroll); + rowcol = XtVaCreateManagedWidget("rc",xmRowColumnWidgetClass, + scroll,NULL); + for (i = 0; i < params.cp_nslots; i++) + add_elem(rowcol, CHET_ST, i); +} + +/* --------------------------------------------------------------------- */ +/* main */ + +static void usage(void) +{ + fprintf(stderr, + "xmover -- X11 frontend for scsi media changers\n" + "options:\n" + " -debug enable debug messages\n" + " -device <dev> use <dev> instead of /dev/sch0\n"); +} + +int +main(int argc, char *argv[]) +{ + Widget app_shell; + int err; + + /* init X11 */ + XtSetLanguageProc(NULL,NULL,NULL); + app_shell = XtVaAppInitialize(&app_context, "xmover", + opt_desc, opt_count, + &argc, argv, + fallback_ressources, + NULL); + XtGetApplicationResources(app_shell,&args, + args_desc,args_count, + NULL,0); + if (args.help) { + usage(); + exit(1); + } + + /* init changer */ + fd = open(args.device,O_RDONLY); + if (-1 == fd) { + fprintf(stderr,"open %s: %s\n",args.device,strerror(errno)); + exit(1); + } + err = ioctl(fd,CHIOGPARAMS,¶ms); + if (-1 == err) { + perror("CHIOGPARAMS"); + exit(1); + } + INIT_LIST_HEAD(&elems); + + init_atoms(XtDisplay(app_shell)); + if (!args.debug) + stderr_init(app_shell); + ptr_init(app_shell); + ptr_register(app_shell); + create_widgets(app_shell); + + XtRealizeWidget(app_shell); + XtAppMainLoop(app_context); + return 0; +} diff --git a/xmover.h b/xmover.h new file mode 100644 index 0000000..7e4f2b2 --- /dev/null +++ b/xmover.h @@ -0,0 +1,125 @@ +"xmover.geometry: 400x500", +"xmover.deleteResponse: DO_NOTHING", +"xmover*XmDialogShell.deleteResponse: DESTROY", +"", +"*renderTable: error,full,voltag", +"*renderTable.fontType: FONT_IS_FONTSET", +"*renderTable.fontName: " +" -*-helvetica-medium-r-normal--*-120-*-*-*-*, " +" -*-*-*-r-normal--*-120-*-*-*-*", +"*renderTable.error.renditionForeground: darkred", +"*renderTable.error.fontType: FONT_IS_FONTSET", +"*renderTable.error.fontName: " +" -*-helvetica-bold-r-normal--*-120-*-*-*-*, " +" -*-*-*-r-normal--*-120-*-*-*-*", +"*renderTable.full.renditionForeground: darkgreen", +"*renderTable.full.fontType: FONT_IS_FONTSET", +"*renderTable.full.fontName: " +" -*-helvetica-bold-r-normal--*-120-*-*-*-*, " +" -*-*-*-r-normal--*-120-*-*-*-*", +"*renderTable.voltag.fontType: FONT_IS_FONTSET", +"*renderTable.voltag.fontName: " +" -*-fixed-medium-r-normal--*-120-*-*-*-*, " +" -*-*-*-r-normal--*-120-*-*-*-*", +"", +"xmover.form.?.leftAttachment: ATTACH_FORM", +"xmover.form.?.rightAttachment: ATTACH_FORM", +"xmover.form.ie.topAttachment: ATTACH_WIDGET", +"xmover.form.ie.topWidget: bar", +"xmover.form.dt.topAttachment: ATTACH_WIDGET", +"xmover.form.dt.topWidget: ie", +"xmover.form.st.topAttachment: ATTACH_WIDGET", +"xmover.form.st.topWidget: dt", +"xmover.form.st.bottomAttachment: ATTACH_FORM", +"xmover.form.tool.leftOffset: 5", +"xmover.form.tool.rightOffset: 5", +"xmover.form.XmFrame.leftOffset: 5", +"xmover.form.XmFrame.rightOffset: 5", +"xmover.form.XmFrame.topOffset: 5", +"xmover.form.XmFrame.bottomOffset: 5", +"", +"xmover.form.XmFrame.marginWidth: 5", +"xmover.form.XmFrame.marginHeight: 5", +"xmover.form.XmFrame.label.frameChildType: FRAME_TITLE_CHILD", +"", +"xmover.form.*.rc.packing: PACK_COLUMN", +"xmover.form.*.rc.orientation: VERTICAL", +"xmover.form.*.rc.resizeHeight: True", +"xmover.form.*.rc.numColumns: 1", +"xmover.form.*.scroll.visualPolicy: CONSTANT", +"xmover.form.*.scroll.scrollingPolicy: AUTOMATIC", +"xmover.form.*.scroll.scrollBarDisplayPolicy: AS_NEEDED", +"xmover.form.*.scroll.scrollBarPlacement: BOTTOM_RIGHT", +"", +"xmover.form.*.rc.d.background: gray95", +"xmover.form.*.rc.d.marginWidth: 0", +"xmover.form.*.rc.d.marginHeight: 0", +"xmover.form.*.rc.d.XmLabel.background: gray95", +"xmover.form.*.rc.d.XmLabel.borderWidth: 0", +"xmover.form.*.rc.d.XmLabel.marginLeft: 5", +"xmover.form.*.rc.d.XmLabel.marginRight: 5", +"", +"xmover.title: xmover", +"xmover*ie.label.labelString: Import/export elements (mailslot)", +"xmover*dt.label.labelString: Data transfer elements (drives)", +"xmover*st.label.labelString: Storage elements (slots)", +"xmover*menu.ref.labelString: Reload status", +"xmover*menu.src.labelString: Move back volume", +"xmover*menu.tag.labelString: Edit volume tag", +"", +"xmover*bar.file.labelString: File", +"xmover*bar.file.mnemonic: F", +"xmover*bar.changer.labelString: Media changer", +"xmover*bar.changer.mnemonic: M", +"xmover*bar.help.labelString: Help", +"xmover*bar.help.mnemonic: H", +"", +"xmover*bar*quit.labelString: Quit", +"xmover*bar*quit.mnemonic: Q", +"xmover*bar*quit.acceleratorText: Q", +"xmover*bar*quit.accelerator: <Key>Q", +"", +"xmover*bar*refresh.labelString: Reload status for all elements", +"xmover*bar*refresh.mnemonic: R", +"xmover*bar*refresh.acceleratorText: Ctrl+L", +"xmover*bar*refresh.accelerator: Ctrl<Key>L", +"", +"xmover*bar*man.labelString: Manual page ...", +"xmover*bar*man.mnemonic: M", +"xmover*bar*man.acceleratorText: F1", +"xmover*bar*man.accelerator: <Key>F1", +"xmover*bar*about.labelString: About ...", +"", +"xmover.errbox_popup.title: Errors", +"xmover*tag_popup.title: Volume tag", +"xmover*tag_popup*selectionLabelString: New volume tag?", +"xmover*aboutbox_popup.title: About xmover", +"xmover*aboutbox_popup*messageString: " +" xmover - X11 frontend for scsi media changers\\n" +" \\n" +" (c) 2002 Gerd Knorr <kraxel@bytesex.org>", +"", +"xmover*man_popup.title: Manual page", +"xmover*man_popup*okLabelString: close window", +"xmover*man_popup*label.labelString: please wait ...", +"", +"xmover*man_popup.deleteResponse: DESTROY", +"xmover*man_popup*view.width: 500", +"xmover*man_popup*view.height: 600", +"xmover*man_popup*view.scrollingPolicy: AUTOMATIC", +"xmover*man_popup*view.scrollBarPlacement: BOTTOM_RIGHT", +"", +"xmover*man_popup*label.alignment: ALIGNMENT_BEGINNING", +"xmover*man_popup*label.marginWidth: 5", +"xmover*man_popup*label.marginHeight: 5", +"xmover*man_popup*label.renderTable: bold,underline", +"xmover*man_popup*label.renderTable.fontType: FONT_IS_FONTSET", +"xmover*man_popup*label.renderTable.fontName: " +" -*-fixed-medium-r-normal--13-*-*-*-*-*,", +" -*-*-*-r-normal--*-120-*-*-*-*", +"", +"xmover*man_popup*label.renderTable.bold.fontType: FONT_IS_FONTSET", +"xmover*man_popup*label.renderTable.bold.fontName: " +" -*-fixed-bold-r-normal--13-*-*-*-*-*,", +" -*-*-*-r-normal--*-120-*-*-*-*", +"xmover*man_popup*label.renderTable.underline.underlineType: SINGLE_LINE", diff --git a/xmover.man b/xmover.man new file mode 100644 index 0000000..5d42490 --- /dev/null +++ b/xmover.man @@ -0,0 +1,53 @@ +.TH xmover 1 "(c) 2002 Gerd Knorr" +.SH NAME +xmover - X11 frontend for scsi media changers +.SH SYNOPSIS +.B xmover [ options ] +.SH DESCRIPTION +.B xmover +is a X11 utility which can control media changers using the scsi media +changer device driver. +.SH OPTIONS +.TP +.B -debug +enable debug output. +.TP +.B -device <dev> +Use <dev> instead of /dev/sch0. +.P +The usual toolkit options (-geometry and friends) are supported too. +.SH USING THE TOOL +Just start it. +.B xmover +will read the changer configuration at startup and present a window +with all changer elements when done. It displays the volume tags (if +present) and the element status (full/empty, ...). Every changer +element has a context menu with some functions, you can edit the +volume labels for example. +.P +Moving media from one element to another simply works with drag and +drop. +.B xmover +uses Motif, thus the middle mouse button will start drags. +.SH BUGS +There is no support for double-sided media. +.SH SEE ALSO +mover(1) +.SH AUTHOR +Gerd Knorr <kraxel@bytesex.org> +.SH COPYRIGHT +Copyright (C) 2002 Gerd Knorr <kraxel@bytesex.org> +.P +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. +.P +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. +.P +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |