asp.net - CutyCapt not able to generate HTTPS web site screenshot when launched from w3wp.exe -


i have updated latest code svn , complie cutycapt qt sdk 4.8. , have static-linked libeay32.dll / ssleay32.dll in same location of binary.

it works fine if launch cutycapt process in command window.

it works fine if launch cutycapt process asp.net (w3wp) capture http web page.

it not work if launch cutycapt process asp.net (w3wp) capture https web page, generates blank image.

i doubted because libeay32.dll / ssleay32.dll dependencies not loaded, copied these 2 dll system32 / syswow64, not resolve problem. monitor process processmonitor, shows me libeay32.dll / ssleay32.dll loaded successfully. so, dependencies not reason.

here c# code launch cutycapt,

using (process process = new process()) {     process.startinfo.filename = ti.exefile;     process.startinfo.errordialog = false;     process.startinfo.createnowindow = true;     process.startinfo.windowstyle = processwindowstyle.hidden;     process.startinfo.workingdirectory = path.getdirectoryname(ti.exefile);     process.startinfo.useshellexecute = false;     process.startinfo.arguments = string.format(@"--min-width=1024 --delay=1500 --javascript=off --auto-load-images=on --plugins=off --java=off --url={0} --out={1}"         , ti.url         , destfile         );     process.start();      process.waitforexit();     process.close(); }  

does knows how allow cutycapt works w3wp?

solution: have resolved problem modifying cutycapt source. idea that, html of url set html webkit.

cutycapt.hpp

#include <qtwebkit>  class cutycapt; class cutypage : public qwebpage {   q_object  public:   void setattribute(qwebsettings::webattribute option, const qstring& value);   void setuseragent(const qstring& useragent);   void setalertstring(const qstring& alertstring);   void setprintalerts(bool printalerts);   void setcutycapt(cutycapt* cutycapt);   qstring getalertstring();  protected:   qstring choosefile(qwebframe *frame, const qstring& suggestedfile);   void javascriptconsolemessage(const qstring& message, int linenumber, const qstring& sourceid);   bool javascriptprompt(qwebframe* frame, const qstring& msg, const qstring& defaultvalue, qstring* result);   void javascriptalert(qwebframe* frame, const qstring& msg);   bool javascriptconfirm(qwebframe* frame, const qstring& msg);   qstring useragentforurl(const qurl& url) const;   qstring museragent;   qstring malertstring;   bool mprintalerts;   cutycapt* mcutycapt; };  class cutycapt : public qobject {   q_object  public:     qstring      murl;   // todo: should elsewhere , named differently   enum outputformat { svgformat, pdfformat, psformat, innertextformat, htmlformat,     rendertreeformat, pngformat, jpegformat, mngformat, tiffformat, gifformat,     bmpformat, ppmformat, xbmformat, xpmformat, otherformat };    cutycapt(cutypage* page,            const qstring& output,            int delay,            outputformat format,            const qstring& scriptprop,            const qstring& scriptcode);  private slots:   void htmldownloadfinished(qnetworkreply * reply);   void htmldownloaderror(qnetworkreply::networkerror code);   void documentcomplete(bool ok);   void initiallayoutcompleted();   void javascriptwindowobjectcleared();   void timeout();   void delayed();  private:   void trydelayedrender();   void savesnapshot();   bool msawinitiallayout;   bool msawdocumentcomplete;  protected:   qstring      moutput;   int          mdelay;   cutypage*    mpage;   outputformat mformat;   qobject*     mscriptobj;   qstring      mscriptprop;   qstring      mscriptcode;  }; 

cutycapt.cpp

//////////////////////////////////////////////////////////////////// // // cutycapt - qt webkit web page rendering capture utility // // copyright (c) 2003-2010 bjoern hoehrmann <bjoern@hoehrmann.de> // // program free software; can redistribute and/or // modify under terms of gnu general public license // published free software foundation; either version 2 // of license, or (at option) later version. // // program free software; can redistribute and/or // modify under terms of gnu lesser general public // license published free software foundation; either // version 2.1 of license, or (at option) later version. // // program distributed in hope useful, // without warranty; without implied warranty of // merchantability or fitness particular purpose.  see // gnu general public license more details. // // $id: cutycapt.cpp 7 2011-11-30 10:56:34z hoehrmann $ // ////////////////////////////////////////////////////////////////////  #include <qapplication> #include <qtwebkit> #include <qtgui> #include <qsvggenerator> #include <qprinter> #include <qtimer> #include <qbytearray> #include <qnetworkrequest> #include <qtcore> #include "cutycapt.hpp"    #if qt_version >= 0x040600 && 0 #define cutycapt_script 1 #endif  #ifdef static_plugins   q_import_plugin(qjpeg)   q_import_plugin(qgif)   q_import_plugin(qtiff)   q_import_plugin(qsvg)   q_import_plugin(qmng)   q_import_plugin(qico) #endif  static struct _cutyextmap {   cutycapt::outputformat id;   const char* extension;   const char* identifier; } const cutyextmap[] = {   { cutycapt::svgformat,         ".svg",        "svg"   },   { cutycapt::pdfformat,         ".pdf",        "pdf"   },   { cutycapt::psformat,          ".ps",         "ps"    },   { cutycapt::innertextformat,   ".txt",        "itext" },   { cutycapt::htmlformat,        ".html",       "html"  },   { cutycapt::rendertreeformat,  ".rtree",      "rtree" },   { cutycapt::jpegformat,        ".jpeg",       "jpeg"  },   { cutycapt::pngformat,         ".png",        "png"   },   { cutycapt::mngformat,         ".mng",        "mng"   },   { cutycapt::tiffformat,        ".tiff",       "tiff"  },   { cutycapt::gifformat,         ".gif",        "gif"   },   { cutycapt::bmpformat,         ".bmp",        "bmp"   },   { cutycapt::ppmformat,         ".ppm",        "ppm"   },   { cutycapt::xbmformat,         ".xbm",        "xbm"   },   { cutycapt::xpmformat,         ".xpm",        "xpm"   },   { cutycapt::otherformat,       "",            ""      } };  qstring cutypage::choosefile(qwebframe* /*frame*/, const qstring& /*suggestedfile*/) {   return qstring::null; }  bool cutypage::javascriptconfirm(qwebframe* /*frame*/, const qstring& /*msg*/) {   return true; }  bool cutypage::javascriptprompt(qwebframe* /*frame*/,                            const qstring& /*msg*/,                            const qstring& /*defaultvalue*/,                            qstring* /*result*/) {   return true; }  void cutypage::javascriptconsolemessage(const qstring& /*message*/,                                    int /*linenumber*/,                                    const qstring& /*sourceid*/) {   // noop }  void cutypage::javascriptalert(qwebframe* /*frame*/, const qstring& msg) {    if (mprintalerts)     qdebug() << "[alert]" << msg;    if (malertstring == msg) {     qtimer::singleshot(10, mcutycapt, slot(delayed()));   } }  qstring cutypage::useragentforurl(const qurl& url) const {    if (!museragent.isnull())     return museragent;    return qwebpage::useragentforurl(url); }  void cutypage::setuseragent(const qstring& useragent) {   museragent = useragent; }  void cutypage::setalertstring(const qstring& alertstring) {   malertstring = alertstring; }  qstring cutypage::getalertstring() {   return malertstring; }  void cutypage::setcutycapt(cutycapt* cutycapt) {   mcutycapt = cutycapt; }  void cutypage::setprintalerts(bool printalerts) {   mprintalerts = printalerts; }  void cutypage::setattribute(qwebsettings::webattribute option,                        const qstring& value) {    if (value == "on")     settings()->setattribute(option, true);   else if (value == "off")     settings()->setattribute(option, false);   else     (void)0; // todo: ... }  // todo: consider merging of main() , cutycap    cutycapt::cutycapt(cutypage* page, const qstring& output, int delay, outputformat format,                    const qstring& scriptprop, const qstring& scriptcode) {   mpage = page;   moutput = output;   mdelay = delay;   msawinitiallayout = false;   msawdocumentcomplete = false;   mformat = format;   mscriptprop = scriptprop;   mscriptcode = scriptcode;   mscriptobj = new qobject();    // not nice, restructuring work   // needed anyway, should not bad now.   mpage->setcutycapt(this); }  void cutycapt::initiallayoutcompleted() {   msawinitiallayout = true;    if (msawinitiallayout && msawdocumentcomplete)     trydelayedrender(); }  void cutycapt::documentcomplete(bool /*ok*/) {   msawdocumentcomplete = true;   if (msawinitiallayout && msawdocumentcomplete)     trydelayedrender(); }  void cutycapt::javascriptwindowobjectcleared() {    if (!mscriptprop.isempty()) {     qvariant var = mpage->mainframe()->evaluatejavascript(mscriptprop);     qobject* obj = var.value<qobject*>();      if (obj == mscriptobj)       return;      mpage->mainframe()->addtojavascriptwindowobject(mscriptprop, mscriptobj);   }    mpage->mainframe()->evaluatejavascript(mscriptcode);  }   void cutycapt::htmldownloadfinished(qnetworkreply * reply) {       qvariant statuscode = reply->attribute( qnetworkrequest::httpstatuscodeattribute );     qdebug() << statuscode;     if ( !statuscode.isvalid() )     {         qapplication::exit();         return;     }      int status = statuscode.toint();      if( status == 301 || status == 302 )     {         qvariant loc = reply->header(qnetworkrequest::locationheader);         qdebug() << "location :" << loc.tostring();         qapplication::exit();         return;     }      if ( status != 200 )     {         qstring reason = reply->attribute( qnetworkrequest::httpreasonphraseattribute ).tostring();         qdebug() << reason;         qapplication::exit();         return;     }      qstring html=qstring::fromutf8(reply->readall());     mpage->mainframe()->sethtml( html, qurl(murl) ); }  void cutycapt::htmldownloaderror(qnetworkreply::networkerror code) {     qapplication::exit(); }  void cutycapt::trydelayedrender() {    if (!mpage->getalertstring().isempty())     return;    if (mdelay > 0) {     qtimer::singleshot(mdelay, this, slot(delayed()));     return;   }    savesnapshot();   qapplication::exit(); }  void cutycapt::timeout() {   savesnapshot();   qapplication::exit(); }  void cutycapt::delayed() {   savesnapshot();   qapplication::exit(); }  void cutycapt::savesnapshot() {   qwebframe *mainframe = mpage->mainframe();   qpainter painter;   const char* format = null;    (int ix = 0; cutyextmap[ix].id != otherformat; ++ix)     if (cutyextmap[ix].id == mformat)       format = cutyextmap[ix].identifier; //, break;    // todo: contents/viewport can have size 0x0   // in case saving them fail.   // result of method being called early.   // far i've been unable find workaround, except   // using --delay substantial wait time. i've   // tried resize multiple time, make fake render,   // check other events... problem   // under ubuntu virtual machine.    mpage->setviewportsize( mainframe->contentssize() );    switch (mformat) {     case svgformat: {       qsvggenerator svg;       svg.setfilename(moutput);       svg.setsize(mpage->viewportsize());       painter.begin(&svg);       mainframe->render(&painter);       painter.end();       break;     }     case pdfformat:     case psformat: {       qprinter printer;       printer.setpagesize(qprinter::a4);       printer.setoutputfilename(moutput);       // todo: change quality here?       mainframe->print(&printer);       break;     }     case rendertreeformat:     case innertextformat:     case htmlformat: {       qfile file(moutput);       file.open(qiodevice::writeonly | qiodevice::text);       qtextstream s(&file);       s.setcodec("utf-8");       s << (mformat == rendertreeformat ? mainframe->rendertreedump() :             mformat == innertextformat  ? mainframe->toplaintext() :             mformat == htmlformat       ? mainframe->tohtml() :             "bug");       break;     }     default: {       qimage image(mpage->viewportsize(), qimage::format_argb32);       painter.begin(&image);       mainframe->render(&painter);       painter.end();       // todo: add quality       image.save(moutput, format);     }   }; }    void capthelp(void) {   printf("%s",     " -----------------------------------------------------------------------------\n"     " usage: cutycapt --url=http://www.example.org/ --out=localfile.png            \n"     " -----------------------------------------------------------------------------\n"     "  --help                         print page , exit                \n"     "  --url=<url>                    url capture (http:...|file:...|...)   \n"     "  --out=<path>                   target file (.png|pdf|ps|svg|jpeg|...)   \n"     "  --out-format=<f>               extension in --out, overrides heuristic \n" //  "  --out-quality=<int>            output format quality 1 100          \n"     "  --min-width=<int>              minimal width image (default: 800)   \n"     "  --min-height=<int>             minimal height image (default: 600)  \n"     "  --max-wait=<ms>                don't wait more (default: 90000, inf: 0)\n"     "  --delay=<ms>                   after successful load, wait (default: 0)     \n" //  "  --user-styles=<url>            location of user style sheet (deprecated)    \n"     "  --user-style-path=<path>       location of user style sheet file, if    \n"     "  --user-style-string=<css>      user style rules specified text           \n"     "  --header=<name>:<value>        request header; repeatable; can't set\n"     "  --method=<get|post|put>        specifies request method (default: get)  \n"     "  --body-string=<string>         unencoded request body (default: none)       \n"     "  --body-base64=<base64>         base64-encoded request body (default: none)  \n"     "  --app-name=<name>              appname used in user-agent; default none  \n"     "  --app-version=<version>        appvers used in user-agent; default none  \n"     "  --user-agent=<string>          override user-agent header qt set  \n"     "  --javascript=<on|off>          javascript execution (default: on)           \n"     "  --java=<on|off>                java execution (default: unknown)            \n"     "  --plugins=<on|off>             plugin execution (default: unknown)          \n"     "  --private-browsing=<on|off>    private browsing (default: unknown)          \n"     "  --auto-load-images=<on|off>    automatic image loading (default: on)        \n"     "  --js-can-open-windows=<on|off> script can open windows? (default: unknown)  \n"     "  --js-can-access-clipboard=<on|off> script clipboard privs (default: unknown)\n" #if qt_version >= 0x040500     "  --print-backgrounds=<on|off>   backgrounds in pdf/ps output (default: off)  \n"     "  --zoom-factor=<float>          page zoom factor (default: no zooming)       \n"     "  --zoom-text-only=<on|off>      whether zoom text (default: off) \n"     "  --http-proxy=<url>             address http proxy server (default: none)\n" #endif #if cutycapt_script     "  --inject-script=<path>         javascript injected pages  \n"     "  --script-object=<string>       property hold state injected script   \n"     "  --expect-alert=<string>        try waiting alert(string) before capture \n"     "  --debug-print-alerts           prints out alert(...) strings debugging. \n" #endif     " -----------------------------------------------------------------------------\n"     "  <f> svg,ps,pdf,itext,html,rtree,png,jpeg,mng,tiff,gif,bmp,ppm,xbm,xpm    \n"     " -----------------------------------------------------------------------------\n" #if cutycapt_script     " `inject-script` option can used inject script code loaded web \n"     " pages. code called whenever `javascriptwindowobjectcleared` signal\n"     " received. when `script-object` set, object under specified name \n"     " available script maintain state across page loads. when the\n"     " `expect-alert` option specified, shot taken when script in- \n"     " vokes alert(string) string if happens before `max-wait`. these \n"     " options allow remote control browser , web page.\n"     " experimental , abused , misused feature. use caution.\n"     " -----------------------------------------------------------------------------\n" #endif     " http://cutycapt.sf.net - (c) 2003-2010 bjoern hoehrmann - bjoern@hoehrmann.de\n"     ""); }  int main(int argc, char *argv[]) {    int arghelp = 0;   int argdelay = 0;   int argsilent = 0;   int argminwidth = 800;   int argminheight = 600;   int argmaxwait = 90000;   int argverbosity = 0;    const char* argurl = null;   const char* arguserstyle = null;   const char* arguserstylepath = null;   const char* arguserstylestring = null;   const char* argicondbpath = null;   const char* arginjectscript = null;   const char* argscriptobject = null;   qstring argout;    cutycapt::outputformat format = cutycapt::otherformat;    qapplication app(argc, argv, true);   cutypage page;    qnetworkaccessmanager::operation method =     qnetworkaccessmanager::getoperation;   qbytearray body;   qnetworkrequest req;   qnetworkaccessmanager manager;    // parse command line parameters   (int ax = 1; ax < argc; ++ax) {     size_t nlen;      const char* s = argv[ax];     const char* value;      // boolean options     if (strcmp("--silent", s) == 0) {       argsilent = 1;       continue;      } else if (strcmp("--help", s) == 0) {       arghelp = 1;       break;      } else if (strcmp("--verbose", s) == 0) {       argverbosity++;       continue;  #if cutycapt_script     } else if (strcmp("--debug-print-alerts", s) == 0) {       page.setprintalerts(true);       continue; #endif     }       value = strchr(s, '=');      if (value == null) {       // todo: error       arghelp = 1;       break;     }      nlen = value++ - s;      // --name=value options     if (strncmp("--url", s, nlen) == 0) {       argurl = value;      } else if (strncmp("--min-width", s, nlen) == 0) {       // todo: add error checking here?       argminwidth = (unsigned int)atoi(value);      } else if (strncmp("--min-height", s, nlen) == 0) {       // todo: add error checking here?       argminheight = (unsigned int)atoi(value);      } else if (strncmp("--delay", s, nlen) == 0) {       // todo: see above       argdelay = (unsigned int)atoi(value);      } else if (strncmp("--max-wait", s, nlen) == 0) {       // todo: see above       argmaxwait = (unsigned int)atoi(value);      } else if (strncmp("--out", s, nlen) == 0) {       argout = value;        if (format == cutycapt::otherformat)         (int ix = 0; cutyextmap[ix].id != cutycapt::otherformat; ++ix)           if (argout.endswith(cutyextmap[ix].extension))             format = cutyextmap[ix].id; //, break;      } else if (strncmp("--user-styles", s, nlen) == 0) {       // option provided backwards-compatibility       arguserstyle = value;      } else if (strncmp("--user-style-path", s, nlen) == 0) {       arguserstylepath = value;      } else if (strncmp("--user-style-string", s, nlen) == 0) {       arguserstylestring = value;      } else if (strncmp("--icon-database-path", s, nlen) == 0) {       argicondbpath = value;      } else if (strncmp("--auto-load-images", s, nlen) == 0) {       page.setattribute(qwebsettings::autoloadimages, value);      } else if (strncmp("--javascript", s, nlen) == 0) {       page.setattribute(qwebsettings::javascriptenabled, value);      } else if (strncmp("--java", s, nlen) == 0) {       page.setattribute(qwebsettings::javaenabled, value);      } else if (strncmp("--plugins", s, nlen) == 0) {       page.setattribute(qwebsettings::pluginsenabled, value);      } else if (strncmp("--private-browsing", s, nlen) == 0) {       page.setattribute(qwebsettings::privatebrowsingenabled, value);      } else if (strncmp("--js-can-open-windows", s, nlen) == 0) {       page.setattribute(qwebsettings::javascriptcanopenwindows, value);      } else if (strncmp("--js-can-access-clipboard", s, nlen) == 0) {       page.setattribute(qwebsettings::javascriptcanaccessclipboard, value);      } else if (strncmp("--developer-extras", s, nlen) == 0) {       page.setattribute(qwebsettings::developerextrasenabled, value);      } else if (strncmp("--links-included-in-focus-chain", s, nlen) == 0) {       page.setattribute(qwebsettings::linksincludedinfocuschain, value);  #if qt_version >= 0x040500     } else if (strncmp("--print-backgrounds", s, nlen) == 0) {       page.setattribute(qwebsettings::printelementbackgrounds, value);      } else if (strncmp("--zoom-factor", s, nlen) == 0) {       page.mainframe()->setzoomfactor(qstring(value).tofloat());      } else if (strncmp("--zoom-text-only", s, nlen) == 0) {       page.setattribute(qwebsettings::zoomtextonly, value);      } else if (strncmp("--http-proxy", s, nlen) == 0) {       qurl p = qurl::fromencoded(value);       qnetworkproxy proxy = qnetworkproxy(qnetworkproxy::httpproxy,         p.host(), p.port(80), p.username(), p.password());       manager.setproxy(proxy);       page.setnetworkaccessmanager(&manager); #endif  #if cutycapt_script     } else if (strncmp("--inject-script", s, nlen) == 0) {       arginjectscript = value;      } else if (strncmp("--script-object", s, nlen) == 0) {       argscriptobject = value;      } else if (strncmp("--expect-alert", s, nlen) == 0) {       page.setalertstring(value); #endif      } else if (strncmp("--app-name", s, nlen) == 0) {       app.setapplicationname(value);      } else if (strncmp("--app-version", s, nlen) == 0) {       app.setapplicationversion(value);      } else if (strncmp("--body-base64", s, nlen) == 0) {       body = qbytearray::frombase64(value);      } else if (strncmp("--body-string", s, nlen) == 0) {       body = qbytearray(value);      } else if (strncmp("--user-agent", s, nlen) == 0) {       page.setuseragent(value);      } else if (strncmp("--out-format", s, nlen) == 0) {       (int ix = 0; cutyextmap[ix].id != cutycapt::otherformat; ++ix)         if (strcmp(value, cutyextmap[ix].identifier) == 0)           format = cutyextmap[ix].id; //, break;        if (format == cutycapt::otherformat) {         // todo: error         arghelp = 1;         break;       }      } else if (strncmp("--header", s, nlen) == 0) {       const char* hv = strchr(value, ':');        if (hv == null) {         // todo: error         arghelp = 1;         break;       }        req.setrawheader(qbytearray(value, hv - value), hv + 1);      } else if (strncmp("--method", s, nlen) == 0) {       if (strcmp("value", "get") == 0)         method = qnetworkaccessmanager::getoperation;       else if (strcmp("value", "put") == 0)         method = qnetworkaccessmanager::putoperation;       else if (strcmp("value", "post") == 0)         method = qnetworkaccessmanager::postoperation;       else if (strcmp("value", "head") == 0)         method = qnetworkaccessmanager::headoperation;       else          (void)0; // todo: ...      } else {       // todo: error       arghelp = 1;     }   }    if (argurl == null || argout == null || arghelp) {       capthelp();       return exit_failure;   }    // used use qurl(argurl) escapes %hh sequences   // though should not, urls can assumed escaped.   req.seturl( qurl::fromencoded(argurl) );    qstring scriptprop(argscriptobject);   qstring scriptcode;    if (arginjectscript) {     qfile file(arginjectscript);     if (file.open(qiodevice::readonly)) {       qtextstream stream(&file);       stream.setcodec(qtextcodec::codecforname("utf-8"));       stream.setautodetectunicode(true);       scriptcode = stream.readall();       file.close();     }   }     cutycapt main(&page, argout, argdelay, format, scriptprop, scriptcode);     main.murl = argurl;    app.connect(&page,     signal(loadfinished(bool)),     &main,     slot(documentcomplete(bool)));    app.connect(page.mainframe(),     signal(initiallayoutcompleted()),     &main,     slot(initiallayoutcompleted()));    if (argmaxwait > 0) {     // todo: should register 1 application?     qtimer::singleshot(argmaxwait, &main, slot(timeout()));   }    if (arguserstyle != null)     // todo: need syntax checking?     page.settings()->setuserstylesheeturl( qurl::fromencoded(arguserstyle) );    if (arguserstylepath != null) {     page.settings()->setuserstylesheeturl( qurl::fromlocalfile(arguserstylepath) );   }    if (arguserstylestring != null) {     qurl data("data:text/css;charset=utf-8;base64," +       qbytearray(arguserstylestring).tobase64());     page.settings()->setuserstylesheeturl( data );   }    if (argicondbpath != null)     // todo: need syntax checking?     page.settings()->seticondatabasepath(arguserstyle);    // documentation not say, seems mainframe   // never change, can set here. otherwise we'd   // have set in snapshot , trigger update,   // not possible (qt 4.4.0) far can tell.   page.mainframe()->setscrollbarpolicy(qt::horizontal, qt::scrollbaralwaysoff);   page.mainframe()->setscrollbarpolicy(qt::vertical, qt::scrollbaralwaysoff);   page.setviewportsize( qsize(argminwidth, argminheight) );  #if cutycapt_script   // javascriptwindowobjectcleared not called on   // initial load unless javascript has been executed.   page.mainframe()->evaluatejavascript(qstring(""));    app.connect(page.mainframe(),     signal(javascriptwindowobjectcleared()),     &main,     slot(javascriptwindowobjectcleared())); #endif       app.connect( &manager,signal(finished(qnetworkreply *)), &main,slot(htmldownloadfinished(qnetworkreply *)));     qnetworkreply *reply = manager.get(req);     app.connect( reply,signal(error(qnetworkreply::networkerror)), &main,slot(htmldownloaderror(qnetworkreply::networkerror)));      return app.exec(); } 

Comments

Popular posts from this blog

jquery - How can I dynamically add a browser tab? -

node.js - Getting the socket id,user id pair of a logged in user(s) -

keyboard - C++ GetAsyncKeyState alternative -