aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkraxel <kraxel>2005-02-09 11:34:37 +0000
committerkraxel <kraxel>2005-02-09 11:34:37 +0000
commit73671a2f4f0e6e03a509ff5a97ab70d7b0db12b1 (patch)
treed0099f50705e45392d9666c0aaa64bd69fc18b32
parent92a99fec1e7c0c05568a3abd647c9ae781acbd2f (diff)
downloadscsi-changer-73671a2f4f0e6e03a509ff5a97ab70d7b0db12b1.tar.gz
- add files to repository.
-rw-r--r--INSTALL34
-rw-r--r--Makefile62
-rw-r--r--README184
-rw-r--r--RegEdit.c1795
-rw-r--r--RegEdit.h65
-rw-r--r--RegEditI.h368
-rw-r--r--autojuke.c344
-rw-r--r--autojuke.conf8
-rw-r--r--autojuke.man60
-rw-r--r--chio.h160
-rw-r--r--list.h166
-rw-r--r--man.c116
-rw-r--r--man.h4
-rw-r--r--mover.c367
-rw-r--r--mover.man105
-rw-r--r--unload.c65
-rw-r--r--unload.man27
-rw-r--r--xmover.c777
-rw-r--r--xmover.h125
-rw-r--r--xmover.man53
20 files changed, 4885 insertions, 0 deletions
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..a7ee64b
--- /dev/null
+++ b/INSTALL
@@ -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>
diff --git a/Makefile b/Makefile
index e69de29..707a0c8 100644
--- a/Makefile
+++ b/Makefile
@@ -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
+
diff --git a/README b/README
new file mode 100644
index 0000000..04a4ea9
--- /dev/null
+++ b/README
@@ -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,&params)) {
+ 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.
diff --git a/chio.h b/chio.h
new file mode 100644
index 0000000..b08e8c2
--- /dev/null
+++ b/chio.h
@@ -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)
diff --git a/list.h b/list.h
new file mode 100644
index 0000000..10a0518
--- /dev/null
+++ b/list.h
@@ -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))
+
diff --git a/man.c b/man.c
new file mode 100644
index 0000000..4862b7f
--- /dev/null
+++ b/man.c
@@ -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]);
+}
diff --git a/man.h b/man.h
new file mode 100644
index 0000000..3a04d93
--- /dev/null
+++ b/man.h
@@ -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);
diff --git a/mover.c b/mover.c
new file mode 100644
index 0000000..8866abe
--- /dev/null
+++ b/mover.c
@@ -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,&params);
+ 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,&params);
+ 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.