
/*
 *@@sourcefile apm.c:
 *      this contains the XFolder APM interface for automatically
 *      turning the computer off after shutdown has completed.
 *      This is mostly used in the context of XShutdown (xshutdwn.c).
 *
 *@@include #include <os2.h>
 *@@include #include "apm.h"
 */

/*
 *      Massive thanks go out to ARAKAWA Atsushi (arakaw@ibm.net)
 *      for filling this in, and to Roman Stangl (rstangl@vnet.ibm.com)
 *      for finding out all the APM stuff in the first place.
 *
 *      This file Copyright (C) 1997-99 Ulrich M”ller, ARAKAWA Atsushi.
 *      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, in version 2 as it comes in the COPYING
 *      file of the XFolder main distribution.
 *      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.
 */

#define INCL_DOS
#define INCL_DOSERRORS
#define INCL_DOSDEVICES
#define INCL_DOSDEVIOCTL
#include <os2.h>

#include <stdio.h>
#include <string.h>
#include "apm.h"            // APM power-off for XShutdown

HFILE           hfAPMSys = NULLHANDLE;
ULONG           ulAPMStat = APM_UNKNOWN;
USHORT          usAPMVersion = 0;

CHAR szAPMVersion[10];

/*
 *@@  apmQueryVersion:
 *      This function returns a PSZ to a static APM
 *      version string, e.g. "1.2".
 *
 *      <B>Usage:</B> any time, esp. on the shutdown
 *      notebook page.
 *
 */

PSZ apmQueryVersion(VOID)
{
    if (ulAPMStat == APM_UNKNOWN)
        apmPowerOffSupported();
    sprintf(szAPMVersion, "%d.%d", usAPMVersion>>8, usAPMVersion & 0xff);
    return (szAPMVersion);
}

/*
 *@@  apmPowerOffSupported:
 *      This function returns TRUE if the computer
 *      supports the APM power-off function.
 *
 *      <B>Usage:</B> any time, especially
 *      by the "XDesktop" page in the Desktop's
 *      settings notebook to determine if the "APM power
 *      off" checkbox will be enabled or not (i.e. cannot
 *      be selected).
 *
 */

BOOL apmPowerOffSupported(VOID)
{
    USHORT          usBIOSVersion, usDriverVersion;
    APIRET          arc = NO_ERROR;
    ULONG           ulAction = 0;
    GETPOWERINFO    getpowerinfo;
    ULONG           ulAPMRc;
    ULONG           ulPacketSize;
    ULONG           ulDataSize;

    if (ulAPMStat == APM_UNKNOWN) {
        // open APM.SYS
        ulAction = 0;
        arc = DosOpen("\\DEV\\APM$", &hfAPMSys, &ulAction, 0, FILE_NORMAL,
                OPEN_ACTION_OPEN_IF_EXISTS,
                OPEN_FLAGS_FAIL_ON_ERROR | OPEN_SHARE_DENYNONE | OPEN_ACCESS_READWRITE,
                NULL);
        if (arc == NO_ERROR) {
            // query version of APM-BIOS and APM driver
            memset(&getpowerinfo, 0, sizeof(getpowerinfo));
            getpowerinfo.usParmLength = sizeof(getpowerinfo);
            ulPacketSize = sizeof(getpowerinfo);
            ulAPMRc = 0;
            ulDataSize = sizeof(ulAPMRc);
            arc = DosDevIOCtl(hfAPMSys, IOCTL_POWER,
                    POWER_GETPOWERINFO,
                    &getpowerinfo, ulPacketSize, &ulPacketSize,
                    &ulAPMRc, ulDataSize, &ulDataSize);
            if ((arc == NO_ERROR) && (ulAPMRc == NO_ERROR)) {
                // swap lower-byte(major vers.) to higher-byte(minor vers.)
                usBIOSVersion = (getpowerinfo.usBIOSVersion & 0xff) <<8 |
                    (getpowerinfo.usBIOSVersion >>8);
                usDriverVersion = (getpowerinfo.usDriverVersion & 0xff) <<8 |
                    (getpowerinfo.usDriverVersion >>8);
                // set general APM version to lower
                usAPMVersion = (usBIOSVersion < usDriverVersion)
                    ? usBIOSVersion : usDriverVersion;

                // check APM version whether power-off is supported
                if (usAPMVersion >= 0x102)  // version 1.2 or above
                    ulAPMStat = APM_OK;
                else
                    ulAPMStat = APM_IGNORE;

            } else {  // DosDevIOCtl failed
                ulAPMStat = APM_IGNORE;
                usAPMVersion = 0;
            }
            DosClose(hfAPMSys);
        } else {  // DosOpen failed; APM.SYS is not loaded
            ulAPMStat = APM_IGNORE;
            usAPMVersion = 0;
        }
    }
    return (ulAPMStat == APM_OK);
}

/*
 *@@ apmPreparePowerOff:
 *      This function is called _once_ by XShutdown while
 *      the system is being shut down, if apmPowerOffSupported
 *      above returned TRUE. This call happens
 *      after all windows have been closed, but
 *      before DosShutdown is called, so you may
 *      open the APM.SYS device driver here and
 *      do other preparations you might need.
 *
 *      After this, you must return _one_ of the
 *      following flags:
 *      --  APM_OK:  go ahead with XShutdown and call
 *                   apmDoPowerOff later.
 *      --  APM_IGNORE: go ahead with XShutdown and do
 *                   _not_ call apmDoPowerOff later;
 *                   this will lead to the normal
 *                   "Press Ctrl+Alt+Del" window.
 *      --  APM_CANCEL: cancel shutdown and present the
 *                   error message which you must
 *                   then copy to pszError, which
 *                   points to a buffer 500 bytes in
 *                   size.
 *
 *      ORed with _one_ or _none_ of the following:
 *      --  APM_DOSSHUTDOWN_0:
 *                   set this flag if XFolder should
 *                   call DosShutdown(0); this is _not_
 *                   recommended, because this would block
 *                   any subsequent DosDevIOCtl calls.
 *      --  APM_DOSSHUTDOWN_1:
 *                   the same for DosShutdown(1), which is
 *                   recommended.
 *
 *      If you return APM_OK only without either
 *      APM_DOSSHUTDOWN_0 or APM_DOSSHUTDOWN_1,
 *      XFolder will call apmDoPowerOff later,
 *      but without having called DosShutdown.
 *      You MUST do this yourself then.
 *      This will however prevent XFolder from
 *      presenting the proper informational windows
 *      to the user.
 *
 *      The buffer that pszError points to is only
 *      evaluated if you return APM_CANCEL.
 */

ULONG apmPreparePowerOff(PSZ pszError)      // in: error message
{
    APIRET          arc = NO_ERROR;
    ULONG           ulAction = 0;
    SENDPOWEREVENT  sendpowerevent;
    ULONG           ulAPMRc;
    ULONG           ulPacketSize;
    ULONG           ulDataSize;

    // open APM.SYS
    ulAction = 0;
    arc = DosOpen("\\DEV\\APM$", &hfAPMSys, &ulAction, 0, FILE_NORMAL,
            OPEN_ACTION_OPEN_IF_EXISTS,
            OPEN_FLAGS_FAIL_ON_ERROR | OPEN_SHARE_DENYNONE | OPEN_ACCESS_READWRITE,
            NULL);
    if (arc != NO_ERROR) {
        strcpy(pszError, "Cannot open APM dirver.");
        return (APM_CANCEL);
    }
    // enable APM feature
    memset(&sendpowerevent, 0, sizeof(sendpowerevent));
    ulAPMRc = 0;
    sendpowerevent.usSubID = POWER_SUBID_ENABLE_APM;
    ulPacketSize = sizeof(sendpowerevent);
    ulDataSize = sizeof(ulAPMRc);
    arc = DosDevIOCtl(hfAPMSys, IOCTL_POWER, POWER_SENDPOWEREVENT,
            &sendpowerevent, ulPacketSize, &ulPacketSize,
            &ulAPMRc, ulDataSize, &ulDataSize);
    if ((arc != NO_ERROR) || (ulAPMRc != NO_ERROR)) {
            strcpy(pszError, "Cannot enable APM.");
            return (APM_CANCEL);
    }
    return (APM_OK | APM_DOSSHUTDOWN_1);
}

/*
 *@@ apmDoPowerOff:
 *      If apmPreparePowerOff returned with the APM_OK flag set,
 *      XFolder calls this function after it is
 *      done with XShutdown. In this function,
 *      you should call the APM function which
 *      turns off the computer's power. If you
 *      have not specified one of the APM_DOSSHUTDOWN
 *      flags in apmPreparePowerOff, you must call DosShutdown
 *      yourself here.
 *
 *      <P><B>Usage:</B>
 *      only once after XShutdown.
 *      We do not return from this function, but stay in an infinite loop.
 *
 *      <P><B>Parameters:</B>
 *      none.
 *      </LL>
 */

VOID apmDoPowerOff(VOID)
{
    APIRET          arc = NO_ERROR;
    SENDPOWEREVENT  sendpowerevent;
    USHORT          usAPMRc;
    ULONG           ulPacketSize;
    ULONG           ulDataSize;

    memset(&sendpowerevent, 0, sizeof(sendpowerevent));
    usAPMRc = 0;
    sendpowerevent.usSubID = POWER_SUBID_SET_POWER_STATE;
    sendpowerevent.usData1 = POWER_DEVID_ALL_DEVICES;
    sendpowerevent.usData2 = POWER_STATE_OFF;
    ulPacketSize = sizeof(sendpowerevent);
    ulDataSize = sizeof(usAPMRc);

    // this initiates the APM power-off
    arc = DosDevIOCtl(hfAPMSys, IOCTL_POWER, POWER_SENDPOWEREVENT,
            &sendpowerevent, ulPacketSize, &ulPacketSize,
            &usAPMRc, ulDataSize, &ulDataSize);
    DosClose(hfAPMSys);

    // OS/2 _does_ return from that function, but in the
    // background APM power-off is now being executed.
    // So we must now loop forever until power-off has
    // completed, when the computer will just stop
    // executing anything.

    // We must _not_ return to the XFolder shutdown routines,
    // because XFolder would then enter a critical section,
    // which would keep power-off from working.
    while (TRUE)
        DosSleep(10000);

    // return; // keep compiler happy
}

