Please consider a donation to the Higher Intellect project. See https://preterhuman.net/donate.php or the Donate to Higher Intellect page for more info.

Ij.h

From Higher Intellect Vintage Wiki
Jump to navigation Jump to search
/*
 * ij.h - device-independent input jacks for VL
 *
 * this is a simple hack API.  you pick a VL device.  you get a list of
 * available input jacks as text strings.  you pick one.  the hack will
 * create a path from a properly configured video source node to the
 * drain node you give.  hopefully someday the VL will offer this
 * functionality built-in so that developers do not have to deal with
 * this level of device-dependence.
 *
 * this hack also lets you pick the default jack and see what that was.
 *
 * WARNING this version has not been tested for all jacks 
 *         for all devices.
 */

#include <dmedia/vl.h>

typedef struct _IJhandle *IJhandle;

/*
 * open a handle to use to pick input jacks.  at this point you
 * specify a VLServer, and you specify what device you'll be using 
 * (VL_ANY for the default device).
 *
 * if you'd just like to present a list of device names, you can use
 * something like this snippet:
 *
  {
    VLDevList devlist;
    
    vlGetDeviceList(vlServer, &devlist);
    
    for (dv=0; dv < (int)devlist.numDevices; dv++)
      printf("the device with VLDev %d is called [%s]\n",  
             devlist.devices[dv].dev,
             devlist.devices[dv].name);
  }
 * 
 * the VLDev (not the index dv) is what you pass to ijOpenHandle.
 *
 * during this call, ij may scan the hardware to see which flavor of a
 * board you have, and which options are really present.  instead of
 * exposing the (often misleading) VL device name to the user, you can
 * get ij to give you a much more meaningful name (the marketing name
 * for the flavor of board the user has installed) with this snippet:
 *
  {
    VLDevList devlist;
    
    vlGetDeviceList(vlServer, &devlist);
    
    for (dv=0; dv < (int)devlist.numDevices; dv++)
      {
        VLDev dev = devlist.devices[dv].dev;
        
        VLNode drain_node = vlGetNode(vlServer, VL_DRN, VL_MEM, VL_ANY);
        IJhandle h = ijOpenHandle(vlServer, dev, drain_node);
        
        printf("device with VLDev %d: VL calls it [%s]"
               " but it's really [%s]\n",  
               devlist.devices[dv].dev,
               devlist.devices[dv].name
               ijGetDeviceName(h));
        
        ijCloseHandle(h);
      }
  }
 *
 * ij will use the VLServer and VLDev passed in for other functions
 * you call in the lifetime of this IJhandle.
 *
 * NOTE: you must also specify the handle to a drain node of the 
 * kind you will be using (screen, memory, etc.). this VLNode is 
 * not saved in the IJhandle and thus it will not be used by future 
 * ij calls.  ij may need to use this drain_node in order to probe 
 * the device to find out its capabilities, and in order to translate 
 * VL_ANY to an actual default device.  In these cases, ijOpenHandle()
 * will actually create, set up, and destroy a path using your 
 * drain_node.  The VL leaves us no alternative to doing this.
 * ij uses VL_READ_ONLY, VL_READ_ONLY when setting up the path, 
 * so in theory this should not interfere with the VL preemption 
 * mechanism.  There is one more caveat that results from this: 
 * VL has a limitation where it cannot deal with the same VLNode 
 * handle being used on a path on different devices.  Therefore, 
 * once you pass drain_node into ijOpenHandle(), you should never 
 * use that node handle again.  You can call vlGetNode() with the 
 * same arguments to get a new handle that refers to the same
 * resources.  This is shown in the code snippet above.  
 *
 * NOTE: in most cases, the hardware inventory information available
 * to ij is derived from a scan done at BOOT TIME.  Therefore, if
 * the user plugs in some dongle, break-out-box, or digital camera 
 * after the machine has booted, it may not be included in ij's list 
 * of jacks (the user must reboot to get the jacks listed).
 */
IJhandle ijOpenHandle(VLServer svr,
                      VLDev device, /* VL_ANY for default device */
                      VLNode drain_node);  /* see above. */

/*
 * ijCloseHandle() deallocates ij's resources for this handle:
 * it does not destroy any VL nodes or paths.
 */
void ijCloseHandle(IJhandle h);

/*
 * returns the VLDev chosen.  
 * useful if you specified VL_ANY to ijOpenHandle().
 */
int ijGetVLDev(IJhandle h);

/*
 * get a name suitable to describe THIS flavor of the board you have
 * selected.  This will return the appropriate string in cases like
 * "Indy Video" vs. "Galileo Video".
 *
 * string belongs to ij and is valid as long as this IJhandle is alive.
 */
char *ijGetDeviceName(IJhandle h);

/*
 * get number of jacks for this handle
 */
int ijGetNumJacks(IJhandle h);

/*
 * get a textual description of each input jack.
 *
 * string belongs to ij and is valid as long as this IJhandle is alive.
 *
 * idx should range from 0 to ijGetNumJacks() - 1
 */
char *ijGetJackName(IJhandle h, int idx); /* from 0 to NumJacks-1 */

/*
 * how to choose a jack:
 *
 * you pick a jack by specifying an index idx from 0 to 
 * ijGetNumJacks()-1,  or you can pick the jack currently
 * selected in vcp by passing VL_ANY for idx.
 *
 * use the following functions in this way to create a properly 
 * configured path from that jack to your specified drain_node:
 *
  { 
    ... create IJhandle h and drain node drain_node, pick idx ...
    int ret_idx;
    int node_number = ijGetNodeNumber(h, idx);
    VLDev device = ijGetVLDev(h);
    VLNode source_node = vlGetNode(svr, VL_SRC, VL_VIDEO, node_number);
    VLPath path = vlCreatePath(svr, device, source_node, drain_node);
    
    if (node_number < 0 || source_node < 0 || path < 0) 
      return FALSE;
    
    if (vlSetupPaths(svr, (VLPathList)&path, 1, VL_SHARE, VL_SHARE) < 0)
      { vlDestroyPath(svr, path); return FALSE; }
    
    if (ijConfigurePath(h, idx, 
                        source_node, drain_node, path, 
                        node_number, &ret_idx))
      { vlDestroyPath(svr, path); return FALSE; }
  }
 * 
 * as you can see here, jack selection in the VL consists of two parts:
 * first you must choose the right VLNode and set up a path containing
 * that node, and then you must properly configure the node.  in the VL,
 * each VL_VIDEO, VL_SRC node can serve one or more input jacks, and
 * the choice of video node and its required settings are totally 
 * device-dependent. ij handles the device-dependent aspects for you.
 *
 * ijGetNodeNumber() returns a video source node number which lets
 * you create the video source node. 
 *
 * once you have created the node and path and set up the path,
 * you now pass control back to ijConfigurePath() which does the
 * necessary vlSetControl()s to configure your path to input
 * data from the requested jack.
 *
 * the index of the actual jack chosen is returned in *ret_idx if
 * ret_idx is non-NULL.  this is how you tell which is the default jack.
 *
 * ijGetNodeNumber() and ijConfigurePath() return <0 on failure.
 * no error return values are provided now, except for VLErrno.
 *
 * you can call this routine as many times as you like with different
 * arguments.  calling this function does not change the state of the 
 * IJhandle in any way.  ij does not store the VLPath, VLNodes, index,
 * or node number it returns in the handle at all.
 *
 * VL CRAP:
 *
 * Q: why doesn't ij provide a quicky routine that just returns you an
 * already-configured node and path?
 *
 * A: to do this, IJ would need to create the source node, because 
 * the video source node number varies by jack in the VL.
 *
 * then, ij would need to create the path, because we need to set
 * controls on the nodes we create, and VL only lets you set controls
 * on a node if you also specify the path the node is on!  argh!  
 *
 * then, ij would need to set up the paths, since the VL only lets
 * you set controls on a path which is set up!  argh!!  vlSetupPaths()
 * is a critical operation whose arguments and return value are of great 
 * importance to an app;  it would be difficult to do this inside ij
 * without removing critical control from the app.  what's worse, 
 * some apps want to pass more than one path at a time to 
 * vlSetupPaths() so that the resource allocation is atomic.  this
 * would not be possible if ij did the path setup.
 *
 * Q: why isn't there a function you can call to simply return 
 * the current jack rather than creating a path?  
 *
 * A: because in VL, you CAN'T get the current jack without 
 * creating a path. argh!
 * 
 * the notion of "current jack" in VL is complicated.  the current jack
 * selected on vcp is the current jack on the current VL default source 
 * node.  ij determines the current default souce node by creating a 
 * device path, setting it up VL_READ_ONLY, VL_READ_ONLY, reading 
 * its node number from the control VL_DEFAULT_SOURCE, and destroying
 * the path.  Then it gets a handle to that node, opens up a path on 
 * that node, and determines which jack is the current jack on that node 
 * by getting various device-dependent VL controls on that node.  
 * it is truly unfortunate that VL users must deal with this.
 *
 */
int ijGetNodeNumber(IJhandle h, int idx);

int ijConfigurePath(IJhandle h, int idx,
                    VLNode source_node, VLNode drain_node, VLPath path,
                    int node_number, int *ret_idx);


/*
 * when you get a VLControlChanged event, pass in the type of the
 * VL control which changed, and this function will return 1
 * if that control could affect the choice of input jack, or 0
 * if not.  see the Q and A below to see why you would want to
 * do this.
 */
int ijDoesControlAffectInputJack(IJhandle h, VLControlType type) ;


/*
 * Some questions and answers about input jack selection:
 *
 * Q: what to do when the user chooses a different jack in vcp?
 *
 * A: when this happens, one of two things occurs:
 *
 * - if the user changes the default source node in vcp (usually 
 *   this looks like a "default in" menu), your app will receive a 
 *   VLDefaultSource event.
 * - if the user changes the current jack on the default source node, 
 *   your app will receive a VLControlChanged event for the relevant
 *   controls.  many menus on vcp could change the current jack on the
 *   default source node.
 *
 * if the default source node changes, and you want to track vcp, 
 * you MUST destroy and recreate the node and path, since the current
 * jack now resides on a new VLNode.
 *
 * if the jack on the default source node changes, then technically you
 * don't have to destroy and recreate the node and path, but you might 
 * as well do it anyway.
 *
 * so, to track changes in vcp, 
 *
 * - add VLDefaultSourceMask|VLControlChangedMask to your event mask.  
 * - when you receive a VLDefaultSource event, destroy your 
 *   node and path, and call ijGetNodeNumber()/ijConfigurePath() 
 *   again with an argument of VL_ANY for idx.  ijConfigurePath()
 *   will return the index of the newly chosen jack.
 * - when you receive a VLControlChanged event, check to see whether
 *   the control that changed could affect the current input jack, 
 *   and if so, do the same as if you had received a VLDefaultSource
 *   event.  Determining whether the VLControlChanged event could cause
 *   an input jack change is---you guessed it---device-dependent.  Use 
 *   ijDoesControlAffectInputJack() to determine this (see above).
 *
 *
 * Q: what if I want my app to be totally independent of vcp?
 *
 * A: sorry, bud.  the system was just not designed that way.
 * on most SGI devices, the available input jacks are separated 
 * into groups, and the hardware is only designed to take in data 
 * from ONE jack per group at a time.  for example, an ev1 board has 
 * up to 4 analog video inputs, but only one of those inputs can be 
 * used for video->memory or video->screen paths at a time.  This is  
 * true even if the paths in question are owned by totally separate 
 * processes--the choice of a jack from the group of analog input
 * jacks device-global.  So, when a user goes and changes the 
 * "analog input source" setting on vcp, or any other app running on 
 * the system changes the same control, your app WILL start getting 
 * data from a different jack, and you cannot control or lock out this
 * behavior in any way.
 *
 * the "groups" of jacks are represented in the VL as separate VLNodes.
 * the selection of a jack within a particular VLNode is done by
 * setting controls on that node, usually (but not always) VL_MUXSWITCH.
 * on all devices so far, the VL_MUXSWITCH (or comparable) setting on a 
 * VLNode is device-global, meaning that it affects all other VLNodes 
 * with the same node number.  Only when different jacks are present
 * on different VLNodes can you use them simultaneously.
 *
 * this lame behavior can be found in: vino, ev1, sirius, and impactvid.
 * future devices may or may not exhibit this behavior.
 *
 * given this, the best you can achieve is to be "sort of" independent 
 * of vcp.  sometimes, when people mess with vcp, it will affect your
 * app.  sometimes, it will not.
 *
 * our video hardware and VL software have left us with only one way
 * of avoiding this usability nightmare, one way to present consistent
 * behavior: make your app input from whatever jack is selected in vcp.
 * if the user selects a new jack in vcp, then your app changes
 * immediately.  this behavior would be consistently true regardless
 * of which jack selection control the user futzed with---your app
 * would not behave differently if the user changed "analog input 
 * source" versus "default in," as many do now.  The GUI user knows
 * nothing about VL nodes or VL_MUXSWITCH controls, and it is silly
 * expecting the user to understand such concepts when all they want 
 * to do is input video from ONE jack (which is what the vast majority 
 * want to do).  If the user is doing a task where they really need
 * two input jacks, then sure, go ahead and expose the subtleties.
 * but if your app just needs to pick one input jack, and it doesn't 
 * specifically need to pick a different jack than other apps which 
 * are running, it may not be worthwhile for you to try and make your 
 * app independent of vcp.  you will only present a user interface 
 * which is inconsistent with vcp and confusing.
 *
 *
 * Q: what to do when the user chooses a different jack in my app?
 *
 * A: destroy the node and the path yourself, and call 
 * ijGetNodeCreatePath again.
 *
 * on the same theory of keeping your app consistent with vcp,
 * you would presumably want to make sure that vcp reflected
 * the new default input jack.  as with the question above,
 * SOME of vcp's controls will automatically reflect your
 * new choice of jack, because they are device-global controls.
 * but not all of them.  In order to keep vcp consistent, you
 * would have to set the default source node to the node number
 * of the newly selected jack.  This would mean setting the
 * VL_DEFAULT_SOURCE parameter on the device node for the
 * device (XXX will VL let you do this? checking...)
 *
 */