/* slimski - Simple Login Manager
   Copyright (C) 1997, 1998 Per Liden
   Copyright (C) 2004-06 Simone Rota <sip@varlock.com>
   Copyright (C) 2004-06 Johannes Winkelmann <jw@tks6.net>

   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.
*/

#include <sstream>
#include <poll.h>
#include "panel.h"
//    #   i nclude "capslock.h"      23Feb     moved, to app.cpp
#include <iostream>
#include <fstream>
// #i nclude <cstdio>  //  ~~  needed for logging?   // not helpful
#include "log.h"
#include <pwd.h>    // getpwnam
#include <sys/types.h>
#include "app.h"   //   zeebug
#include "util.h"  // runCommand()

#include <unistd.h>  // showMultilineMsg()
#include <stdio.h>
#include <fcntl.h>

using namespace std;

extern bool testing;
extern bool zeebug;

Panel::Panel(Display* dpy, int scr, Window woot, Cfg* config, const string& themedir) {
    Dpy = dpy;
    Scr = scr;
    Rootwin = woot;
    cfg = config;
    haschosenF1 = false;

    // at this point, no username has been entered. We don't yet know who the user is...
    if ( cfg->getOption("default_user") != "" ) {
        string itzahome;
        char *aaname = const_cast<char*>(cfg->getOption("default_user").c_str());
        struct passwd *lookp;
        if ( (lookp = getpwnam(aaname)) == NULL ) {
            if (testing)
                cout << "default_user: " << aaname << " ~~~~ non-existent login account" << endl;
        } else {
            itzahome = lookp->pw_dir;
            if (zeebug)    cout << "  ~~~ userhomedir ~~~  " << itzahome << endl;

            string luf;
            luf = itzahome + "/.config/slimski.lastused";
            readLastUsed(luf);
        }
    } else if ( cfg->isValidSesstype(cfg->getOption("default_sessiontype")) ) {
        sessiontype = cfg->getOption("default_sessiontype");
        if (zeebug)      cout << "applied default_sessiontype: " << sessiontype << endl;

    } else {
        sessiontype = cfg->dacurrentSessiontype();    //   howdy      non-ideal (next F1 press will not always return "next" item, eh)
        if (zeebug)      cout << "dacurrentSessiontype is now set to: " << sessiontype.c_str() << endl;

    }
    PaintRootwinTexts();  //  aka   "ShowSessiontype()"


    // Init GC
    XGCValues gcv;
    unsigned long gcm = GCForeground|GCBackground|GCGraphicsExposures;
    gcv.foreground = GetColor("black");
    gcv.background = GetColor("white");
    gcv.graphics_exposures = False;
    TextGC = XCreateGC(Dpy, Rootwin, gcm, &gcv);

    font = XftFontOpenName(Dpy, Scr, cfg->getOption("input_font").c_str());
    welcomefont = XftFontOpenName(Dpy, Scr, cfg->getOption("welcome_font").c_str());
    forbiddenfont = XftFontOpenName(Dpy, Scr, cfg->getOption("forbidden_font").c_str());
    F1font = XftFontOpenName(Dpy, Scr, cfg->getOption("F1_font").c_str());   // displayed within panel

    F2font = XftFontOpenName(Dpy, Scr, cfg->getOption("F2_font").c_str());   // displayed on background
    F3font = XftFontOpenName(Dpy, Scr, cfg->getOption("F3_font").c_str());
    F4font = XftFontOpenName(Dpy, Scr, cfg->getOption("F4_font").c_str());
    F5font = XftFontOpenName(Dpy, Scr, cfg->getOption("F5_font").c_str());
    F6font = XftFontOpenName(Dpy, Scr, cfg->getOption("F6_font").c_str());
    F7font = XftFontOpenName(Dpy, Scr, cfg->getOption("F7_font").c_str());
    F8font = XftFontOpenName(Dpy, Scr, cfg->getOption("F8_font").c_str());
    F9font = XftFontOpenName(Dpy, Scr, cfg->getOption("F9_font").c_str());
    F10font = XftFontOpenName(Dpy, Scr, cfg->getOption("F10_font").c_str());
    F11font = XftFontOpenName(Dpy, Scr, cfg->getOption("F11_font").c_str());
    F12font = XftFontOpenName(Dpy, Scr, cfg->getOption("F12_font").c_str());
    cust1font = XftFontOpenName(Dpy, Scr, cfg->getOption("cust1font").c_str());
    cust2font = XftFontOpenName(Dpy, Scr, cfg->getOption("cust2font").c_str());
    cust3font = XftFontOpenName(Dpy, Scr, cfg->getOption("cust3font").c_str());
    cust4font = XftFontOpenName(Dpy, Scr, cfg->getOption("cust4font").c_str());
    cust5font = XftFontOpenName(Dpy, Scr, cfg->getOption("cust5font").c_str());

    enterfont = XftFontOpenName(Dpy, Scr, cfg->getOption("username_font").c_str());
    msgfont = XftFontOpenName(Dpy, Scr, cfg->getOption("msg_font").c_str());

    Visual* visual = DefaultVisual(Dpy, Scr);
    Colormap colormap = DefaultColormap(Dpy, Scr);
    // NOTE: using XftColorAllocValue() would be a better solution.
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("input_color").c_str(), &inputcolor);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("welcome_color").c_str(), &welcomecolor);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("forbidden_color").c_str(), &forbiddencolor);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("F1_color").c_str(), &F1color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("F2_color").c_str(), &F2color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("F3_color").c_str(), &F3color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("F4_color").c_str(), &F4color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("F5_color").c_str(), &F5color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("F6_color").c_str(), &F6color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("F7_color").c_str(), &F7color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("F8_color").c_str(), &F8color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("F9_color").c_str(), &F9color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("F10_color").c_str(), &F10color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("F11_color").c_str(), &F11color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("F12_color").c_str(), &F12color);

    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("username_color").c_str(), &entercolor);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("msg_color").c_str(), &msgcolor);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("sessiontype_color").c_str(), &sessioncolor);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("background_color").c_str(), &backgroundcolor);  // recheck: moot unless style=solidcolor?

    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("cust1_color").c_str(), &cust1color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("cust2_color").c_str(), &cust2color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("cust3_color").c_str(), &cust3color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("cust4_color").c_str(), &cust4color);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("cust5_color").c_str(), &cust5color);


    // Load properties from config / theme
    input_name_x = cfg->getIntOption("input_name_x");
    input_name_y = cfg->getIntOption("input_name_y");
    input_pass_x = cfg->getIntOption("input_pass_x");
    input_pass_y = cfg->getIntOption("input_pass_y");

    if (input_pass_x < 0 || input_pass_y < 0){  // single inputbox mode
        input_pass_x = input_name_x;
        input_pass_y = input_name_y;
    }

    // Load  background image and panel image
    //   Yes, again (separately loaded by app.cpp)
    Image* bg = new Image();
    string bgstyle = cfg->getOption("background_style");
    //if (bgstyle == "")      bgstyle = "solidcolor";  // handle edge case ~~ line has been declared within theme, but value is absent
    //                                                    naw, in that case the implicit value "solidcolor" assigned in cfg.cpp would be in effect

    if (bgstyle != "solidcolor") {
        string bgimgsrc = themedir + "/background.png";
        bool bgsrcloaded = bg->Read(bgimgsrc.c_str());
        if (!bgsrcloaded) {
            bgimgsrc = themedir + "/background.jpg";
            bgsrcloaded = bg->Read(bgimgsrc.c_str());
            if (!bgsrcloaded){
                if (testing) {
                    cout << "slimski: background{png,jpg} imagefile absent for theme '" << basename((char*)themedir.c_str()) << "'" << std::endl;
                }
                bgstyle = "solidcolor";  // fallback
            }
        }
    }

    string hexval = cfg->getOption("background_color");
    hexval = hexval.substr(1,6);
    //if (hexval == "")     hexval = "ff0000";  // handle edge case ~~ line has been declared within theme, but value is absent
    //                                             naw, in that case the implicit value "#CCCCCC" assigned in cfg.cpp would be in effect

    if (bgstyle == "stretch" || bgstyle == "stretched") {
        bg->Resize(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)));
    } else if (bgstyle == "tile" || bgstyle == "tiled") {
        bg->Tile(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)));
    } else if (bgstyle == "center" || bgstyle == "centered") {
        bg->Center(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), hexval.c_str() );
    } else {          // solidcolor or error
        bg->Center(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), hexval.c_str());
        if (zeebug) {
            cout << "backgroundcolor: " << hexval << endl;  // the resultant color assignment is a detail which may differ from -s output
        }
    }
/**
//--------------        xref   app.cpp    Line 1201
    showlogo = cfg->getOption("showlogo_enabled")=="true";
    string logofile = themedir + "/logo.png";
    logoimage = new Image;
    bool limloaded = logoimage->Read( logofile.c_str() );
    if (showlogo) {
        if (!limloaded) {
            logofile = themedir + "/logo.jpg";
            limloaded = logoimage->Read(logofile.c_str());
            if (!limloaded  &&  testing  ) {
                cout << "slimski: could not load a logo image for the specified theme '"
                     << basename((char*)themedir.c_str()) << "'" << std::endl;
            }
        } else {
            string cfgX = cfg->getOption("logo_x");
            string cfgY = cfg->getOption("logo_y");
            Xog = Cfg::absolutepos(cfgX, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), logoimage->Width());
            Yog = Cfg::absolutepos(cfgY, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), logoimage->Height());
          //logoimage->Merge(bg, Xog, Yog);
            logoimage->Merge(bg, 100, 100);     //  grabbing at straws here
            ////LogoPixmap = logoimage->createPixmap(Dpy, Scr, Rootwin);   // no effect ( alone, nor in conjunction with the following line)
            //XSetWindowBackgroundPixmap(Dpy, Rootwin, LogoPixmap);   // no effect
        }
    }
//--------------
*/

    string panelpj = themedir + "/panel.png";
    pimage = new Image;
    bool pimloaded = pimage->Read( panelpj.c_str() );
    if (!pimloaded) {
        panelpj = themedir + "/panel.jpg";
        pimloaded = pimage->Read(panelpj.c_str());
        if (!pimloaded  &&  testing ) {
            cout << "slimski: could not load a panel image for the specified theme '"
                 << basename((char*)themedir.c_str()) << "'" << std::endl;
        }
    }

    string cfgX = cfg->getOption("input_panel_x");
    string cfgY = cfg->getOption("input_panel_y");
    if (pimloaded) {
        X = Cfg::absolutepos(cfgX, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), pimage->Width());
        Y = Cfg::absolutepos(cfgY, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), pimage->Height());
        pimage->Merge(bg, X, Y);        // merge image into background
        PanelPixmap = pimage->createPixmap(Dpy, Scr, Rootwin);
    }

    delete bg;   //    pimage not deleted here
    ////delete logoimage;
}


Panel::~Panel() {
    Visual* visual = DefaultVisual(Dpy, Scr);
    Colormap colormap = DefaultColormap(Dpy, Scr);

    XftColorFree(Dpy, visual, colormap, &inputcolor);
    XftColorFree(Dpy, visual, colormap, &welcomecolor);
    XftColorFree(Dpy, visual, colormap, &forbiddencolor);
    XftColorFree(Dpy, visual, colormap, &F1color);

    XftColorFree(Dpy, visual, colormap, &F2color);
    XftColorFree(Dpy, visual, colormap, &F3color);
    XftColorFree(Dpy, visual, colormap, &F4color);
    XftColorFree(Dpy, visual, colormap, &F5color);
    XftColorFree(Dpy, visual, colormap, &F6color);
    XftColorFree(Dpy, visual, colormap, &F7color);
    XftColorFree(Dpy, visual, colormap, &F8color);
    XftColorFree(Dpy, visual, colormap, &F9color);
    XftColorFree(Dpy, visual, colormap, &F10color);
    XftColorFree(Dpy, visual, colormap, &F11color);
    XftColorFree(Dpy, visual, colormap, &F12color);

    XftColorFree(Dpy, visual, colormap, &entercolor);
    XftColorFree(Dpy, visual, colormap, &msgcolor);
    XftColorFree(Dpy, visual, colormap, &sessioncolor);
    XftColorFree(Dpy, visual, colormap, &backgroundcolor);

    XftColorFree(Dpy, visual, colormap, &cust1color);
    XftColorFree(Dpy, visual, colormap, &cust2color);
    XftColorFree(Dpy, visual, colormap, &cust3color);
    XftColorFree(Dpy, visual, colormap, &cust4color);
    XftColorFree(Dpy, visual, colormap, &cust5color);

    XFreeGC(Dpy, TextGC);
    XftFontClose(Dpy, font);
    XftFontClose(Dpy, msgfont);
    XftFontClose(Dpy, welcomefont);
    XftFontClose(Dpy, forbiddenfont);
    XftFontClose(Dpy, F1font);
    XftFontClose(Dpy, F2font);
    XftFontClose(Dpy, F3font);
    XftFontClose(Dpy, F4font);
    XftFontClose(Dpy, F5font);
    XftFontClose(Dpy, F6font);
    XftFontClose(Dpy, F7font);
    XftFontClose(Dpy, F8font);
    XftFontClose(Dpy, F9font);
    XftFontClose(Dpy, F10font);
    XftFontClose(Dpy, F11font);
    XftFontClose(Dpy, F12font);

    XftFontClose(Dpy, enterfont);

    XftFontClose(Dpy, cust1font);
    XftFontClose(Dpy, cust2font);
    XftFontClose(Dpy, cust3font);
    XftFontClose(Dpy, cust4font);
    XftFontClose(Dpy, cust5font);

    delete pimage;    //  howdy      hmm Pane lPixmap is never destroyed
}

void Panel::OpenPanel(const string& themedir) {
    string zsrcimg = themedir + "/panel.png";
    Image* zpimage = new Image();
    bool zpimloaded = zpimage->Read(zsrcimg.c_str());
    if (!zpimloaded) {
        zsrcimg = themedir + "/panel.jpg";
        zpimloaded = zpimage->Read(zsrcimg.c_str());
    }

    if (!zpimloaded) {
        //        this is non-ideal (can result in white-text-on-white if panel imagefile missing)
        Win = XCreateSimpleWindow(Dpy, Rootwin, X, Y, 580, 360, 0, GetColor("white"), GetColor("white"));
    } else {
        Win = XCreateSimpleWindow(Dpy, Rootwin, X, Y, zpimage->Width(), zpimage->Height(), 0, GetColor("white"), GetColor("white"));
        XSetWindowBackgroundPixmap(Dpy, Win, PanelPixmap);
    }


//  v----   works, but is fairly useless toward the intended goal. Only events within the panelimage bounds are detected.
//          Would require a stupidly-oversized (transparent?) nearly fullscreen panel image displayed during a (an additional, TBD) test mode...
    if (testing)
        XSelectInput(Dpy, Win, ExposureMask | KeyPressMask | ButtonPressMask);   // indicates to xserver the event types we care about
    else
        XSelectInput(Dpy, Win, ExposureMask | KeyPressMask);


    // Show window
    XMapWindow(Dpy, Win);
    XMoveWindow(Dpy, Win, X, Y);

    // Grab keyboard
    XGrabKeyboard(Dpy, Win, False, GrabModeAsync, GrabModeAsync, CurrentTime);  // ------- howdy bub        may interfere with app launched voa popfirst_cmd

    XFlush(Dpy);
    delete zpimage;
}


void Panel::ClosePanel() {
    XUngrabKeyboard(Dpy, CurrentTime);
    XUnmapWindow(Dpy, Win);      //  conditionally, it may not have been mapped
    XDestroyWindow(Dpy, Win);
    XFlush(Dpy);
}

void Panel::ClearPanel() {
    ResetName();     //     (currently, clearPanel is called only when failed authentication occurs)
    ResetPasswd();
    XClearWindow(Dpy, Rootwin);
    XClearWindow(Dpy, Win);
    Cursor(SHOW);
    ShowText();
    XFlush(Dpy);
}


void Panel::Message(const string& text, int duration /*= 2*/) {
    string cfgX, cfgY;
    XGlyphInfo extents;
    int howlong = duration;     //   howdy   why?
    if (howlong < 1)   howlong = 1;

    XftDraw *draw = XftDrawCreate(Dpy, Rootwin, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
    XftTextExtentsUtf8(Dpy, msgfont, reinterpret_cast<const XftChar8*>(text.c_str()), text.length(), &extents);

    cfgX = cfg->getOption("msg_x");   // 30 if not specified in theme file
    cfgY = cfg->getOption("msg_y");
    int msg_x = Cfg::absolutepos(cfgX, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.width);
    int msg_y = Cfg::absolutepos(cfgY, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.height);

    if ( msg_x >= 0  &&  msg_y >= 0 ) {
        SlimDrawString8 (draw, &msgcolor, msgfont, msg_x, msg_y, text);
    }

    XFlush(Dpy);
    sleep(howlong);
    XftDrawDestroy(draw);
}


unsigned long Panel::GetColor(const char* colorname) {
    XColor color;
    XWindowAttributes attributes;

    XGetWindowAttributes(Dpy, Rootwin, &attributes);
    color.pixel = 0;

    if(!XParseColor(Dpy, attributes.colormap, colorname, &color))
        logStream << "slimski: Panel::GetColor() cannot parse color " << colorname << endl;
    else if(!XAllocColor(Dpy, attributes.colormap, &color))
        logStream << "slimski: Panel::GetColor() cannot allocate color " << colorname << endl;

    return color.pixel;
}

void Panel::Cursor(int visible) {
    if(cfg->getOption("input_hidetextcursor") == "true") {
        return;
    }

    const char* text = NULL;
    int xx = 0, yy = 0, y2 = 0, cheight = 0;
    const char* txth = "Wj";  // used to calc cursor height

    switch(field) {
        case Get_Passwd:
            text = HiddenPasswdBuffer.c_str();
            xx = input_pass_x;
            yy = input_pass_y;
            break;

        case Get_Name:
            text = NameBuffer.c_str();
            xx = input_name_x;
            yy = input_name_y;
            break;
    }

    XGlyphInfo extents;
    XftTextExtentsUtf8(Dpy, font, (XftChar8*)txth, strlen(txth), &extents);
    cheight = extents.height;
    y2 = yy - extents.y + extents.height;
    XftTextExtentsUtf8(Dpy, font, (XftChar8*)text, strlen(text), &extents);
    if(cfg->getOption("input_center_text") == "true") {
        xx += extents.width / (double) 2;       // spec double is a late change
    } else {
        xx += extents.width;
    }

    if(visible == SHOW) {
        XSetForeground(Dpy, TextGC, GetColor(cfg->getOption("input_color").c_str()));
        XDrawLine(Dpy, Win, TextGC, xx+1, yy-cheight, xx+1, y2);
    } else {
        XClearArea(Dpy, Win, xx+1, yy-cheight, 1, y2-(yy-cheight)+1, false);
    }
}

void Panel::EventHandler(const Panel::FieldType& curfield) {
    XEvent event;
    field=curfield;
    bool loop = true;
    OnExpose();

    struct pollfd x11_pfd = {0};
    x11_pfd.fd = ConnectionNumber(Dpy);
    x11_pfd.events = POLLIN;

    while(loop) {
      if(XPending(Dpy) || poll(&x11_pfd, 1, -1) > 0) {
//    if(XPending(Dpy) || poll(&x11_pfd, 1, 100) > 0) {
/////////////      THIS MAY BE A BREAKING CHANGE     howdy bub      this change  would reputedly  "fix  `text_widget` updating delay "
/////////////                   related to XCopyArea() call causing segfaults during slim startup (we do not use that exact fn)
//
//      Yes, "breakage" !
//      I had introduced the looping delay b/c I anticipated the (at the time) introduction of polling mousecursor event would
//      create a flood.  Whoops, for a speedy typist, I suspect this delay caused some keystrokes to be missed.
//      Maybe I will retest later, using a lesser (10ms?) delay
//
//
            while(XPending(Dpy)) {
                XNextEvent(Dpy, &event);
                switch(event.type) {
                    case Expose:
                        OnExpose();
                        break;

                    case KeyPress:
                        loop=OnKeyPress(event);
                        break;
                    // --------------------------
                    case ButtonPress:
                        //
                        //      CURRENTLY, THIS WILL NOT BE REACHED. ABOVE, WE ARE _NOT_  MONITORING ButtonPressMask VIA XSelectInput()
                        //
                        if (testing) {      //  might create an -m commandline option and employ an ORed test here
                            cout << "mouse buttonclick detected @PANEL:  x= " << event.xbutton.x << "  y= " << event.xbutton.y << endl;
                            /**
                               We do not care WHICH button was clicked, eh     (event.xbutton.button)
                               Later, toward facilitating themer task of precisely positioning
                               the offsets/coordinates for messagestrings...
                               ...might (create an commandline option) display in-app as the clicks occur.
                               Testing whether events from entire window are caught here, or (probably) only events within the bounds of the "panel" rectangle
                               -=-
                               The coord values may prove to be relative (skewed by runtime rescaling and/or non-rootwindow display during preview mode)
                                              // int x, y;         x, y coordinates in event window
                                              // int x_root;       coordinates relative to root
                                              // https://www.x.org/releases/X11R7.7/doc/libXi/inputlib.html
                            */
                        }
                        break;
                    // -------------------------- feedback, to facilitate more exact placement of panel and msg texts
                }   //              also demonstrates that the mouse cursor is just hidden (vs "disabled")
            }
        }
    }
    return;
}


void Panel::OnExpose(void) {
    XftDraw *draw = XftDrawCreate(Dpy, Win, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
    XClearWindow(Dpy, Win);

    if (input_pass_x != input_name_x || input_pass_y != input_name_y){
        SlimDrawString8 (draw, &inputcolor, font, input_name_x, input_name_y, NameBuffer);
        SlimDrawString8 (draw, &inputcolor, font, input_pass_x, input_pass_y, HiddenPasswdBuffer);
    } else {    // "single input field" mode
        switch(field) {
            case Get_Passwd:
                SlimDrawString8 (draw, &inputcolor, font, input_pass_x, input_pass_y, HiddenPasswdBuffer);
                break;
            case Get_Name:
                SlimDrawString8 (draw, &inputcolor, font, input_name_x, input_name_y, NameBuffer);
                break;
        }
    }

    XftDrawDestroy(draw);
    Cursor(SHOW);
    ShowText();
}


bool Panel::OnKeyPress(XEvent& event) {
    char ascii;
    KeySym keysym;
    XComposeStatus compstatus;
    int xx = 0;
    int yy = 0;
    string text;
    string formerString = "";

    XLookupString(&event.xkey, &ascii, 1, &keysym, &compstatus);
    switch(keysym) {
        case XK_F1:
            haschosenF1 = true;
            ChangeSessiontype();
            return true;  // ----------- continue looping

        case XK_F2:
            system( cfg->getOption("F2_cmd").c_str() );   // verbatim, as given. No validation
            //Util::runCommand( cfg->getOption("F2_cmd"), "F2_cmd");
            return true;
        case XK_F3:
            system( cfg->getOption("F3_cmd").c_str() );
            //Util::runCommand( cfg->getOption("F3_cmd"), "F3_cmd");
            return true;
        case XK_F4:
            system( cfg->getOption("F4_cmd").c_str() );
            //Util::runCommand( cfg->getOption("F4_cmd"), "F4_cmd");
            return true;
        case XK_F5:
            system( cfg->getOption("F5_cmd").c_str() );
            //Util::runCommand( cfg->getOption("F5_cmd"), "F5_cmd");
            return true;
        case XK_F6:
            system( cfg->getOption("F6_cmd").c_str() );
            //Util::runCommand( cfg->getOption("F6_cmd"), "F6_cmd");
            return true;
        case XK_F7:
            system( cfg->getOption("F7_cmd").c_str() );
            //Util::runCommand( cfg->getOption("F7_cmd"), "F7_cmd");
            return true;
        case XK_F8:
            system( cfg->getOption("F8_cmd").c_str() );
            //Util::runCommand( cfg->getOption("F8_cmd"), "F8_cmd");
            return true;
        case XK_F9:
            system( cfg->getOption("F9_cmd").c_str() );
            //Util::runCommand( cfg->getOption("F9_cmd"), "F9_cmd");
            return true;
        case XK_F10:
            system( cfg->getOption("F10_cmd").c_str() );
            //Util::runCommand( cfg->getOption("F10_cmd"), "F10_cmd");
            return true;
        case XK_F11:
            system( cfg->getOption("F11_cmd").c_str() );
            //Util::runCommand( cfg->getOption("F11_cmd"), "F11_cmd");
            return true;
        case XK_F12:
            system( cfg->getOption("F12_cmd").c_str() );
            //Util::runCommand( cfg->getOption("F12_cmd"), "F12_cmd");
            return true;
        case XK_Return:
        case XK_KP_Enter:
            if (field==Get_Name){
                if (NameBuffer.empty()) {
                    if ( zeebug ) {
                        cout << "detected ENTER keypress while NameBuffer empty" << endl;
                    }
                    return true; // reject empty username     howdy  tested ok
                }              //// no harm (will still fail, downstream)  ////      BTW, NameBuffer  is type c_str
/*
                if ( !IsCleanString(NameBuffer) ) {
                    if (zeebug)       cout << "Ouch! NameBuffer contains spaces or ctrl chars (or is empty)" << endl;  // should never happen, eh?

                }
*/
                if (NameBuffer == "root") {
                    NameBuffer.clear();
                    HiddenPasswdBuffer.clear();
                    PasswdBuffer.clear();
                    showMuMsg(cfg->getOption("forbidden_msg"), 3);  // howdy     may need to call ResetPassword()
                    return true;
                }

// ---------------------
                //     on one hand, this might be welcomed as a convenience,
                //     but on the other hand may be regarded as a nuisance.
                //     When default_user is not configured (and when it IS, but someone
                //     other than the default user is logging in)
                //     If the user has F1 selected a sessiontype different from their last-used
                //     prior to typing username + Enter...
                //     ...this winds up resetting//undoing that manually-selected sessiontype
                //
                //      ^----- nuisance now avoided, by setting/checking bool haschosenF1
                string itzahome;
                const char *aaname = NameBuffer.c_str();  // already sanity-checked, above
                struct passwd *lookp;
                lookp = getpwnam(aaname);
                if ( !haschosenF1 && (lookp != NULL) ) {   // tested ok
                    itzahome = lookp->pw_dir;
                    string luf;
                    luf = itzahome + "/.config/slimski.lastused"; // oh well, can snoop the lastused of any user account
                    readLastUsed(luf);
                }
                PaintRootwinTexts();     //  aka   "ShowSessiontype()"
// ---------------------

                if (NameBuffer == "halt" || NameBuffer == "systemhalt" || NameBuffer == "shutdown"){
                    if (cfg->getOption("systemhalt_enabled") != "true") {
                        showMuMsg(cfg->getOption("disabledviaconf_msg"), 2);
                        return true;
                    }
                    if (testing) {
                        string boba = "systemhalt_enabled=true  BUT THIS ACTION IS UNAVAILABLE DURING PREVIEW";
                        showMuMsg(boba, 2);
                        cout << boba.c_str() << endl;
                        return true;
                    }
                    action = Halt;
                } else if (NameBuffer == "reboot"){

                    if (cfg->getOption("reboot_enabled") != "true") {     // howdy    redundant
                        showMuMsg(cfg->getOption("disabledviaconf_msg"), 2);
                        return true;
                    }
                    if (testing) {
                        string boba ="reboot_enabled=true  BUT THIS ACTION IS UNAVAILABLE DURING PREVIEW";
                        showMuMsg(boba, 2);
                        cout << boba.c_str() << endl;
                        return true;
                    }
                    action = Reboot;
                } else if (NameBuffer == "exit") {
                    if (cfg->getOption("exit_enabled") != "true") {
                        showMuMsg(cfg->getOption("disabledviaconf_msg"), 2);
                        return true;
                    }
/**
                    if (testing) {   //   howdy bub    THIS IS INEFFECTIVE HERE
                        string boba = "exit_enabled=true  BUT THIS ACTION IS UNAVAILABLE DURING PREVIEW";
                        showMuMsg(boba, 3);
                        cout << boba.c_str() << "\n" << endl;
                        return true;
                    }
*/
                    action = Exit;
                } else if (NameBuffer=="suspend"){     // conf can omit if unwanted (no enable/disable toggle is provided)
                    action = Suspend;
                } else {
                    if (! NameBuffer.empty()){    //    EMPTY check is probably redundant here
                        action = Login;
                    }
                }
            }
            return false;
        default:
            break;
    } // end SWITCH lookupstring

    Cursor(HIDE);          //   const.h:23:#define HIDE        0
    switch(keysym){
        case XK_Delete:
        case XK_BackSpace:
            switch(field)
            {
                case GET_NAME:
                    if (! NameBuffer.empty()){
                        formerString=NameBuffer;
                        NameBuffer.erase(--NameBuffer.end());
                    }
                    break;
                case GET_PASSWD:
                    if (! PasswdBuffer.empty()){
                        formerString=HiddenPasswdBuffer;
                        PasswdBuffer.erase(--PasswdBuffer.end());
                        HiddenPasswdBuffer.erase(--HiddenPasswdBuffer.end());
                    }
                    break;
            }
            break;

        case XK_w:  // howdy bub     undocumented (apparently requested at some point through the years)
        case XK_u:
            if (reinterpret_cast<XKeyEvent&>(event).state & ControlMask) {
                switch(field) {
                    case Get_Passwd:
                        formerString = HiddenPasswdBuffer;
                        HiddenPasswdBuffer.clear();
                        PasswdBuffer.clear();
                        break;

                    case Get_Name:
                        formerString = NameBuffer;
                        NameBuffer.clear();
                        break;
                }
                break;
            }
        default:
            if (isprint(ascii) && (keysym < XK_Shift_L || keysym > XK_Hyper_R)){ // howdy  bub  document ascii input chars only
                switch(field) {                     //   TODO   #i nclude <locale>     en.cppreference.com/w/cpp/locale/isprint
                    case GET_NAME:
                        formerString=NameBuffer;
                        if (NameBuffer.length() < INPUT_MAXLENGTH_NAME-1){
                            NameBuffer.append(&ascii,1);
                        }
                        break;
                    case GET_PASSWD:
                        formerString=HiddenPasswdBuffer;
                        if (PasswdBuffer.length() < INPUT_MAXLENGTH_PASSWD-1){
                            PasswdBuffer.append(&ascii,1);
                            HiddenPasswdBuffer.append("*");
                        }
                    break;
                }
            }
            break;
    }

    XGlyphInfo extents;
    XftDraw *draw = XftDrawCreate(Dpy, Win, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
    switch(field) {
        case Get_Name:
            text = NameBuffer;
            xx = input_name_x;
            yy = input_name_y;
            break;
        case Get_Passwd:
            text = HiddenPasswdBuffer;
            xx = input_pass_x;
            yy = input_pass_y;
            break;
    }

    if (!formerString.empty()){
        const char* txth = "Wj"; // get proper maximum height
        XftTextExtentsUtf8(Dpy, font, reinterpret_cast<const XftChar8*>(txth), strlen(txth), &extents);
        int maxHeight = extents.height;

        XftTextExtentsUtf8(Dpy, font, reinterpret_cast<const XftChar8*>(formerString.c_str()), formerString.length(), &extents);
        int maxLength = extents.width;
        int centeringOffset = 0;
        if(cfg->getOption("input_center_text") == "true"){
            centeringOffset = maxLength / (double) 2;   // spec double is a late change
        }
        XClearArea(Dpy, Win, xx-3-centeringOffset, yy-maxHeight-3, maxLength+6, maxHeight+6, false);
    }

    if (!text.empty()) {
        int centeringOffset = 0;
        if(cfg->getOption("input_center_text") == "true"){
            XftTextExtentsUtf8(Dpy, font, reinterpret_cast<const XftChar8*>(text.c_str()), text.length(), &extents);
            int maxLength = extents.width;
            centeringOffset = maxLength / (double) 2;      // spec double is a late change
        }
        SlimDrawString8 (draw, &inputcolor, font, xx-centeringOffset, yy, text);
    }

    XftDrawDestroy(draw);
    Cursor(SHOW);
    return true;
}


// paint "enter username" or "welcome to %host" message (displayed within the bounds of panel)
void Panel::ShowText(){
    string cfgX, cfgY, msg;
    XGlyphInfo extents;
    bool singleInputMode =  input_name_x == input_pass_x  &&  input_name_y == input_pass_y;
    XftDraw *draw = XftDrawCreate(Dpy, Win, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));

    welcome_message = cfg->getWelcomeMessage();  //  for certain locales, the messagestring may be too long to fit upon a tiny panel image

    XftTextExtentsUtf8(Dpy, welcomefont, (XftChar8*)welcome_message.c_str(), strlen(welcome_message.c_str()), &extents);
    cfgX = cfg->getOption("welcome_x");
    cfgY = cfg->getOption("welcome_y");
    welcome_x = Cfg::absolutepos(cfgX, pimage->Width(), extents.width);
    welcome_y = Cfg::absolutepos(cfgY, pimage->Height(), extents.height);
    if (welcome_x >= 0 && welcome_y >= 0) {
        SlimDrawString8 (draw, &welcomecolor, welcomefont, welcome_x, welcome_y, welcome_message);
    }

    //  Enter username+password message
    if (!singleInputMode|| field == Get_Passwd ) {
        msg = cfg->getOption("password_msg");
        if (msg != ""){
            XftTextExtentsUtf8(Dpy, enterfont, (XftChar8*)msg.c_str(), strlen(msg.c_str()), &extents);
            cfgX = cfg->getOption("password_x");
            cfgY = cfg->getOption("password_y");
            password_x = Cfg::absolutepos(cfgX, pimage->Width(), extents.width) - extents.width / (double) 2;
            password_y = Cfg::absolutepos(cfgY, pimage->Height(), extents.height);
            if (password_x >= 0 && password_y >= 0){
                SlimDrawString8 (draw, &entercolor, enterfont, password_x, password_y, msg);
            }
        }
    }
    if (!singleInputMode || field == Get_Name ) {
        msg = cfg->getOption("username_msg");
        if (msg != ""){
            XftTextExtentsUtf8(Dpy, enterfont, (XftChar8*)msg.c_str(), strlen(msg.c_str()), &extents);
            cfgX = cfg->getOption("username_x");
            cfgY = cfg->getOption("username_y");
            username_x = Cfg::absolutepos(cfgX, pimage->Width(), extents.width) - extents.width / (double) 2;
            username_y = Cfg::absolutepos(cfgY, pimage->Height(), extents.height);
            if (username_x >= 0 && username_y >= 0){
                SlimDrawString8 (draw, &entercolor, enterfont, username_x, username_y, msg);
            }
        }
    }
    XftDrawDestroy(draw);
    PaintRootwinTexts();   //  aka   "ShowSessiontype()"    howdy bub     recheck: still necessary here?
}


string Panel::getSessiontype() {
    return sessiontype;
}


// choose next available sessiontype
void Panel::ChangeSessiontype() {
    sessiontype = cfg->nextSession(sessiontype);
    if (sessiontype.size() > 0)        PaintRootwinTexts();
    //               ^-----------  whadda?  checking (still) blank, or config specifies xsessionsdir but dir contains no usable items
    //       instead    if(cfg->getIntSesslistSize > 1)    ???
}


// Display sessiontype and other msgstrings on rootwin.
// necessary to re-draw the static strings each time b/c they share the same context  (so would be erased//overwritten otherwise)
//        previously, the sole caller was ChangeSessiontype(), above
void Panel::PaintRootwinTexts() {    //  howdy     aka "ShowSessiontype()"
    string msg_x, msg_y, dastring, currsession;
    XGlyphInfo extents;
    XClearWindow(Dpy, Rootwin);

    currsession = cfg->getOption("sessiontype_msg") + " " + sessiontype;
    sessionfont = XftFontOpenName(Dpy, Scr, cfg->getOption("sessiontype_font").c_str());

    XftDraw *draw = XftDrawCreate(Dpy, Rootwin, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
    XftTextExtentsUtf8(Dpy, sessionfont, reinterpret_cast<const XftChar8*>(currsession.c_str()), currsession.length(), &extents);
    msg_x = cfg->getOption("sessiontype_x");
    msg_y = cfg->getOption("sessiontype_y");
    int x = Cfg::absolutepos(msg_x, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.width);
    int y = Cfg::absolutepos(msg_y, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.height);
    if ( x >= 0  &&  y >= 0 ) {
        SlimDrawString8(draw, &sessioncolor, sessionfont, x, y, currsession);
    }
    dastring = cfg->getOption("F1_msg");      //   "Press F1 to change sessiontype"
    msg_x = cfg->getOption("F1_x");
    msg_y = cfg->getOption("F1_y");
    if (dastring != ""  &&  cfg->getIntSesslistSize() > 1 ){
        dafont = XftFontOpenName(Dpy, Scr, cfg->getOption("F1_font").c_str());
        XftTextExtentsUtf8(Dpy, dafont, reinterpret_cast<const XftChar8*>(dastring.c_str()), dastring.length(), &extents);
        x = Cfg::absolutepos(msg_x, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.width);
        y = Cfg::absolutepos(msg_y, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.height);
        if ( x >= 0  &&  y >= 0 )     SlimDrawString8(draw, &F1color, dafont, x, y, dastring);
    }

    PaintTextline( cfg->getOption("cust1_msg"), cfg->getOption("cust1_x"), cfg->getOption("cust1_y"), cfg->getOption("cust1_font"), &cust1color );
    PaintTextline( cfg->getOption("cust2_msg"), cfg->getOption("cust2_x"), cfg->getOption("cust2_y"), cfg->getOption("cust2_font"), &cust2color );
    PaintTextline( cfg->getOption("cust3_msg"), cfg->getOption("cust3_x"), cfg->getOption("cust3_y"), cfg->getOption("cust3_font"), &cust3color);
    PaintTextline( cfg->getOption("cust4_msg"), cfg->getOption("cust4_x"), cfg->getOption("cust4_y"), cfg->getOption("cust4_font"), &cust4color);
    PaintTextline( cfg->getOption("cust5_msg"), cfg->getOption("cust5_x"), cfg->getOption("cust5_y"), cfg->getOption("cust5_font"), &cust5color);
               //   F1_msg is drawn onto the panel, not the background
    PaintTextline( cfg->getOption("F2_msg"), cfg->getOption("F2_x"), cfg->getOption("F2_y"), cfg->getOption("F2_font"), &F2color);
    PaintTextline( cfg->getOption("F3_msg"), cfg->getOption("F3_x"), cfg->getOption("F3_y"), cfg->getOption("F3_font"), &F3color);
    PaintTextline( cfg->getOption("F4_msg"), cfg->getOption("F4_x"), cfg->getOption("F4_y"), cfg->getOption("F4_font"), &F4color);
    PaintTextline( cfg->getOption("F5_msg"), cfg->getOption("F5_x"), cfg->getOption("F5_y"), cfg->getOption("F5_font"), &F5color);
    PaintTextline( cfg->getOption("F6_msg"), cfg->getOption("F6_x"), cfg->getOption("F6_y"), cfg->getOption("F6_font"), &F6color);
    PaintTextline( cfg->getOption("F7_msg"), cfg->getOption("F7_x"), cfg->getOption("F7_y"), cfg->getOption("F7_font"), &F7color);
    PaintTextline( cfg->getOption("F8_msg"), cfg->getOption("F8_x"), cfg->getOption("F8_y"), cfg->getOption("F8_font"), &F8color);
    PaintTextline( cfg->getOption("F9_msg"), cfg->getOption("F9_x"), cfg->getOption("F9_y"), cfg->getOption("F9_font"), &F9color);
    PaintTextline( cfg->getOption("F10_msg"), cfg->getOption("F10_x"), cfg->getOption("F10_y"), cfg->getOption("F10_font"), &F10color);
    PaintTextline( cfg->getOption("F11_msg"), cfg->getOption("F11_x"), cfg->getOption("F11_y"), cfg->getOption("F11_font"), &F11color);
    PaintTextline( cfg->getOption("F12_msg"), cfg->getOption("F12_x"), cfg->getOption("F12_y"), cfg->getOption("F12_font"), &F12color);
    // ----------------------------  TEARDOWN
    XFlush(Dpy);
    //XftFontClose(Dpy, sessionfont);  //    \___  LATE ADDITION
    //XftFontClose(Dpy, dafont);       //    /
    XftDrawDestroy(draw);
}


void Panel::PaintTextline(string& dastring, string& msg_x, string& msg_y, string& wantedfont, XftColor *wantedcolor) {
    XGlyphInfo extents;
    XftDraw *draw = XftDrawCreate(Dpy, Rootwin, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
    if (dastring != ""){
        dafont = XftFontOpenName(Dpy, Scr, wantedfont.c_str());
        if(zeebug)
            cout << "PaintTextLine  " << dastring.c_str() << "     " <<  wantedfont.c_str() << endl;

        XftTextExtentsUtf8(Dpy, dafont, reinterpret_cast<const XftChar8*>(dastring.c_str()), dastring.length(), &extents);
        int x = Cfg::absolutepos(msg_x, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.width);
        int y = Cfg::absolutepos(msg_y, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.height);
        if ( x >= 0  &&  y >= 0 )      SlimDrawString8(draw, wantedcolor, dafont, x, y, dastring);
    }   ///////////////////     howdy   reconsider whether (a USABLE minimum value of) 5 or so should be tested
    //XftFontClose(Dpy, dafont);     //    \___  LATE ADDITION
    //XftDrawDestroy(draw);          //    /
}


void Panel::SlimDrawString8(XftDraw *d, XftColor *color, XftFont *font, int x, int y, const string& str) {
    XftDrawStringUtf8(d, color, font, x, y, reinterpret_cast<const FcChar8*>(str.c_str()), str.length());
}


bool Panel::IsCleanString(const string& s) {
////if ( s.empty() )   return false;      //an empty string is unusable
    if ( s.empty() )   return false;    // necessary for validating string read from lastused file

    for (const char c : s) {
        if ( iscntrl(c) || isspace(c) )   return false;
    }
    return true;
}


Panel::ActionType Panel::getAction(void) const{
    return action;
}

void Panel::ResetName(void){
    NameBuffer.clear();
}

void Panel::ResetPasswd(void){
    PasswdBuffer.clear();
    HiddenPasswdBuffer.clear();
}

void Panel::SetName(const string& name){
    NameBuffer=name;
    action = Login;
}

const string& Panel::GetName(void) const{
    return NameBuffer;
}

const string& Panel::GetPasswd(void) const{
    return PasswdBuffer;
}



void Panel::showMuMsg(string& bess, int duration) {
    Window widdow;
    static const unsigned int border_wid = 1;
    static const char *background_color = "#0d0d0d";   //   howdy bub    consider making this detail configurable
    static const char *border_color = "#ff0000";
    std::string lulu;
    if ( bess == cfg->getOption("forbidden_msg") ) {
         lulu = "#ef0000";
    } else {
         lulu = "#efefef";
    }
    static const char* bessfont_color = lulu.c_str();

    if (duration < 1)    duration = 2;

    Visual *vvisual = DefaultVisual(Dpy, Scr);
    //Colormap colormap = DefaultColormap(Dpy, Scr);

    XSetWindowAttributes attributes;
    attributes.override_redirect = True;
    XftColor ccolor;
    XftColorAllocName(Dpy, vvisual, DefaultColormap(Dpy, Scr), background_color, &ccolor);
    attributes.background_pixel = ccolor.pixel;
    XftColorAllocName(Dpy, vvisual, DefaultColormap(Dpy, Scr), border_color, &ccolor);
    attributes.border_pixel = ccolor.pixel;

    int ckwidth = XWidthOfScreen(ScreenOfDisplay(Dpy, Scr));
    if (testing)       cout << "MuMs ckwidth = " << ckwidth << endl;   //   howdy   nixme

    string foo_details = "";
    if ( ckwidth > 1920 ) {
        foo_details = "Verdana:size=16";  // "Verdana:size=24:bold";     // howdy bub         this begs further finesse
    } else if ( ckwidth < 1024 ) {
        foo_details = "Verdana:size=12";  // "Verdana:size=24:bold";
    } else {
        foo_details = "Verdana:size=14";  // "Verdana:size=24:bold";
    }


    XftFont *foofont = XftFontOpenName(Dpy, Scr, foo_details.c_str());
    //unsigned int text_height = foofont->ascent - foofont->descent;

/**
    int midx = XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)) / (double) 2;
    int midy = XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)) / (double) 2;
    int centTextMidpoint = extents.width / (double) 2;
*/
    widdow = XCreateWindow(Dpy, RootWindow(Dpy, Scr), 60, 360, 980, 198, border_wid, DefaultDepth(Dpy, Scr),
                           CopyFromParent, vvisual, CWOverrideRedirect | CWBackPixel | CWBorderPixel, &attributes);
                           //                          ^--- WITHOUT THIS, DURING TESTING THE WINDOW RECIEVES A WM TITLEBAR

    XftDraw *draw = XftDrawCreate(Dpy, widdow, vvisual, DefaultColormap(Dpy, Scr));
    XftColorAllocName(Dpy, vvisual, DefaultColormap(Dpy, Scr), bessfont_color, &ccolor);

    XSelectInput(Dpy, widdow, ExposureMask );    //  ---------- HOWDY   YES, THIS LINE IS NECESSARY

    XMapWindow(Dpy, widdow);
    XftDrawStringUtf8(draw, &ccolor, foofont, 24, 90, reinterpret_cast<const FcChar8*>(bess.c_str()), bess.length());
    //      ^----------v
    // XftDrawStringUtf8(d, color, font, x, y, reinterpret_cast<const FcChar8*>(str.c_str()), str.length());
    // SlimDrawString8(draw, &F9color, dafont, x, y, dastring);

    XFlush(Dpy);  // ------------------------- late addition
/**
                    The XUnmapWindow() function unmaps the specified window and causes the X server to generate an UnmapNotify event.
                    If the specified window is already unmapped, XUnmapWindow() has no effect. Normal exposure processing
                    on formerly obscured windows is performed. Any child window will no longer be visible until another
                    map call is made on the parent. In other words, the subwindows are still mapped but are not visible
                    until the parent is mapped. Unmapping a window will generate Expose events on windows that were formerly obscured by it.
*/

    //XClearWindow(Dpy, widdow);  //  X Error of failed request:  BadWindow (invalid Window parameter)   Major opcode of failed request:  61 (X_ClearArea)

    sleep(duration);
    XftDrawDestroy(draw);
    XftColorFree(Dpy, vvisual, DefaultColormap(Dpy, Scr), &ccolor);
    XftFontClose(Dpy, foofont);
    XDestroyWindow(Dpy, widdow);
    return;
}



void Panel::readLastUsed(string lufile) {
    string aline;
    bool needfallback = false;
    ifstream afile;
    afile.open(lufile);
    if (afile.is_open()) {     // howdy   if it exists...
        getline(afile, aline);
        afile.close();
        if (!aline.empty()) {
            if ( IsCleanString(aline) ) {
                if (zeebug) {
                    cout << "slimski lastused lookup sez: " <<  aline << endl;
                }

                //           confirm the specified sessiontype is still available
                //  At login, a potentially invalid %sessiontype string may be passed to commandline:
                //    ~~ the window manager associated the name sessiontype has been uninstalled
                //    ~~ the user has imported dotfiles from another system
                //  so test for exact match against the names of currently-available sessiontype names.
                if ( cfg->isValidSesstype(aline) ) {
                    sessiontype = aline; //   set prefilled sessiontype
                } else {
                    if (zeebug) {
                        cout << "The lastused file declares an INVALID sessiontype." << endl;
                        cout << "Will fallback to default_sessiontype, if a valid default has been specified." << endl;
                    }
                    needfallback = true;
                }
            } else
                needfallback = true;
        } else
            needfallback = true;
    } else {
        if (zeebug)     cout << "Cannot read slimski.lastused file: " << lufile << endl;

        needfallback = true;
    }

    if (needfallback  &&  cfg->isValidSesstype(cfg->getOption("default_sessiontype")) )
    {
        sessiontype = cfg->getOption("default_sessiontype");
    } else {
        //  a further fallback, handled elsewhere, applies the first listed name specified  in "sessiontypes" line within slimski.conf
        //   BUT whatif the default no longer has a  matching xsessiondir item ?
        if ( cfg->getOption("xsessionsdir") != "")
            sessiontype = cfg->dacurrentSessiontype();
    }
}
