Coding additional window manager support for wmtheme
----------------------------------------------------

OVERVIEW
========

  Wmtheme consists of a main program that loads another perl file
  depending on the window manager it's supporting. This additional
  file has two main responsibilities:

   - Initialization.  It must set up some variables that are used
     by the main program.  It may also do other initialization if
     necessary.

   - Specific functions.  There are several (7 as of this writing)
     WM-specific functions that must be defined, if only as a stub.
     Additional supporting functions are possible, but to avoid
     name collisions they should be postfixed by the name of the
     window manager being supported.

     (example: Blackbox code wants a "purge_duplicates" sub, it
     should be named "purge_duplicates_blackbox")

  The main file supplies several functions that should be used
  whenever possible. Most importantly, DIE and EXIT should not be
  used, instead use choke() and finish().


INITIALIZATION
==============

  These variables should be defined:

    $wm_executable - name of the window manager program (for
    determining whether it's running)

    %unsupported - set to TRUE any of the following that are not
    supported for this window manager:

      'activation' (why: so that the message "theme activation is
                    not supported" will be printed even if the WM
                    isn't running - without this, main prog would
                    check for the WM and print "xxxWM not running"
                    before calling wm_activatetheme)
      'renaming'


    $dont_extract - set to TRUE if archives shouldn't be
    extracted during get or install. This will make wm_installtheme
    get the name of an archive in a temp directory.

    $tosite - "themes.org site" - site at themes.org from which
    themes may be downloaded or updated.

    $badnamechars - _append_ characters which may not be in theme names
                    to this variable.

    $preinst_callback - reference to a sub which will be called after
       theme scanning, but before an installation.  This is useful
       when wmtheme cannot install a theme until the window manager
       has done some setup, but wmtheme _can_ scan for themes.
       
       It is not used with IceWM (for example), because itheme can create
       an entire IceWM user setup on its own.

       When -i/-I/-g are used, the local dir is required and this sub
       will be called.


USEFUL GLOBAL VARIABLES
=======================

%themes
---------

  This hash is keyed by theme name.  The theme name must
  be unique, and should match the name displayed by the window
  manager's gui theme selection tool if possible.

  The associated value is a reference to another hash with the
  following keys:

    updatable           'yes' or 'no', user's desire to include in --update
    path                full filesystem name, to directory or single file
    owner               numeric uid of theme's owner
    mtime               modified time of the 'path'
    validated           used temporarily during cache resolution
    local_updated       time, taken from the themes.org theme that was installed
    id                  themes.org database key
    origname            themes.org's name for this theme
    link                URL that should result in downloading the theme
    updated             themes.org's time the theme was last updated
    author              themes.org nugget o' info
    category            themes.org nugget o' info
    size                number of bytes in download from themes.org
    version             window manager version for which theme was designed

  The path is somewhat flexible.  It should be an actual file or a
  directory. This depends on the way the window manager handles themes.

  For example: Window Maker has three (count 'em) ways of installing
  themes, and when wmaker is being supported, <path> will represent
  both directories and plain files.  A theme named "Baywater" could
  exist like any of the following:

    ~/GNUstep/Library/WindowMaker/Themes/Baywater          (a file)
    ~/GNUstep/Library/WindowMaker/Themes/Baywater.style    (a file)
    ~/GNUstep/Library/WindowMaker/Themes/Baywater.themed   (a directory)

  What you store as <path> is up to you, as long as it is an existing
  file or directory. Bear in mind that this is what you will have to
  work with when activating or uninstallling a theme.


$localthemedir
---------------

  The place to install a theme, if $> != 0.

$globalthemedir
---------------

  The place to install a theme if $> == 0.

%cleanups
---------

  The values are fully qualified directory or file names that
  will be removed on program termination.  This hash is used to
  ensure that temporary files are removed.  For example, it may be
  used in wm_installtheme to ensure that an incompletely installed
  theme won't hang around if something unexpected occurs.

  To use it, choose a unique key beginning with the name of the
  window manager.  The following code can keep things from becoming
  messy if the program gets interrupted while installing a SuperWM
  theme:

    $cleanups{'superwm_install'} = $current_themedir;

    mkdir $current_themedir, 755 or choke("can't mkdir $current_themedir: $!");
    stowfile($themeconfiginfo, "$current_themedir/theme.cfg");
    forktick("mv \"$tempdir/*\" \"$current_themedir\"");
    superwm_addtomenu($themename, $current_themedir);

    delete $cleanups{'superwm_install'};

  Several potentially dangerous operations take place during
  the course of this hypothetical installation.  In the event
  of an error, the main program's signal catcher will remove
  $current_themedir.


SPECIFIC FUNCTIONS
==================

  The names of these functions are literal (do not replace 'wm' with
  the window manager's name).

wm_getdefaultdirs
-----------------

  INPUTS

    none

  OUTPUTS

    list of base directories which will be scanned (later) for themes

  RESPONSIBILITIES

    Scan all possible theme locations.  Determine which directories
    actually exist, and are likely to be in use in the system.

  NOTES

    It may be a good idea to try reading the current user's
    config file so that the apparently available themes are the
    same as the user sees, in the place that they normally look
    for themes to activate.

    The global variables $localthemedir and $globalthemedir should
    be set, but use sub verify_themedirs to do this.

wm_scandir
----------

  INPUTS

    a theme directory

  OUTPUTS

    a hash of dir->mtime, with all directories that were scanned

  RESPONSIBILITIES

    Identify all themes in the directory, and determine the proper
    name, the path, and the owner's uid.  Call savetheme() with
    each theme found.  Determine the directory's modification time
    and store it in the hash which will be returned.

    If the window manager recurses into subdirectories, so must
    this function (this is how the hash can get more than one key).


wm_installtheme
---------------

  INPUTS

    a temporary location - either of:
      - a directory into which an archive has been extracted
      - a file which was not recognized as an archive, or not
        extracted because $dont_extract was set to 1.

    the theme archive's intended filename

    the theme's definitive name (may be missing)

  OUTPUTS

    the theme name

  RESPONSIBILITIES

    Analyze the file(s) in the temporary location, fixing the theme
    if it was poorly packaged. Determine the proper theme name.
    Use the third parameter (definitive name) if present.

    Avoid name collisions with currently installed themes, by
    using checkname():

      $themename = checkname($possiblename);

    checkname also removes characters that were set in $badnamechars
    during initialization.

    If the third parameter (the definitive name) is supplied, use
    it as the name (but pass it through the collision checker too).

    If the theme is viable, install it to $globalthemedir or
    $localthemedir depending on $>.

    Call savetheme() when installation is complete.

  NOTES

    The filename (second parameter) is only passed because it might
    be of assistance in determining the theme's name.


wm_activatetheme
----------------

  INPUTS

    a theme name

    theme activation explicitly indicated (boolean)

  OUTPUTS

    none

  RESPONSIBILITIES

    Make the theme visible, current, active...

    If theme activation is not supported, just return,
    unless it was explicitly indicated, in which case print
    a warning message.

  NOTES

    Some window managers make this difficult or impossible to do
    from an external program, so it's ok to just print a message
    about "operation not supported".  But print this only if
    the second parameter is true.


wm_uninstalltheme
-----------------

  INPUTS

    a theme name

  OUTPUTS

    TRUE on success, FALSE on failure

  RESPONSIBILITIES

    Delete the theme.  You can use $themes{$name}{'path'} to get
    its path.

  NOTES

    The theme's ownership has already been checked. The user has
    already been asked to confirm the action. Just kill the theme.


wm_rename
---------

  INPUTS

    a theme name

    a new name

  OUTPUTS

    the theme's new path

    the theme's new name (optional)

  RESPONSIBILITIES

    Determine the modified path component of the theme's
    %themes value.  Take such steps are necessary to rename the
    theme in the eyes of the window manager, such as renaming
    the path component, changing strings in text files, etc.

  NOTES

    The theme's ownership has already been checked, and the
    input theme name is exact.  Don't record changes in %themes,
    that will be done in the main program.

    Sometimes a theme's new name cannot match the request exactly,
    so return the actual new name in this case.  (Currently
    affects IceWM only.)


wm_versionok
------------

  INPUTS

    version for which the theme is intended (string)

  OUTPUTS

    Whether it's ok to install the theme (boolean).

  RESPONSIBILITIES

    Return true if the theme seems ok to install, otherwise
    return false.

 NOTES

    Don't attempt to cache version <-> suitability matches;
    the main program does this. DO cache the version returned by
    running "wmname -version" (or whatever), however.

    If no theme vs. WM version issues are known, just return true.
    The default stub code also returns true.

    When supporting a window manager with theme version matching
    issues, the WM's version should be determined and compared
    with the supplied strings.  Sawfish, for example, and to a
    lesser extent Enlightenment, may have problems with a theme
    designed for a different version of the WM.

    If it seems that there will be a problem, the user should
    be asked whether to proceed (return false).

    Future versions of a WM may break compatibility with older
    themes. However, people will still want/try to install older
    themes. This code should have particular attention paid to
    its maintainability for this reason.

    When deciding what to do with WM versions that will be
    released in the future, use good judgement.  For Blackbox
    and Window Maker, it's probably ok just to return true, UNLESS
    the version is 1.0 or greater.  For Enlightenment and Sawfish,
    it would probably be best to ask the user's permission for any
    minor version upgrade. For example, if the code was written
    during Sawmill 0.24, that version of the WM is running,  and
    the theme is written for Sawfish 0.28, ask.


TYING IT IN
===========

  Or, how to integrate this into the main program in a development
  environment

  I will be using Afterstep support as an example.


  1) Make sure you have copied all of the wmtheme distribution files
     to the place you want to work on them, such as ~/projects/wmtheme/

  2) Decide on the invocation name and the code filename. For AfterStep,
     astheme is a good invocation name, and afterstep.pl is a good code
     filename.

  3) Copy stubwm.pl to the code filename (afterstep.pl)

  4) Change all occurrances of STUB to the WM name ('AfterStep')
     or the invocation name ('astheme') as appropriate.

  4) Edit the makefile:

     add the invocation name to the ALLINVOCATIONS definition:
       ALLINVOCATIONS=bbtheme xxtheme astheme

     add the code filename to the SUPPORTFILES definition:
       SUPPORTFILES=wmaker.pl blackbox.pl xxxxxxx.pl afterstep.pl

  5) Be root and run "make install-dev".
     The Makefile documents the meaning of install-dev.

  6) Edit wmtheme.

     Add an entry to $ids in get_wm_specific_names:

       astheme  => ['AfterStep',    'afterstep.pl']

  7) Run "astheme -l". Hopefully you will see this:

       astheme: Sorry, astheme does not support scanning for themes, yet.

  8) Begin developing afterstep.pl.

