Dienstag, 2. September 2014

Wie ich einmal (vielleicht) das lästige Problem der Synergy sticky keys löste

Es ist schon relativ spät, nicht ganz drei Uhr morgens, als ich mir überlege, noch eine Portion Koffein und Zucker zu mir zu nehmen und einen Report für die Arbeit fertigzuschreiben. Da plötzlich taucht mal wieder das vermaledeite Synergy-Problem der sticky keys auf. Ein Zustand, den ich bis heute nur durch einen Neustart des betroffenen Ubuntu-Rechners beheben konnte.

Wie gesagt, wollte ich gerade wieder losarbeiten und hatte entsprechend viele Fenster offen sowie ein VPN und zwei SSH-Sitzungen laufen. "Viele Fenster offen?" höre ich Euch sagen, "Das habe ich auch, wenn der Tag lang ist." Aber es geht nicht um die bloße Anzahl. Der Zustand der Arbeitsoberfläche enthält all die kleinen Hinweise, die sich bei kontinuierlicher Arbeitsweise ergeben, um eben diese langfristig zu ermöglichen. Hatte mich gerade wer gefragt, wie schwer es wäre, diese oder jene Webseite "schnell zu bauen", bringt mich danach das simple Drücken von Alt+Tab wieder in den vorherigen Gedanken zurück. Und genau das ist es, was einem so erbarmungslos durch einen Neustart genommen ist. Hmm, vielleicht sollte ich mich mal nach Sitzungsspeichern umsehen...

Was ist Synergy?

Worum geht's eigentlich? Synergy ist ein phantastisches input device sharing tool, d.h. ich kann mit einem Satz Tastatur und Maus alle meine Rechner am Schreibtisch - drei Maschinen, auf denen zwei bis drei verschiedene Betriebssysteme laufen - bedienen und das bedeutet, es ist viel mehr Platz, um endlos Papier zu verteilen.
Synergy läuft auf Linux, Windows und Mac und ist kostenlos, bittet aber um Spenden. Ich rate dazu, das tool erstmal auszuprobieren und wenn man es nach ein paar Monaten noch benutzt, kann man mal bilanzieren und sich beantworten, wieviel Zeit und Streß man sich gespart hat (oder auch nicht) und entsprechend spenden (oder auch nicht).

Zur Einrichtung installiert man das Programm in derselben Version und der jeweiligen Architektur entsprechend auf allen gewünschten Rechnern (eines LANs), sucht sich einen von diesen als server-Rechner aus, startet das Programm überall, ordnet auf dem server die Rechner virtuell nebeneinander oder übereinander an und verbindet die client-Rechner mit dem server. Fertig.

Ich bin im prinzip so begeistert, daß ich mich zur (geringfügigen) finanziellen Unterstützung des Projektes überredet habe.

Ein, nicht immer ganz flüssiges, aber trotzdem sehr praktischen feature ist die Übertragung von Zwischenspeichern. Texte, die man auf einem der Rechner per Strg+C in den Zwischenspeicher kopiert, kann man auf einen anderen Rechner per Strg+V einfügen.

Übrigens werden die Eingabegeräte der clients nicht einfach lahmgelegt. Trägt man einen client laptop mal von der Haupttastatur weg, kann man einfach am client weitertippen.

Sticky Keys

Ganz streßfrei ist Synergy allerdings nicht, sonst wäre es nicht zu diesen Zeilen gekommen. Sporadisch und unvermittelt tritt bei mir das Problem auf, daß sich einer der client-Rechner einbildet, eine der Metatasten wäre gedrückt. Es ist nicht konsistent dieselbe Taste und das Problem tritt immer erst nach einer ganzen Weile auf, d.h. im Abstand von Tagen oder Wochen.
Drückt man die "hängende" Taste, so springt das Problem manchmal zu einer anderen Taste. Ich denke, die Problemtasten beschränken sich auf:
Capslock, Strg_L/Ctrl_L, Strg_R/Ctrl_R, Shift_R, Shift_L, Alt_R, AltGr/Alt_R, Menu (quasi die Rechtsklicktaste).
Symptome des Problems (bei Ubuntu 12.04) beinhalten
  • die Unmöglichkeit, KeyEvents von Tasten in Verbindung mit Shift auszulösen (?),
  • die Unmöglichkeit, KeyEvents von Tasten ohne Verbindung mit Shift auszulösen (Shift down?),
  • Flimmern der Fenstermenüleiste, da der WindowManager meint, Tastaturkürzel zu empfangen (Alt down),
  • Verschieben von Fenstern durch DragEvents (Alt down) und
  • maßloses Markieren durch DragEvents der Maus (Strg/Ctrl down).

Kapitulation

Um die Problemtaste weiterspringen zu lassen, muß man ab und zu beide Tasten einer Art gleichzeitig dürcken, zum Beispiel beide Shift-Tasten. Landet man in dem Zustand, daß AltGr/Alt_L als gedrückt gilt, so kann man wenigstens bei tatsächlich gedrückter AltGr/Alt_L-Taste alle Klickaktionen durchführen, wie etwa Fenster schließen, Dateien speichern (nicht jedoch den Namen ändern) und den Neustart initiieren.
Dies war bisher die einzige und dem langen Drücken der Power-Taste noch vorzuziehende Methode für mich, das System wieder in einen nutzbaren Zustand zu versetzen.

Intuition/Haltlose Gerüchte

Ich habe das Gefühl, daß sticky keys nur auftreten, nachdem die client-Maschine wieder aus dem sleep mode erwacht. Zudem vermute ich, daß das Laden von einem oder mehreren Firefox-Profilen vielleicht zum Auftauchen des Problems beiträgt.

Gelöst(?)

Als nun also das Problem wieder auftrat, brachte ich mal wieder genug Energie auf, um dieses immerhin schon seit mehreren Jahren bekannte Problem zu erforschen und habe nach einfachen InputEvent listening devices gesucht, also Programmen, die mir mitteilen, wenn zum Beispiel ein KeyEvent (per Tastatur) oder MouseEvent (per Maus) ausgelöst wird. So stieß ich auf xev, einen event listener für X-Systeme wie zum Beispiel Ubuntu.

xev wies mich sofort nach dem Starten (mühsames Zusammenkopieren der drei Buchstaben und von Return in einen Terminal) darauf hin, daß unentwegt Alt_L aka AltGr gedrückt wurde, auch ohne einen Finger an irgendeiner Taste. Da man ja lösungsorientiert denken soll,verstand ich die Angaben einfach mal als Aufforderungen und drückte, nach und nach wasauchimmer xev mir als Taste in den Terminal warf, ab und zu auch mit der zugehörigen Zwillingstaste zusammen und manchmal gedankenverloren unterbrochen durch Drücken auch noch nicht genannter Tasten wie Capslock und Menu (Taste mit dem Symbol eines Drop-Down-Menüs). Auf diese Weise änderten sich die angegebenen KeyEvents und nach weniger als drei Minuten hatten sich plötzlich alle sticky keys gelöst und ich konnte wieder normal tippen auch ohne Neustart.

Ich weiß nicht, ob es zur Lösung beigetragen hat, daß ich zwischendurch die meisten Anwendungen (z.B. alle Firefox-Fenster) geschlossen habe. Das einfache Weiterarbeiten hatte sich damit jedenfalls schon erledigt.

Vorbereitung

Um beim nächten Mal besser reagieren zu können, habe ich mir auf dem Desktop ein Verzeichnis cd-xev angelegt und die ausführbare Datei terminal-vom-Desktop:

#!/bin/bash
cd cd-xev
/usr/bin/gnome-terminal

Wenn ich die Datei ausführe, öffnet sie einen bash-Terminal vom Verzeichnis cd-xev aus, wodurch ich mir einfach per Maus das Kommando xev zusammenklicken kann, um dann wie oben beschrieben die Lösung nochmal zu versuchen.

Synergy-Hinweise

Seit irgendwann kann Synergy nicht mehr mit client-Rechnern umgehen, deren gesendeter Name mit einer Nummer beginnen. Sinnvollerweise kann man im Synergy des clients den Anmeldenamen anpassen.

Beim Rechnerwechesel kann es zu Problemen kommen, wenn man noch Metatasten drückt, wann man also zum Beispiel bei gedrückter Taste Strg den Mauszeiger zu einem anderen Rechner zieht. Ich rate entsprechend davon ab.

Ich hoffe, das hilft.

English (short)

Synergy Sticky Keys

Client machines in a Synergy setup may experience sticky key, i.e. even though no key is touched the machine constantly gets KeyEvents from one of
Capslock, Strg_L/Ctrl_L, Strg_R/Ctrl_R, Shift_R, Shift_L, Alt_R, AltGr/Alt_R, Menu (basically the right-click key, has drop-down menu symbol).

Symptoms (on Ubuntu 12.04) include:
  • impossibility to execute KeyEvents in connection with Shift (?),
  • impossibility to execute KeyEvents without connection to Shift (Shift down?),
  • flickering window menu bar, since the WindowManager "receives" shortcuts (Alt down),
  • moving windows on DragEvents (Alt down) and
  • greedy marking on DragEvents (Strg/Ctrl down).

Solved(?)

Run xev, an event listener for X systems like Ubuntu. Keep pressing the key xev reports as being pressed already. The key that's perceived as constantly pressed should change. It may be necessary to press the meta key's twin key as well at the same time. From time to time also press those meta keys that haven't appeared yet, e.g. Capslock und Menu.
When my machine normalized its key events I had also closed most of my applications, specifically all Firefox windows. So this may also have had an effect.

Prepare

When this started today I had already a hard time running xev, since I had to find and mark the required letters with the mouse and paste them into a terminal as well as an easier to find Return/linebreak.
In order to react in an easier fashion the next time the problem occurs I created a directory cd-xev on my Desktop and an executable file terminal-from-Desktop:
#!/bin/bash
cd cd-xev
/usr/bin/gnome-terminal

I hope this will help.

Montag, 1. September 2014

Wie ich mal ein GreaseMonkey-Skript für Steam search results schrieb


Update [2014-09-27]

Steam hat mittlerweile das Design umgestellt, so daß discounts für Suchergebnisse automatisch angezeigt werden. Das Skript hier könnte man nun so anpassen, daß es die discounts nach Prozent einfärbt wie unten beschrieben.

Steam has changed the design so that discounts for search results are directly shown. The script here can be adapted to recolour discount displays according to percentages and the colour keys given below.


Deutsch

Motivation

Ich ziehe derzeit, da ich Null Zeit zum Spielen habe, einige Energie daraus, mir wenigstens ab und zu auf Steam Spiele zu kaufen. Da ich fast nur Spiele kaufe, die im Preis heruntergesetzt sind, finde ich mich immer mal wieder auf der Suchseite von Steam wieder. Im Preis heruntergesetzte Suchergebnisse werden dort mit zwei Preisen angegeben, dem durchgestrichenen Normalpreis und dem heruntergesetzten. Doch welche lohnen sich?

Ein Rabatt von 50% oder weniger ist häufig für alle möglichen Spiele zu sehen, 75% und mehr für interessante Spiele sind dagegen eher selten. Dieser Hinweis auf die Seltenheit des Angebotes steht aber nicht direkt zur verfügung. Stattdessen muß man die beiden Preise verrechnen, von denen einer auch noch schwerer lesbar gemacht wurde.

Warum steht nicht einfach der Rabatt da? Hmm, warum schreibe ich mir nicht die Rabatte dazu? Dann könnte man das gleich weiterverteilen.

Aufgabe

Schreibe ein GreaseMonkey-Skript (JavaScript), daß die Anzeige um die Rabatte erweitert und einfärbt, um schnell zu erkennen, wie lukrativ und selten ein Angebot ist.
Weil ich mich nicht damit beschäftigen wollte, die Ladezustände von selbstaktualisierten Seiten zu erkennen, habe ich entschieden, daß ich nur einen Button auf der Suchseite haben will, über den ich die Rabatte zu den Suchergebnissen hinzufügen kann.

Probleme

Ich habe das Skript stückweise in Firebug 2.0.3 zusammengeschrieben und dann in ein GreaseMonkey-Skript (2.2) gegossen. Dabei bin ich als GM newbie auf das Problem gestoßen, daß GM seine Fehlermeldungen nicht einfach in die Firebug-Konsole schmeißt, wodurch ich zunächst nur feststellen konnte, daß mir Funktionalität fehlt, nicht aber was das eigentliche Problem ist oder an welcher Stelle es auftritt.
Es stellt sich heraus, daß GM Fehler an die Firefox-Fehlerkonsole weitergibt, die man in Firefox über das Kürzel CTRL+SHIFT+J öffnen kann.


Das code-Problem bestand darin, daß in der Firebug-Konsole für strings die Methode strip() existiert, nicht aber in GM. Dort heißt die Methode trim() (steht auch in Firebug bereit).

GM-Skript

Der Knopf

Der Ansicht der Suchseite wird oben links ein Knopf hinzugefügt, auf dem einfach ein Prozentzeichen zu sehen ist.
Der Knopf ist "an den Bildschirm geheftet" (style.position="fixed"), wodurch er auch beim scrollen an seiner Position bleibt.
Durch das Klicken des Knopfes werden, die gerade gezeigten Suchergebnisse durchlaufen und soweit nötig um den kolorierten Rabatt erweitert. Da beim scrollen zum Seitenende unter Umständen weitere Ergebnisse geladen werden, muß man den Knopf nochmal betätigen, um auch dort die Rabatte zu sehen.
Ich empfehle, zum Seitenende zu scrollen, bis Steam keine Ergebnisse mehr nachlädt und dann den Knopf zu drücken.

Die Rabatte sind nach subjektiv gewählten Prozentbereichen koloriert.
ProzentbereichBasis16-CodeFarbeFarbname
[0,49]#b0aeacLight grey (Steam default)
(49,64]#FF4500Orange red
(64,74]#FFFF00Yellow
(74,79]#00FF00Lime (was #00FFFF Aqua)
(79,89]#00FFFFAqua (was #0000FF Blue)
(89,100]#8B008BDark magenta
Hier nochmal alle Farb-codes am Beispiel:

Skript

Zur Verwendung des Skriptes installiert man GreaseMonkey im Firefox geht dann über das GM-Menü auf die Skripteverwaltung und paßt das folgende Skript ein:

// ==UserScript==
// @name        Steam Search with Discount Percentages
// @namespace   www.dauth.de
// @description Enhance Steam search results with color-coded discount percentages.
// @include     http://store.steampowered.com/search/*
// @version     1
// @grant       none
// ==/UserScript==

// include     http://store.steampowered.com/search/?sort_by=Name&sort_order=ASC&category2=29&specials=1


/**
 * Add a button that adds color-coded discount percentages to Steam's search results.
 *
 * Not wanting to figure out how to react to page changes like they happen with Steam's
 * search results I decided to add a button to the search page that enhances the price
 * data with the discount percentage and color-codes it for easy reference.
 *
 * The simple HTML button is added to the top left of the Firefox screen space thus not
 * moving when the page is scrolled. Click the button to update discount percentages
 * after the page has updated which usually happens when scrolling to the bottom.
 *
 * See color codes below.
 *
 * @author valentin dauth
 * @version 2014-09-01
 */

/**
 * Colors corresponding to percentage p of deal:
 * p in [0,49]: #b0aeac Light grey (Steam default)
 *     (49,64]: #FF4500 Orange red
 *     (64,74]: #FFFF00 Yellow
 *     (74,79]: #00FF00 Lime (was #00FFFF Aqua)
 *     (79,89]: #00FFFF Aqua (was #0000FF Blue)
 *    (89,100]: #8B008B Dark Magenta
 *
 * @param {number} percentage The discount percentage.
 * @return {string} Hexadecimal color code for given percentage.
 * @see http://www.w3schools.com/tags/ref_colornames.asp
 */
var percentageColors = {'49':'#FF4500','64':'#FFFF00','74':'#00FF00','79':'#00FFFF','89':'#8B008B'};
function getPercentageColor(percentage) {
 var color = '#b0aeac';
 for (c in percentageColors){
   if ( percentage>c ) {
   color=percentageColors[c];
  };
 };
 return color;
};

/**
 * Find prices on the steam search page and calculate percentage of deal.
 * Then color-code percentage and add it to the price data.
 * Also change styles to make room for the percentage.
 */
function addColoredPercentages(){
 var prices = document.querySelectorAll("div.search_price");
 var hiLoPrices,percentage,color,percentageSpan,text;
 for (var p=0;p%lt;prices.length;p++) {
  hiLoPrices = prices[p].textContent.replace(/[€$£]/g,'').replace(',','.','g').trim().split(' ');
  // Asserting situation.
  if ( !prices[p].style || // Skip non-HTML-tag
   prices[p].getElementsByTagName("strike").length==0 || // No struck-out price, no deal
   prices[p].textContent.indexOf('%')>=0 || // Skip previously handeled
   hiLoPrices.length<2 ) {  // Skip if no two prices
    continue;
  };

  // If deal, find percentage. Color percentage, default Steam text color.
  percentage = (100-parseFloat(hiLoPrices[1])/parseFloat(hiLoPrices[0])*100).toFixed(1);
  color = getPercentageColor(percentage);
  
  // Add information to page.
  prices[p].style.marginTop = "5px"; // Make space for percentage
  percentageSpan = document.createElement('span');
  percentageSpan.style.color = color;
  text = document.createTextNode(percentage+'%');
  percentageSpan.appendChild(text);
  prices[p].appendChild(percentageSpan);
 };
};

/**
 * Create button fixed to screen as opposed to page, clicking displays 
 * colored percentages.
 */
function createButton(){
 var button = document.createElement('button');
 button.id = 'getPercentageButton';
 button.style.cssText += "position:fixed;top:5px;left:5px";
 var text = document.createTextNode('%');
 button.appendChild(text);
 document.getElementsByTagName('body')[0].appendChild(button);
 document.getElementById("getPercentageButton").onclick = addColoredPercentages;
 
};

// MAIN: Execute creation of button.
createButton();
 

English

Motivation

Having no time at the moment to actually play games, I still draw a bit of energy from simply buying games on Steam. I almost exclusively buy discounted games and so I often peruse Steam's search page. Discounted games are displayed with two prices there, one being the struck-out standard price and one being the discounted price. But which discounts are actually a good deal?

A discount of 50% or less is relatively often seen for most of Steam's games while 75% and more for those game one is interested in are rather rare. Now the actual discount isn't displayed in search results thus requiring the user to process both displayed prices one of which is also made harder to read. All this even though discount is an important hint I often use to dismiss current offers.

Now, why aren't discounts simply displayed with results? Wait, why don't I add them? That could also be useful for others.

Task

Produce a GreaseMonkey script (JavaScript) that enhances displayed items by adding discounts and colouring them for easy distinction of rare deals.
Since I didn't want to deal with loading states of a page that self-updates I decided to simply add a button to the page that would add discounts to search results.

Problems

I pieced the script together in Firebug 2.0.3 and combined it to a GreaseMonkey script (2.2). As a GM newbie I was stumped by the fact that GM doesn't propagate it's error messages to the Firebug console, so that it took me some time to realize missing functionality and even then I didn't know what the actual problem was or where it originated.
Turns out, GM posts error to the Firefox error console, accessible from within Firefox by hitting the shortcut CTRL+SHIFT+J.


The actual problem with the code resulted from Firebug console offering strip() for strings but GM doesn't. Instead GM has trim() (also available in Firebug).

GM Script

The Button

The view of the search page is enhanced by a button in the top left featuring just a percent symbol.
The button is "fixed to the screen" (style.position="fixed"), i.e. scrolling the page doesn't move the button from its position.
Clicking the button will sort of parse current search results and add the colour-coded discounts where applicable. Since scrolling to the bottom of the pagemay lead to more results being loaded the button may require additional clicks to enhance previously hidden results.
I advise to scroll to the bottom of the page until Steam is done loading all results and then clicking the button.
 
Colouring of percentages follows subjectively chosen percentage ranges.
Percentage RangeBase16 CodeColourColour Name
[0,49]#b0aeac
Light grey (Steam default)
(49,64]#FF4500
Orange red
(64,74]#FFFF00
Yellow
(74,79]#00FF00
Lime (was #00FFFF Aqua)
(79,89]#00FFFF
Aqua (was #0000FF Blue)
(89,100]#8B008B
Dark magenta
Here's an example with all colour codes:

Script

To use the script install GreaseMonkey in Firefox and using the GM menu go to script management and fit in the script given in the subsection "Skript" of the German part above.