hterm 1.53: Add 'alt-gr-mode' preference.

* Add a preference to select the preferred AltGr heuristic.  This replaces the
  change from 1.52, which prevented the use of Ctrl-Alt-... on all platforms.
  The new preference takes one of the following values:

    null: Autodetect based on navigator.locale:
          'en-us' => 'none', else => 'right-alt'
    'none': Disable any AltGr related munging.
    'ctrl-alt': Assume Ctrl+Alt means AltGr.
    'left-alt': Assume left Alt means AltGr.
    'right-alt': Assume right Alt means AltGr.

  The default value is null.  Autodection won't work in many cases, and the
  left-alt/right-alt tracking will have issues if the window loses focus
  while the alt key is down, but this may be the best we can do on the web.

Change-Id: I47b7e156262c57e8ce2ce265f89f90b1aba48d88
Reviewed-on: https://chromium-review.googlesource.com/254331
Reviewed-by: Marius Schilder <mschilder@chromium.org>
Commit-Queue: Marius Schilder <mschilder@chromium.org>
Tested-by: Marius Schilder <mschilder@chromium.org>
diff --git a/hterm/doc/changelog.txt b/hterm/doc/changelog.txt
index 944c856..fb9d4c8 100644
--- a/hterm/doc/changelog.txt
+++ b/hterm/doc/changelog.txt
@@ -1,3 +1,20 @@
+1.53, 2015-02-26, Add 'alt-gr-mode' preference.
+
+* Add a preference to select the preferred AltGr heuristic.  This replaces the
+  change from 1.52, which prevented the use of Ctrl-Alt-... on all platforms.
+  The new preference takes one of the following values:
+
+    null: Autodetect based on navigator.locale:
+          'en-us' => 'none', else => 'right-alt'
+    'none': Disable any AltGr related munging.
+    'ctrl-alt': Assume Ctrl+Alt means AltGr.
+    'left-alt': Assume left Alt means AltGr.
+    'right-alt': Assume right Alt means AltGr.
+
+  The default value is null.  Autodection won't work in many cases, and the
+  left-alt/right-alt tracking will have issues if the window loses focus
+  while the alt key is down, but this may be the best we can do on the web.
+
 1.52, 2015-02-18, Treat Ctrl-Alt as AltGr
 
 * Assume that Ctrl-Alt-[Printable] means AltGr-[Printable].  After this change,
diff --git a/hterm/js/hterm_keyboard.js b/hterm/js/hterm_keyboard.js
index d21ed3d..f6cf6a5 100644
--- a/hterm/js/hterm_keyboard.js
+++ b/hterm/js/hterm_keyboard.js
@@ -39,6 +39,14 @@
   this.keyMap = new hterm.Keyboard.KeyMap(this);
 
   /**
+   * none: Disable any AltGr related munging.
+   * ctrl-alt: Assume Ctrl+Alt means AltGr.
+   * left-alt: Assume left Alt means AltGr.
+   * right-alt: Assume right Alt means AltGr.
+   */
+  this.altGrMode = 'none';
+
+  /**
    * If true, Shift-Insert will fall through to the browser as a paste.
    * If false, the keystroke will be sent to the host.
    */
@@ -147,9 +155,11 @@
 
   /**
    * Used to keep track of the current alt-key state, which is necessary for
-   * the altBackspaceIsMetaBackspace preference above.
+   * the altBackspaceIsMetaBackspace preference above and for the altGrMode
+   * preference.  This is a bitmap with where bit positions correspond to the
+   * "location" property of the key event.
    */
-  this.altIsPressed = false;
+  this.altKeyPressed = 0;
 
   /**
    * If true, Chrome OS media keys will be mapped to their F-key equivalent.
@@ -326,12 +336,13 @@
 };
 
 hterm.Keyboard.prototype.onBlur_ = function(e) {
-  this.altIsPressed = false;
+  this.altKeyPressed = 0;
 };
 
 hterm.Keyboard.prototype.onKeyUp_ = function(e) {
   if (e.keyCode == 18)
-    this.altIsPressed = false;
+    this.altKeyPressed = this.altKeyPressed & ~(1 << (e.location - 1));
+
   if (e.keyCode == 27)
     this.preventChromeAppNonShiftDefault_(e);
 };
@@ -341,7 +352,8 @@
  */
 hterm.Keyboard.prototype.onKeyDown_ = function(e) {
   if (e.keyCode == 18)
-    this.altIsPressed = true;
+    this.altKeyPressed = this.altKeyPressed | (1 << (e.location - 1));
+
   if (e.keyCode == 27)
     this.preventChromeAppNonShiftDefault_(e);
 
@@ -387,11 +399,29 @@
   // In the key-map, we surround the keyCap for non-printables in "[...]"
   var isPrintable = !(/^\[\w+\]$/.test(keyDef.keyCap));
 
-  if (isPrintable && control && alt) {
-    // ctrl-alt-printable means altGr on the web.  We clear out the control and
-    // alt modifiers and wait to see the charCode in the keydown event.
-    control = false;
-    alt = false;
+  switch (this.altGrMode) {
+    case 'ctrl-alt':
+    if (isPrintable && control && alt) {
+      // ctrl-alt-printable means altGr.  We clear out the control and
+      // alt modifiers and wait to see the charCode in the keydown event.
+      control = false;
+      alt = false;
+    }
+    break;
+
+    case 'right-alt':
+    if (isPrintable && (this.terminal.keyboard.altKeyPressed & 2)) {
+      control = false;
+      alt = false;
+    }
+    break;
+
+    case 'left-alt':
+    if (isPrintable && (this.terminal.keyboard.altKeyPressed & 1)) {
+      control = false;
+      alt = false;
+    }
+    break;
   }
 
   var action;
diff --git a/hterm/js/hterm_keyboard_keymap.js b/hterm/js/hterm_keyboard_keymap.js
index fe57770..a8ed5d1 100644
--- a/hterm/js/hterm_keyboard_keymap.js
+++ b/hterm/js/hterm_keyboard_keymap.js
@@ -129,7 +129,7 @@
       var action = (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey ||
                     !self.keyboard.applicationKeypad) ? a : b;
       return resolve(action, e, k);
-    }
+    };
   }
 
   // If mod or not application cursor a, else b.  The keys that care about
@@ -139,21 +139,21 @@
       var action = (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey ||
                     !self.keyboard.applicationCursor) ? a : b;
       return resolve(action, e, k);
-    }
+    };
   }
 
   // If not backspace-sends-backspace keypad a, else b.
   function bs(a, b) {
     return function(e, k) {
-      var action = !self.keyboard.backspaceSendsBackspace ? a : b
+      var action = !self.keyboard.backspaceSendsBackspace ? a : b;
       return resolve(action, e, k);
-    }
+    };
   }
 
   // If not e.shiftKey a, else b.
   function sh(a, b) {
     return function(e, k) {
-      var action = !e.shiftKey ? a : b
+      var action = !e.shiftKey ? a : b;
 
       // Clear e.shiftKey so that the keyboard code doesn't try to apply
       // additional modifiers to the sequence.
@@ -161,15 +161,15 @@
       e.shiftKey = false;
 
       return resolve(action, e, k);
-    }
+    };
   }
 
   // If not e.altKey a, else b.
   function alt(a, b) {
     return function(e, k) {
-      var action = !e.altKey ? a : b
+      var action = !e.altKey ? a : b;
       return resolve(action, e, k);
-    }
+    };
   }
 
   // If no modifiers a, else b.
@@ -177,7 +177,7 @@
     return function(e, k) {
       var action = !(e.shiftKey || e.ctrlKey || e.altKey || e.metaKey) ? a : b;
       return resolve(action, e, k);
-    }
+    };
   }
 
   // Compute a control character for a given character.
@@ -430,7 +430,7 @@
  */
 hterm.Keyboard.KeyMap.prototype.onKeyDel_ = function(e) {
   if (this.keyboard.altBackspaceIsMetaBackspace &&
-      this.keyboard.altIsPressed && !e.altKey)
+      this.keyboard.altKeyPressed && !e.altKey)
     return '\x1b\x7f';
   return '\x1b[3~';
 };
diff --git a/hterm/js/hterm_preference_manager.js b/hterm/js/hterm_preference_manager.js
index 4450e65..3f913de 100644
--- a/hterm/js/hterm_preference_manager.js
+++ b/hterm/js/hterm_preference_manager.js
@@ -22,6 +22,18 @@
 
 hterm.PreferenceManager.defaultPreferences = {
   /**
+   * Select an AltGr detection hack^Wheuristic.
+   *
+   * null: Autodetect based on navigator.language:
+   *       'en-us' => 'none', else => 'right-alt'
+   * 'none': Disable any AltGr related munging.
+   * 'ctrl-alt': Assume Ctrl+Alt means AltGr.
+   * 'left-alt': Assume left Alt means AltGr.
+   * 'right-alt': Assume right Alt means AltGr.
+   */
+  'alt-gr-mode': null,
+
+  /**
    * If set, undoes the Chrome OS Alt-Backspace->DEL remap, so that
    * alt-backspace indeed is alt-backspace.
    */
diff --git a/hterm/js/hterm_terminal.js b/hterm/js/hterm_terminal.js
index b4f42fa..0960666 100644
--- a/hterm/js/hterm_terminal.js
+++ b/hterm/js/hterm_terminal.js
@@ -194,6 +194,25 @@
 
   this.prefs_ = new hterm.PreferenceManager(this.profileId_);
   this.prefs_.addObservers(null, {
+    'alt-gr-mode': function(v) {
+      if (v == null) {
+        if (navigator.language.toLowerCase() == 'en-us') {
+          v = 'none';
+        } else {
+          v = 'right-alt';
+        }
+      } else if (typeof v == 'string') {
+        v = v.toLowerCase();
+      } else {
+        v = 'none';
+      }
+
+      if (!/^(none|ctrl-alt|left-alt|right-alt)$/.test(v))
+        v = 'none';
+
+      terminal.keyboard.altGrMode = v;
+    },
+
     'alt-backspace-is-meta-backspace': function(v) {
       terminal.keyboard.altBackspaceIsMetaBackspace = v;
     },