[Greasemonkey] GM_sendKeys

csaba2000 csaba2000 at yahoo.com
Sun Apr 9 14:11:44 EDT 2006


I wrote the code below to implement GM_sendKeys(charStream, targetElem,
repeatCount[=1], initialDelay[=0], pauseBetweenKeyEvents[=0],
DispatchKeyDownKeyUpEvents[=0], oAdditionalEventProperties[={}]);

Simple usage examples from GM scripts:
To select the last word in a text input: 
GM_sendKeys("^{end}^+{left}",textElem);
Insert HI Mom at the beginning:  GM_sendKeys("^{home}+{hi
space}+mom",textareaElem);
Pull down a select element:  GM_sendKeys("%{down}",selectElem);

Warnings and caveats:
This is an alpha version function.  While I don't know of errors in the
code, I would be surprised if there weren't any since several sections have
not been exercised/tested.  Although my tests are inconclusive, I did not
find a way to get from GM_sendKeys to the disk.  What I mean by that is that
it is possible to activate dialogs (such as File/Save As or Tools/Options),
but I could not use GM_sendKeys past that point.  Even with the alert
message mox.  This is a good thing.  For my tastes, there is only one thing
missing from GM_sendKeys and that is that I have not figured out how to get
FF to process the {tab} key so that I can have the focus moved.  If someone
can figure that out, that would be great.

As to why GM_sendKeys is needed in the first place... Turns out that FF will
allow you to dispatch KeyEvents and your event handlers will catch them, but
FF won't actually carry out the action associated with the key or character
that you wanted to simulate.  They mumble something about security, but I'm
not buying without an explanation - as long as the keystrokes are carrying
out actions on the document in memory that could be achieved in other ways
by script I don't see the problem.  There's even a bug filed on it, but I'm
skeptical.  That means, that until and unless FF relaxes their security
model, sendKeys must live in privileged script.

Hmm, I haven't really justified sendKeys there, just putting it into GM. 
The thing is that there are some bugs in FF, notably with Textareas and the
ONLY way that I found so far to deal with these bugs is by means of
GM_sendKeys.  Basically, GM_sendKeys allows scripts to make determination of
things that are otherwise not possible from script, but are possible from
the keyboard.  Such as pulling down the menu on a select element.  Such as
determining the selection direction within a text element or textarea.  Or
setting a reverse selection, for that matter.  Etc.

Installation:
Since GM_sendKeys is not part of GM, the installation must be done manually. 
There are two changes that must be made to one file.  Mess up this procedure
and none of your scripts will work.  So make a backup copy.

Find GreaseMonkey.js.  On my Win 2K pro system, if I go to the directory
where my greasemonkey scripts reside, then go up 2 levels, then go into the
extensions directory I see a bunch of horribly long identifiers.  On my
system the one for GM is {e4a8a97b-f2ed-450b-b12d-ee082ba24781} and then I
go into the components subdirectory below that.

Ensure that FF is not running since GreaseMonkey.js is only read when FF
starts up.  Look for the only occurrence of sandbox.GM_getValue.  Right
underneath that add the following one line:
    sandbox.GM_sendKeys = GM_hitch(this, "sendKeys", sandbox);

That links GM_sendKeys on the outside to the internal sendKeys function.

Part 2 is to add in the internal sendKeys function.  A little bit below the
line that was just added, find the following:
  registerMenuCommand: function (
Find the end of the function definition, 12 lines down, indented the same as
registerMenuCommand.  On that line you should see only:
  },

That comma is important!

Below that comma, make sure there are a couple of blank lines.  We're going
to add sendKeys in the same fashion between two of those blank lines.  All
you should have to do is cut and paste the code provided below my signature,
except that I'm not too sanguine about wrapping.  Well, I didn't see a way
to add an attachment to this mailing, but I expect that if the wrapping
gives you trouble, you might not want to fiddle with this in the first
place.  Remember, the entire function (starting from sendKeys: function
(...) and the terminating comma (,) must be copied or GM will die on the
vine.

Csaba Gabor from Vienna


  sendKeys: function (mysandbox, keys, target, repeat, delay, pause, downUp,
oEvt) {
  /*
      this will send the keys to FF, targeted toward target
      format for keys is a string of chars, each char gets sent
individually.  Certain chars have special meaning:
      +, ^, %, and # signify shift, control, alt, meta, unless they are
preceded by a backslash (\)
      \ preceding any of +, ^, %, #, { or \ will cause the following char to
not be special.
      \ in front of a non special character (ie. those on the above line) is
not special
      some keys (besides shift, control, alt, meta) do not cause a
"printable" character to be output.
      Their equivalents may be entered as comma or space separated values
within {}
        These equivalents may be (used as {equivalent1,equivalent2
equivalent3...}):
          1. Text name of the equivalent.  See
http://lxr.mozilla.org/mozilla/source/dom/public/idl/events/nsIDOMKeyEvent.idl#40
          2. a number with more than 1 digit (possibly starting with 0),
representing the key code (not character code)
          3. a hex value of the form 0xH... with at least one digit,
representing the key code (not character code)
             2a, 3a.  If either case 2 or 3 is prefixed by a minus, no
translation is done and the negative of the value is sent out
          4. Anything else is parsed one character at a time, if 2 or 3
apply at any position, they are applied
        There are no special values within {}, except that } terminates.
      If oEvt, is provided each property is attached to the event
      first keystroke goes out after delay, with pause milliseconds between
each key event
      The characters/keys specified in keys are sent out as keypress events
- this is what FF mostly cares about.
        However, in some cases one may wish to send out the accompanying
keydown and/or keyup event
  */
    String.prototype.trim = function() { return this.replace(/^\s+/,
"").replace(/\s+$/, ""); }
    function dispatchChar(char, target, wait, mods, oEvt) {
      var i, ev = mysandbox.document.createEvent ('KeyEvents');
      ev.initKeyEvent('keypress', true, true, mysandbox.window, mods & 1,
mods & 2, mods & 4, mods & 8, 0, char.charCodeAt(0) );
      if (oEvt) for (i in oEvt) ev[i]=oEvt[i];
      if (!wait) target.dispatchEvent(ev); else
      mysandbox.window.setTimeout(function(tgt,ev){return function() {
tgt.dispatchEvent(ev) }}(target,ev), wait); }
    function dispatchKey (key, target, wait, pause, mods, downUp, oEvt) {
      var i, ev = mysandbox.document.createEvent ('KeyEvents');
      if (downUp & 1) {		// 1 => down, 2 => up
        ev.initKeyEvent('keydown', true, true, mysandbox.window, mods & 1,
mods & 2, mods & 4, mods & 8, (key<0) ? -key : key, 0 );
        if (oEvt) for (i in oEvt) ev[i]=oEvt[i];
        if (!wait) target.dispatchEvent(ev); else
        mysandbox.window.setTimeout(function(tgt,ev){return function() {
tgt.dispatchEvent(ev) }}(target,ev), wait);
        wait += pause; }

      // now we do the main event (so to speak)
      var skip=downUp & 4;
      // the keys below did not a keypress event
      var oSkip = {0x10:1, 0x11:1, 0x12:1, 0x14:1, 0x2C:1, 0x90:1, 0x91:1,
0xE0:1}  // shift, control, alt, caps_lock, prtsc, num_lock, scroll_lock,
meta
      var oXlate = {0x6B:0x2B, 0x6D:0x2D, 0x6A:0x2A, 0x6F:0x2F }
      if (key<0) { ev.initKeyEvent('keypress', true, true, mysandbox.window,
mods & 1, mods & 2, mods & 4, mods & 8, -key, 0 ); }
      else if (oSkip[key]) { skip=true; }
      else if (oXlate[key]) { ev.initKeyEvent('keypress', true, true,
mysandbox.window, mods & 1, mods & 2, mods & 4, mods & 8, 0, oXlate[key] );
}
      else if (key>=0x60 && key<=0x69) {ev.initKeyEvent('keypress', true,
true, mysandbox.window, mods & 1, mods & 2, mods & 4, mods & 8, 0, key-0x30
); }
      else if (key>=0x41 && key<=0x5A) {
        ev.initKeyEvent('keypress', true, true, mysandbox.window, mods & 1,
mods & 2, mods & 4, mods & 8, 0, key+(!(mods&4))*0x20 ); }
      else if (key>=0x30 && key<=0x39) {ev.initKeyEvent('keypress', true,
true, mysandbox.window, mods & 1, mods & 2, mods & 4, mods & 8, 0, key ); }
      else { ev.initKeyEvent('keypress', true, true, mysandbox.window, mods
& 1, mods & 2, mods & 4, mods & 8, key, 0 ); }
      if (!skip) {
        if (oEvt) for (i in oEvt) ev[i]=oEvt[i];
        if (!wait) target.dispatchEvent(ev); else
        mysandbox.window.setTimeout(function(tgt,ev){return function() {
tgt.dispatchEvent(ev) }}(target,ev), wait);
        wait += pause; }

      if (downUp & 2) {		// 1 => down, 2 => up
        ev.initKeyEvent('keyup', true, true, mysandbox.window, mods & 1,
mods & 2, mods & 4, mods & 8, (key<0) ? -key : key, 0 );
        if (oEvt) for (i in oEvt) ev[i]=oEvt[i];
        if (!wait) target.dispatchEvent(ev); else
        mysandbox.window.setTimeout(function(tgt,ev){return function() {
tgt.dispatchEvent(ev) }}(target,ev), wait);
        wait += pause; }
      return wait;
    }

    if (!downUp) downUp = 0;			// default is to only send key presses
    if (!pause) pause = 0;			// default 0 milliseconds between dispatching
keystrokes
    if (!delay) delay = 0;			// first keystroke dispatched immediately
    if (arguments.length<4) repeat = 1;		// default is no repeats

    var braceR = "", braceLev, subBrace;
    var char, char2, idx, reVal;
    var oSpecial = {'+':4, '^':1, '%':2, '#': 8, "\\":16, "{":"}"}        //
+=shift, ^=control, %=alt, #=meta
    var oVirtual = {cancel:0x03, help:0x06, back_space:0x08, tab:0x09,
clear:0x0C, 'return':0x0D, enter:0x0E,
                    shift:0x10, control:0x11, alt:0x12, pause:0x13,
caps_lock:0x14, escape:0x1B, space:0x20,
                    page_up:0x21, page_down:0x22, end:0x23, home:0x24,
left:0x25, up:0x26, right:0x27, down:0x28,
                    printscreen:0x2C, insert:0x2D, 'delete':0x2E,
semicolon:0x3B, equals:0x3D, context_menu:0x5D,
                    multiply:0x6A, add:0x6B, separator:0x6C, subtract:0x6D,
decimal:0x6E, divide:0x6F,
                    num_lock:0x90, scroll_lock:0x91, comma:0xBC,
period:0xBE, slash:0xBF,
                    back_quote:0xC0, open_bracket:0xDB, back_slash:0xDC,
close_bracket:0xDD, quote:0xDE, meta:0xE0,
                    del:0x2E, bksp:0x08, prtsc:0x2C, ins:0x2D}
    
    for (var i=0;i<10;++i) oVirtual["numpad"+i] = 0x60+i;
    for (i=i;i<25;++i) oVirtual["F"+i] = 0x6F+i;
    keys0=keys;
    for (;repeat--;) {
      keys = keys0;
      var oScam = {0:0};	// shift, control, alt, meta
      while (keys) {
        char=keys.charAt(0);
        if (braceR) {    // very inefficient, cause keep reevaluating the
RegExp, especially the 2nd
          var eatenCt, keyVal, rV;
          if (char==braceR) {	// we are terminating this particular {} bit
            braceR=""; keys = keys.substr(1);
            // need to output the modifier keys
            if ((downUp & 2) && oScam[0]) {   // we have to back out the
modifier keys, so need to reverse the object
              var aTmp=[];
              var scam=oScam[0];
              for (idx in oScam) if (idx) aTmp[aTmp.length] = idx;
	      for (idx=aTmp.length;i--;) {
		if (scam & oSpecial[aTmp[idx]]) scam = scam ^ oSpecial[aTmp[idx]];   //
strip a bit
                delay = dispatchKey(oVirtual[aTmp[idx]], target, delay,
pause, scam, 2); } }
            oScam={0:0};
            continue;
          }
          if (keys.match(/^[,\s]+/)) { keys =
keys.substr(RegExp.lastMatch.length); braceLev=0; continue; }
          keys.match(/^((\d{2})|(-?0x([0-9a-fA-F]+))|([^\s},]+))/);         
// match is guaranteed
          if (RegExp.$2) { keyVal = parseInt(rV=RegExp.$2); eatenCt =
rV.length; } else
          if (RegExp.$3) { keyVal = ((rV=RegExp.$3).charAt(0)=="-" ? -1 : 1)
* parseInt(RegExp.$4,16); eatenCt=rV.length; } else
          if (oVirtual[rV=RegExp.$5.toLowerCase()] && !braceLev++) { keyVal
= oVirtual[rV]; eatenCt=rV.length; } else
                                                     { keyVal =
keys.toUpperCase().charCodeAt(0); eatenCt=1; }

          keys = keys.substr(eatenCt);
          delay = dispatchKey(keyVal, target, delay, pause, oScam[0],
downUp);
          continue;
        }
        if (char=="\\") {
          if (keys.length==1) { dispatchChar(char, target, delay, oScam[0]);
return; }
          if (oSpecial[char2=keys.charAt(1)]) { dispatchChar(char2, target,
delay, oScam[0]); keys=keys.substr(2); }
          else { dispatchChar(char, target, delay, oScam[0]);
keys=keys.substr(1); }
	  delay += pause;
          oScam = {0:0};
        } else if (!oSpecial[char]) {
          dispatchChar(char, target, delay, oScam[0]);
          delay += pause;
          keys = keys.substr(1);
          scam = {0:0};
        } else if (oSpecial[char]<=8) {
          oScam[char] = oVirtual[char];
          oScam[0] = oScam[0] | oSpecial[char];
          keys = keys.substr(1);
        } else if (char=="{") {
          braceR = oSpecial[char];		// terminating char
          braceLev = 0;				// 0 for initial; 1 for subsequent passes
          keys = keys.substr(1);
          // need to output the modifier keys
          if ((downUp & 1) && oScam[0]) {
            var scamBuildup = 0;
            for (idx in oScam) {
              if (!idx) continue;		// idx 0 is the summary; otherwise, idx
is a char
              scamBuildup = scamBuildup | oSpecial[idx];		// shift (4),
control (1), alt (2), or meta (8)
              delay = dispatchKey(idx, target, delay, pause, scamBuildup,
1); } }
        }
      }
    }    
  },

--
View this message in context: http://www.nabble.com/GM_sendKeys-t1421601.html#a3832020
Sent from the MozDev - greasemonkey forum at Nabble.com.



More information about the Greasemonkey mailing list