UI Schlendrian bei Apple iCal

written by Martin Häcker on

iCal - ein Programm das man lieben und hassen kann.

Einerseits, es funktioniert gut, ist ordentlich ins System integriert - aber andererseits sowas:

Wenn man einen Termin bearbeitet, dann doppelklickt man ihn. Danach öffnet sich dieser in einem neuen Fenster, in dem man dann noch einmal auf Bearbeiten klicken muss damit man ihn tatsächlich verändern kann.

Das tolle daran: Wenn man dann irgendwann mit seinen Änderungen zufrieden ist und ihn bestätigen möchte, dann stellt man fest das der "Bestätigen" Knopf auf keinen sinnvollen Keyboard Shortcut reagiert. Enter, Return, Controll-Return, Command-Return, Alt-Return - alles voll daneben.

Bisher dachte ich nur: "Naja, voll nervig" aber heute ist mir aufgefallen dass sie es sogar geschafft haben dem noch die Spitze aufzusetzen: Es gibt nämlich doch eine Tastenkombination um die gemachten Änderungen zu akzeptieren:

ESCAPE

!!!!1111!!!!(elf)!!!

Ich bin Sprachlos.

Authenticating an OTR user in Adium

written by Martin Häcker on

(With many thanks to Dan Levin for his help)

This is what I arrived at [blog:2008/08/19/19.49 last week] as an authentication GUI:

Image

This GUI Design has several good and bad points - first the good ones:

  • It tells the user exactly what he should do
  • It gives help in how to achieve that
  • It provides additional help on how to achieve this

The bad ones though weigh very heavy:

  • Its a dialog.
  • Its super text heavy
  • it looks ugly (well not because of the design, but because it has so much text in it.)

The most pressing problem however is in the library support from OTR. It simply does not know what a question is.

This is a problem, because it makes it almost impossible to have the Question as a special entitiy in the GUI - and also that none of the synchronisation problems that this brings are adressed in the GUI. So what to do if both users start typing their own question? The other User wouldn't know - then how does the programm tell the user that his question was just overruled be the other persons and that he should provide the answer to that as the secret word?

There is more of course - but this is what I consider the most important.

Well, this is our current approach at solving these problems:

Image

This is what I like about this:

  • No more dialog, it's all happening inline
  • It clearly states what goes where in clear statements
  • It tells the user in clear terms what he needs to do and how to do it
  • Only as much text as is absolutely needed
  • Provides more help should the user request it.

Especially this is the most simple GUI of all the approaches that I tried.

I don't have finished mockups of the other aproaches (most is pen'n'paper work) but I did try having the question and answer thing inline like so:

Image

I especially like the fact that this gives lots of practical information to the user - even if it is still very wordy and does not solve the race condition of who will actually ask the question.

Therefore we decided to go with the first aproach pictured above.

There is of course more to this GUI than just the pretty pictures shown above. A few more things come to mind, that we think are very important:

  • The normal chat window can and should be used as a feedback and instruction window. (With help messages clearly marked as such of course.) We want to use this to show some initial help when the "Verify" button is clicked and the authentication process is started and also at least when the authentication process fails multiple times, to provide additional help and tips as to how to overcome this situation.
  • Whenever the user types the secret word in the normal chat we want to either clearly notify the user that he has just compromised his password exchange and needs to choose a new question and answer (also if it is in the recent history) or we'd like to prevent the sending of the message (perhaps with a dialog box that also allows to proceed if that is really wanted).

One unsolved problem is, that when the authentication fails multiple times, we need to find a good compromise between teaching the user about ways to prevent errors and making him aware of the possibility that there may be a man in the middle attack taking place.

Of course, just as the last time: Feedback welcome.

Here's the rest of the work

Schadenfreude ist..

written by Martin Häcker on

... die schönste freude.

Stimmt einfach. :)

Kryptographie saugt

written by Martin Häcker on

Und zwar gewaltig.

Heute hab ich eine Menge Zeit darauf verbrannt unserem Online-Update-System auf den Zahn zu fühlen. Das arbeitet nämlich ganz hervorragend mit Sparkle um für unsere Applikation via Appcast herunter zu laden, wenn eine neue Version ansteht. Und damit das auch schön sicher ist, nutzen wir ein kleines öffentlich/privates Schlüsselpärchen um das abzusichern.

Nun ja, bis heute mittag - da hat es nämlich auf einmal aufgehört und konnte die heruntergeladenen Dateien nicht mehr validieren.

Jetzt hatte ich natĂĽrlich das Problem, das ich an sich recht wenig von Crypto verstehe und erst recht wie man das von der shell aus einsetzt. Das wiederum hatte zur Folge, dass ich das nicht sofort auf der Shell nachvollziehen konnte und wir schon anfingen zu fĂĽrchten das vielleicht unsere SchlĂĽssel abgelaufen sind.

Gott sei dank war es aber doch was anderes - denn unser Proxy hatte fieser weise einfach eine alte Version des Appcasts gespeichert und stattdessen immer die ausgeliefert - was natĂĽrlich nicht funktionierte... Grumpf.

Note to self: Nächstes mal gleich den ganzen Vorgang von Hand reproduzieren um zu sehen wo es schief läuft - und zwar angefangen von da wo das Programm anfängt. (Auch wenn in diesem Fall die Fehlermeldung wirklich eher auf einen OpenSSL Fehler hindeutete).

Debuggen ist eben schwieriger als den Code zu schreiben. Ich vermute, weil man ihn dafür wirklich verstehen muss - inklusive aller Fehler die man in seinem eigenen Verständnis hat.

P.S.: So Signiert und Validiert man ein File mit OpenSSL von der Kommandozeile:

# base 64 encoding to ease textual transmission
openssl dgst -dss1 -sign dsa_private_key.pem Important.rtf | openssl enc -base64 > signature.sig
# and decode again
openssl enc -base64 -d < signature.sig > decoded.signature.sig
# and verify
openssl dgst -dss1 -verify dsa_public_key.pem -signature decoded.signature.sig Important.rtf

Einige Fallstricke: -dss1 und -sha1 erzeugen offenbar die gleichen Checksummen (man könnte also meinen das es der gleiche Algorithmus ist) die beiden digest modi werden aber in openssl offenbar unterschiedlich behandelt. Beim Signieren löst das verwenden von -sha1 auch schön eine Fehlermeldung aus - wenn man beim Überprüfen aber -sha1 zu verwenden versucht, dann erhält man nur den Hinweis dass die Verifikation gescheitert ist - an einem falschen public key. Seeehr hilfreich liebe Entwickler.

Wie man ein SchlĂĽsselpaar erzeugt und vieles mehr gibts hier.

Wo ich gerade am lästern bin

written by Martin Häcker on

Apple hat sich da ja einen richtigen Kracher geleistet mit ihrem App-Store. Vor allem ist das Update-System absolut grandios gelöst.

Man geht einfach im iPhone auf den App-Store, dort in den Updates-Reiter und.... sieht nichts. Weil, da sind zwar die Updates. Aber eben nicht alle.

Ein Programm das ich mir gekauft hatte (Things, weil die UI einfach sehr, sehr gut ist) tauchte dort nämlich nicht auf - obwohl ich es sehnsüchtig erwartete. Und das, obwohl der iTunes Store das Programm schon längst als upgedatet aufgelistet hat.

Tja, was tun? Letztlich habe ich mich dann dafür entschieden, dass ich für 8 € auch das Programm einfach noch einmal herunterladen kann. Wenigstens kann ich dann die neue Version nutzen. Und kann so mal Probieren ob der App-Store nicht vielleicht einen Bug hat und mir den Preis nur fälschlicherweise anzeigt.

Gesagt getan - da kommt dann erst eine Warnung dass der Betrag jetzt direkt von meiner Kreditkarte abgezogen wird (gut, Kreditkarte hab ich nicht, aber das ist halt ne Amerikanische Firma) und dann... dann kommt der Dialog der mir erklärt das ich das Produkt ja schon gekauft hätte und das Update daher kostenlos sei.

Pfffff. Wenn ich ein Update lade, dann will ich vorher wissen ob mich das Geld kostet! Vorher, nicht hinterher. Vielleicht.

Ganz groĂźe Klasse.

Kontrollieren welche Libraries ein Programm lädt

written by Martin Häcker on

ist beim Entwickeln ein echtes Problem.

Frameworks unter Mac OS X werden schließlich so gelinkt, das jeder Framework den Pfad enthält, an dem er später einmal leben wird - und dieser Pfad wird dann in die Applikation eingetragen die ihn laden möchte.

Das ist blöd, wenn man Frameworks in /Library/Frameworks/ installiert, bei der Entwicklung dann aber mal eine andere Version ausprobieren will oder muss. Auch klassisch ist, dass ein Framework der von Apple kommt zu alt ist und man sicherstellen muss, das ein eigener Framework stattdessen verwendet wird, der Bugfixes enthält.

Schwierig.

Nunja, man macht das unter OS X mit dem Äquivalent des Linux Konstrukts LD_LIBRARY_PATH, also diversen Umgebungsvariablen, mit denen man das Verhalten des dynamischen linkers beeinflussen kann. Spaßigerweise hat Apple das verhalten des dynamischen Linkers unter Tiger (10.4) verändert, dass er sich jetzt genauso verhält wie unter Leopard (10.5) - zumindest sagt mein Gedächtnis andere Dinge als die 10.4 manpage von dyld.

Also sind jetzt eigentlich nur noch zwei Umgebungsvariablen interessant:

  • DYLD_FRAMEWORK_PATH wenn man etwas laden will, bevor die Pfade verwendet werden die in der Applikation fĂĽr einen Framework stehen -> also um einen Framework zu ĂĽberschreiben.
  • DYLD_FALLBACK_FRAMEWORK_PATH wenn man etwas nachladen will, nachdem es an den vorgegebenen Pfaden nicht gefunden wurde. Die Manpage ist mir da noch nicht 100% klar - es wirkt fast so, als könnte man dyld auch dafĂĽr verwenden eine neuere Version anzugeben, die nur genutzt wird, wenn in der alten ein bestimmtes Symbol nicht gefunden wurde - das konnte ich aber noch nicht testen (und kommt mir auch komisch vor).

Also: kennen sollte man die - das gibt viel flexibilität.

Obfuscated Objc - oder - wie macht man Konstanten?

written by Martin Häcker on

Ist ja eigentlich nicht schwer, man braucht lediglich eine Definition wie diese

#define kIOHIDVendorIDKey @"VendorID"
const NSString *windowName = @"Morgens"

Einziges Problem - immer wieder hat man vom System konstante C-Strings, die man aber als Objc-Strings benötigt.

Interessanterweise hat der Compiler ĂĽberhaupt kein Problem das hier zu machen:

#define kIOHIDVendorIDKey "VendorID"
// ...
[matchingDict setValue:[NSNumber numberWithInt:entry->Vendor_ID] 
       forKey: @kIOHIDVendorIDKey]; 

// same, but more readable than
[matchingDict setValue:[NSNumber numberWithInt:entry->Vendor_ID] 
       forKey: [NSString stringWithCString:kIOHIDVendorIDKey]];
// Also note that -stringWithCString is deprecated

Oder mit anderen Worten man kann problemlos aus jeder C-String-Konstante eine Objc-String-Konstante machen, indem man einfach ein @ davor schreibt.

Neat.

Debugging Magic in Cococa

written by Martin Häcker on

Erstaunlich aber wahr - viele Programmierer scheinen die Debugging-Möglichkeiten der Plattform ihrer Wahl nicht zu kennen.

Cocoa zum Beispiel. Viele Programmierer wissen nicht wie man Speicherlöcher effektiv fängt.

Well, nicht Leaks - da kann man ja mit ObjectAlloc draufhauen und dann das Programm fixen.

Nein, viel problematischer als das sind die doppelten Befreiungen.

id foo = [[NSString alloc] initWithFormat:@"foo"];
[foo release];
// do something else
NSLog(@"foo is %d", [foo length]); // almost certainly bombs

Die krachen nämlich fast immer - und dann sucht man sich tot was genau da eigentlich gekracht hat und woher das genau kommt. Tja.

Dabei kriegt man aus dem Programm eigentlich alle Informationen heraus die man braucht. Man muss nur jedes malloc mitzählen und aufschreiben wo es passiert ist, dann jedes objekt das freigegeben wird, nicht wirklich freigeben, sondern lediglich markieren und dann bei einem weiteren Zugriff auf so ein Objekt eine Exception werfen.

Nichts leichter als das. :)

Man setze diese Environment Variablen (am besten auf einem Duplikat der zu entwanzenden Applikation in Xcode)

MallocStackLogging YES
MallocStackLoggingNoCompact YES
NSZombieEnabled YES
NSDebugEnabled YES

Hat man dass macht die Runtime alles richtig. Jede allokation wird aufgezeichnet und jedes freigegebene Objekt durch einen Zombie ersetzt. Und der Meldet sich, sobald er für irgend etwas verwendet wird. Nämlich so:

2008-08-26 00:19:43.524 CrashTest[1663:817] *** -[CFString length]:
    message sent to deallocated instance 0x105440

Hängt man dann im Debugger, kann man von dort aus direkt diese Information abgreifen.

(gdb) shell malloc_history 1663 0x105440

Call [2] [arg=16]: thread_a00ddfa0 |start | main | -[NSString initWithFormat:] |
 -[NSPlaceholderString initWithFormat:locale:arguments:] |
 _CFStringCreateWithFormatAndArgumentsAux | CFStringCreateCopy |
 __CFStringCreateImmutableFunnel3 | _CFRuntimeCreateInstance | malloc_zone_malloc

Die Syntax fĂĽr malloc_history ist dabei malloc_history pid address.

Jetzt weiĂź man mit was fĂĽr einem Objekt man es zu tun hat und man weiĂź wo es her kommt.

Nice! (Mehr unter Mac OS X Debugging Magic)

p.s.: Ohne etwas Schelte an Apple geht es aber nicht. Das man sich da von Hand die Umgebungsvariablen einstellen muss - böse. Zumal das noch an einer Custom Executable geschieht. Die sind nämlich nicht im Projektfile gespeichert, sondern in den Dateien die Xcode pro Benutzer anlegt. Das hat zur Folge, dass im Normalfall nicht ein Entwickler diese Dinge raussucht und dann alle anderen im Projekt dass als Tool nutzen können, sondern dass sich das jeder Selber einrichten und warten muss. Ach ja, und natürlich zeichnet malloc_history nur die allocs auf und nicht die frees - das wäre nämlich noch praktischer, dann könnte man nämlich direkt kucken wo es das erste mal freigegeben wurde. Aber nein....

NSToolbarItem whoes

written by Martin Häcker on

Kleines Detail, dass man wissen sollte.

NSToolbarItems sind etwas völlig anderes als normale Controls. Sie sind nämlich keine. Sondern sie enthalten höchstens welche - aber nur als View. Das heißt, wenn eine Toolbar z.b. auf "nur Text" geschaltet ist, dann hat das Item überhaupt kein wissen darüber was in dem NSView eigentlich passiert - und daher versucht es auch überhaupt nicht etwas sinniges zu tun.

Unter Tiger zumindest. Da wird zwar alles schön angezeigt - aber machen tut das dann alles nix mehr. Unter Leopard ist es immerhin so, dass auf einen Klick die Toolbar wieder mit Bild angezeigt wird.

Tja und Lösen muss man das ganze dann so, dass man verstehen muss, das NSToolbarItems für die nur Text Darstellung ein NSMenuItem verwenden, dass man selbst anpassen muss. Steht nur in der Dokumentation lediglich als mini Absatz ohne Erklärung wieso man das braucht.

Hrmpf. Naja. Immerhin macht es Sinn wenn man versteht was sie als Design gewählt haben.

Aber wieso sie sich für dieses Design entschieden haben, verstehe ich absolut nicht. Sie hätten ja auch gleich ein NSControll als content nehmen können - und wenn das ein Target und eine Action hat dann einfach die wiederverwenden...

Aber neee....

Usability und Programmierer

written by Martin Häcker on

Ich habe ja schon lange den Verdacht, dass das allgemeine Vorurteil das Programmierer schlechte GUI-Designer sind vielleicht nicht ganz korrekt ist.

Jetzt habe ich endlich den Beweis: Gutes GUI Design ist wie gutes API-Design - die allermeisten leute können es nicht.

Das hier zum Beispiel gethostbyname() ist teil der libc und gibt einem zu einem Hostnamen die Daten aus einem DNS-Lookup zurĂĽck.

Well.

Nur das wir jetzt eben mal zwei Tage darauf verbrannt haben herauszufinden, dass dieses gethostbyname so dämlich ist, dass es niemals nicht von sich aus checkt ob sich vielleicht seit das Programm gestartet wurde, die Netzwerkeinstellungen irgendwie verändert haben könnten. Egal wie lange das Programm schon läuft...

GroooĂźartig.

Dafür muss man nämlich die Funktion res_init() aufrufen. Ach ja, und wann man das tut rät man einfach. Weil einen Callback gibts dafür natürlich nicht. Ach ja, und in der Manpage von gethostbyname steht natürlich auch nix über res_init.

Gnah.

Das ist fĂĽr mich ein Musterbeispiel fĂĽr eine verkackte API - eine API deren Benutzerfreundlichkeit gegen null geht.

Ergo: Programmierer können Benutzeroberflächen machen - aber die meisten eben nicht. Und das müssen wir alle dann wieder an den APIs erleiden die sie designen.

Hab ich schon mal erwähnt das ich die libc immer weniger mag?