Tuesday, October 5. 2010
Less than two weeks ago, I announced project "bacon bird". And today is the day where I can be proud to announce to first release of baconbird, my new twitter client for text terminals.
Baconbird, much like my previous successful project newsbeuter (an RSS/Atom RSS feedreader for those who haven't heard about it yet), targets people who tend to work on text terminals (using mutt, irssi, slrn, vim, etc. is a strong indicator for that), uses the fabulous STFL as its user interface library, and was created to scratch an itch. My itch in this case was Twitter's switch from basic authentication to OAuth, which had the negative side effect that it rendered all (authenticated) RSS feeds practically unusable, so I was stuck to using the web interface of Twitter, which I wasn't too happy about. Also, other Twitter clients were either slow, had weird user interfaces, or were simply CPU and memory hogs. And then there was the infamous Twitter XSS worm that affected quite a few people. All these things together brought me to the conclusion that I had to change something about that.
At first, I started hacking on another client, but soon I found out that the purely ncurses-based UI is virtually unmaintainable, and so I decided to do it right instead and write my very own thing. And that's how baconbird was born. The code itself is about 850 SLOCs of Perl code, the user interface is based on STFL, the Twitter backend uses Net::Twitter, and all the OO glue code in between uses the Moose object system. All in all it took me less than 2 weeks of my free time to develop the first version of it.
So, what features does baconbird offer? Not too much, so far, but enough for a start and to show it off. Of course, you can view the time line (i.e. all the tweets of you and the people you follow), your mentions, your direct messages, and searches. It reloads continuously, and takes care about the current rate limit as imposed by Twitter. You can post tweets, reply to tweets (it even tracks the status ID, i.e. in the web interface you can see proper "in reply to" links), retweet, send direct messages, reply to direct messages, and when writing tweets, you can even have your URLs shortened (I implemented integration with is.gd).
My personal favorite is the search feature, though, I can search for a certain word or hashtag, and I get a nifty live stream of the latest tweets on that. I can even switch to my timeline and switch back, and the live stream keeps on loading as soon as its view is active, until I switch to another view or start another search.
Baconbird is far from complete, though. I still need to implement subscription management, and everything else the Twitter API has to offer (as long as its applicable from a terminal application). I'm also glad to implement your feature requests. And last, but not least, if you like baconbird and to support its further development, I'd be happy about flattring it. You can also flattr this article, of course (</shameless-plug>).
Monday, July 12. 2010
OpenBSD ist - wie dem einen oder anderen Leser sicherlich bekannt sein dürfte - ein von NetBSD abgeleitetes Unix-artiges Betriebssystem, das seinen Fokus auf Sicherheit legt. Als Entwickler und Maintainer des mittlerweile halbwegs populären Open-Source-Projekts newsbeuter sehe ich es nicht nur als meine Aufgabe, neue Features zu entwickeln und bestehende Bugs zu fixen, sondern auch einen gewissen Aufwand darin zu stecken, die Software für ein möglichst großes Publikum auch tatsächlich praktisch zugänglich zu machen. Diese Aufgabe hat verschiedene Grundrichtungen. Das ist etwa die einfache Verfügbarkeit für Enduser (da habe ich beispielsweise vor kurzem erst eine Liste von Distributionen, die mit fertigen Paketen kommen, zusammengestellt), eine möglichst durchgängige Internationalisierung und Lokalisierung (so ist newsbeuter in mittlerweile 14 verschiedenen Sprachen verfügbar), oder aber auch, die Kompatibilität zu anderen Systemen als nur die populären Linux-Distributionen zu testen und zu gewährleisten. Letzteres hat dazu geführt, dass newsbeuter unter Linux, FreeBSD und Mac OS X läuft.
Vergangenes Wochenende habe ich mich daran gemacht, die Kompatibilität auch zu weiteren Systemen zu testen. Auf meinem Plan standen konkret NetBSD und OpenBSD. Der Zustand von NetBSD ist kurz geschildert der, dass die Citrus-Implementierung von iconv() in der Art und Weise nicht kompatibel ist, als dass es das spezielle Encoding "WCHAR_T", das von der STFL verwendet wird, nicht unterstützt. Das ist nicht schön, aber verkraftbar, da einerseits die von iconv unterstützten Encodings/Zeichensätze systemspezifisch sind, andererseits man da sicherlich auch (achtung, ungetestet!) die GNU libiconv einsetzen könnte.
Bei OpenBSD bietet sich hier ein völlig anderes Bild: hier scheiterte mein Versuchen, die STFL zu übersetzen, schon daran, dass OpenBSD über keine Implementierung von swprintf verfügt. Etwas verdutzt hab ich dann begonnen, weiterzurecherchieren, weil ich eigentlich der Meinung war, dass OpenBSD eigentlich schon mal die Arbeit des Citrus-Projekts importiert hatte, und bin schon nach kurzem über die nahezu talibanesken Ausführungen eines Herrn uriel, der einen Fanatismus dabei zeigt, auf den Unix-Standardweg für Internationalisierung (nämlich wchar_t + Funktionen darauf) zu verzichten und stattdessen UTF-8 als den Einzig Wahren Weg(TM) anzupreisen. Schließlich habe ich mich rangemacht, und geschaut, welche Funktionen aus wchar.h in NetBSD (die Citrus integriert haben), jedoch nicht in OpenBSD zu finden sind, und bin auf folgendes Ergebnis gestoßen. Dadurch, dass das nicht irgendwelche Pipifax-Funktionen sind, sondern da auch ein konkreter Standard bzw. hinreichend verbreitete Implementierungen dahinterstehen, ist die Liste nach Standards sortiert:
Single Unix Specification, Version 2- fwprintf
- fwscanf
- swprintf
- swscanf
- vfwprintf
- vswprintf
- vwprintf
- wprintf (wird aber in der Manpage von wcstok(3) erwähnt...)
- wscanf
Single Unix Specification, Version 3
Proprietär, aber in NetBSD und glibc zu finden- wcsdup
- wcsncasecmp
- wcscasecmp
Man beachte, dass die allermeisten Funktionen schon in SuSv2 spezifiziert sind und es als solche auch in C99 reingeschafft haben.
Tja, und da war es dann aus bei mir mit der guten Laune. Ist es tatsächlich zuviel verlangt von einem doch vergleichsweise populären Betriebssystem (gemessen an der Open-Source-Community, nicht der Gesamtheit aller Computeruser weltweit), etablierte Standards, die thematisch eigentlich genau in die Projektzielsetzung von OpenBSD fallen, umzusetzen, oder ist OpenBSD neuerdings dazu übergegangen, ein möglichst kastriertes System, das entfernt noch irgendwie an Unix erinnert, zu entwickeln? Es ist ja nicht so, dass die Komplexität der oben genannte Funktionen besonders groß wäre, immerhin gibt es davon ja fertige Implementierungen, die sogar in einer für OpenBSD akzeptablen Lizenz vorliegen (nämlich die Entwicklungen des Citrus-Projekts).
Unter solchen Voraussetzungen, dass nicht einmal 13 Jahre alte Standards aus dem C- und Unix-Umfeld umgesetzt werden, kann ich auf jeden Fall OpenBSD nicht mehr als ernstzunehmendes Unix-artiges Betriebssystem betrachten. Ich zeige kein Verständnis für Extrawürste und bewusst weggelassene Funktionalität. Solange nicht zumindest swprintf() in OpenBSD existiert, ist newsbeuter-Support für OpenBSD gestorben, explizit auch so dokumentiert, und ich kann nur jedem, der ernsthafte Open-Source-Entwicklung betreiben will, von einer angestrebten Kompatibilität mit OpenBSD abraten, weil dafür im Vergleich zu anderen System riesige Kompromisse eingegangen werden müssten.
Monday, June 4. 2007
Freitag und Samstag war ich auf den Linuxwochen in Wien, und ich muss sagen, das war ein sehr produktives Wochenende. Ich hab selbst einiges an newsbeuter weiterarbeiten können, und Clifford hat auch an der STFL einiges erweitert. So wird jetzt z.B. der Cursor in Listen am Anfang des jeweiligen ausgewählten Eintrag platziert, was inbesondere für Braille-Zeilen relevant ist. Ausserdem können jetzt die Keybindings der Widgets durch eine Belegungen ersetzt oder ergänzt werden (in newsbeuter wird mit diesem Feature die Navigation durch Listen konfigurierbar gemacht), und man kann durch Tastendrücke beliebige Events generieren. Auch ansonsten konnte ich einige Leute von newsbeuter überzeugen, die Userbase wird also noch weiter steigen. Insbesondere die Möglichkeit, etwas direkt herzuzeigen, und sofort auf Fragen eingehen zu können, ist wirklich spannend. Man hat unmittelbares User-Feedback, was als Entwickler äußerst angenehm ist.
Friday, February 16. 2007
Nachdem meine verehrte Leserschaft über meine Berichte über die Entwicklung von newsbeuter das ncurses-UI-Toolkit STFL zumindest vom Namen her kennt, möchte ich ein wenig mehr über die Eigenschaften und Features dieser genialen Library schreiben, da sie mir bisher sehr beim Erstellen von Konsolen-UIs geholfen hat.
Die Basis von STFL ist die Layout-Beschreibungssprache, mit der man die grundsätzliche Ausrichtung und Positionierung sämtlicher Controls inklusive (optional) des Inhalts und der Farbgebung. Diese Beschreibungssprache wird in eine Datenstruktur geparsed, die dann später zur Laufzeit einfach modifiziert und abgefragt werden kann. Ein wichtiges Prinzip des Layouting bei STFL sind dabei Horizontal und Vertical Boxes. Man stelle sich z.B. den gesamten Bildschirm vor, den man zuerst in mehrere Boxen "untereinander" teilt. In so eine Box kann man dann ein Control platzieren, oder eine weitere vbox (macht nicht sehr viel Sinn), oder aber eine hbox, welche die Box wiederum in mehrere Boxen nebeneinander teilt, usw. usf. Zusätzlich kann man jeder Box noch die Information mitgeben, in welche Richtung sie expandieren soll. Mit diesem simplen Konzept hat man ein mächtiges Werkzeug bei der Hand, um brauchbare und komplexe Layouts für Konsolen-UIs zu designen.
Zur Laufzeit wird diese Layoutbeschreibung dann eingelesen (entweder aus einer Zeichenkette oder aus einem externen File, ich persönlich verwende dieses Skript um STFL-Layouts direkt in Programme einzubinden, aber trotzdem die Layouts in schönen externen Files im Sourcetree liegen zu haben), und die resultierende Datenstruktur wird dem Programmierer dann als opakes "stfl_form *" zur Verfügung gestellt. Dieses Handle kann dann verwendet werden, um den aktuellen Inhalt von Controls auszulesen, zu verändern, den aktuellen Fokus zu bestimmen und zu ändern, und auch um die Datenstruktur selbst zu modifizieren (damit ließe sich theoretisch zur Laufzeit das Layout umstellen, auch wenn das in der Praxis i.A. wenig zielführend ist). Und natürlich kann man das Formular starten und kriegt Benutzereingaben weitergereicht, auf die man entsprechend reagieren kann.
Eine derartige Layoutbeschreibung sieht übrigens so aus:
vbox @style_normal:bg=black,fg=white @info#style_normal:bg=blue,fg=yellow,attr=bold label#info text[head]:"Your feeds" .expand:h list[feeds] style_normal:bg=black,fg=white style_focus:fg=yellow,bg=blue,attr=bold .expand:vh pos_name[feedposname]: pos[feedpos]:0 label#info text[help]:"q:Quit ENTER:Open r:Reload R:Reload All A:Mark All Read C:Catchup All" .expand:h label text[msg]:"" .expand:h
Das ist das Layout der Feedlist, wie sie bei newsbeuter beim Programmstart erscheint. Es besteht aus einer vbox, welches von oben nach unten ein Label für die Überschrift, eine Liste mit den Feeds, ein Label mit Keyboard Shortcuts sowie ein Label für Information und Fehler enthält, alles gespickt mit diversen Details wie Farbgebung und der Information für die Labels, dass sie horizontal expandieren sollen, sowie für die Liste, dass sie horizontal und vertikal expandieren soll (und damit den meisten Platz ausfüllt).
Insgesamt ist das Interface von STFL sehr kompakt (14 API-Funktionen). Die Konstruktion eines Programms erfolgt meistens nach dem gleichen Prinzip: Initialisierung - Event Loop - Ressourcenfreigabe. Die Initialisierung ist bei STFL eine ganz simple:
char * my_form = "..."; struct stfl_form * f1 = stfl_create(my_form); struct stfl_form * f2 = stfl_create("<form.stfl>");
Der erste Schritt ist immer, ein Formular anzulegen, und zwar mit stfl_create(). Dabei gibt es zwei Varianten, und zwar, einen normalen String mitzugeben, dann wird der String als Layoutbeschreibung geparsed, oder aber, man übergibt einen String, der mit '<' anfängt, und mit '>' endet, dann wird der Inhalt der Datei, deren Name zwischen '<' und '>' steht, als Layoutbeschreibung verwendet. Damit kann man auch Layouts aus externen Dateien laden, ohne diese Datei selbst öffnen und auslesen zu müssen, und man muss auch keinen extra API-Call bedienen.
Der nächste Schritt ist dann, das Formular zu starten, und optional auch auf Eingaben des Users zu warten. Und zwar funktioniert das mit stfl_run().
char * event = NULL; event = stfl_run(f1, 0); stfl_run(f1, -1);
Der zweite Parameter gibt dabei, wie lange auf Benutzereingabe gewartet werden soll, welche dann zurückgegeben wird. Der Wert "-1" bedeutet, dass keine Benutzereingaben eingelesen werden (wenn man z.B. lediglich das Formular neu zeichnen will), und 0 bedeutet, dass unendlich lange auf Benutzereingabe gewartet wird. Bei einem Wert > 0 wird soviele Sekunden gewartet. Findet bis dahin keine Benutzereingabe statt, so wird von stfl_run() NULL zurückgeliefert. Hierbei sollte man allerdings auf jeden Fall beachten, dass es auch sein kann, dass auch bei einer Wartezeit von 0 NULL als Rückgabewert zurückkommen kann, weil z.B. ein Tastendruck von dem Widget, das den Fokus hat, "konsumiert" wurde.
Die zurückgegebene Eingabe kann folgendes sein: - "TIMEOUT": bei einem Timeout
- "ENTER": wenn die Eingabetaste gedrückt wurde
- "ESC": wenn die Escape-Taste gedrückt wurde
- "F0" .. "F63": wenn eine F-Taste gedrückt wurde
- "CHAR(%u)" (wobei %u einem unsigned integer entspricht): eine andere Taste wurde gedrückt, und der numerische Wert dieser Taste befindet sich zwischen den Klammern. Der numerische Werte für die Taste "a" ist z.B. 97. Und auch Ctrl-Tastenkombination sind damit abfragbar, so ist etwa Ctrl-A der numerische Wert 1, Ctrl-B der Wert 2, usw.
Am Ende eines Programmes ist es dann noch zweckmäßig, die Funktion stfl_reset() aufzurufen, um den Bildschirm entsprechend zu resetten. Mit diesem Wissen kann man schon ein nettes kleines Programm schreiben, das so eine typische Event Loop demonstriert, und zusätzlich führe ich auch noch ein paar weitere Funktionen ein:
static char form_str[] = "vbox\n" " @style_normal:bg=black,fg=white\n" " @info#style_normal:bg=blue,fg=yellow,attr=bold\n" " list[results]\n" " style_normal:bg=black,fg=white\n" " style_focus:fg=yellow,bg=blue,attr=bold\n" " pos_name[listposname]:\n" " pos[listpos]:0\n" " label#info\n" " .expand:h .height:1\n" " text[help]:\"q:Quit\"\n" " label\n" " .expand:h .height:1\n" " text[msg]:\"\"\n"; #include <stfl.h> #include <stdio.h> int main(void) { int quit = 0; struct stfl_form *f = stfl_create(form_str); do { const char * event = stfl_run(f, 0); if (!event) continue; if (strncmp(event,"CHAR(",5)==0) { unsigned int key; if (sscanf(event,"CHAR(%u)",&key)==1) { switch (key) { case 'q': quit = 1; break; case 'a': stfl_set(f, "msg", "you just pressed a"); break; case 'b': stfl_set(f, "msg", "you just pressed b"); break; case 'c': stfl_modify(f, "results", "replace_inner", "{list{listitem[item1] text:\"list item 1\"}{listitem[item2] text:\"list item 2\"}}"); stfl_set(f, "msg", "you just pressed c and replaced the content of the list"); break; case 'd': stfl_modify(f, "results", "replace_inner", "{list{listitem[line1] text:\"line 1\"}{listitem[line2] text:\"line 2\"}{listitem[line3] text:\"line 3\"}}"); stfl_set(f, "msg", "you just pressed d and replaced the content of the list"); break; case 'e': { const char * listpos = stfl_get(f, "listpos"); const char * listposname = stfl_get(f, "listposname"); char line[1024]; snprintf(line,sizeof(line),"listpos = %s listposname = %s", listpos, listposname); stfl_set(f, "msg", line); } } } else { char line[1024]; snprintf(line,sizeof(line),"you pressed: %s", event); stfl_set(f, "msg", line); } } } while (!quit); stfl_free(f); stfl_reset(); return 0; }
Was man hier sieht, ist eine klassische Event Loop, wobei auf die Tasten a bis e sowie q reagiert wird. Es werden ja nach Tastendruck ein Label bzw. die Liste modifiziert. Mit der Taste e enthält man zusätzlich noch Informationen über die aktuelle Auswahl in der Liste.
Man kann noch wesentlich mehr mit der STFL machen, und dieser Artikel soll nur einen kleinen Einblick in die grundsätzliche Funktionsweise von STFL bringen, um dem einen oder anderen unter euch das Entwickeln von eigenen, neuen Texttools schmackhaft zu machen. Für mehr Informationen sowie andere Widgets wie Eingabefelder oder mehrzeige Text Views ist es ratsam, in die STFL-Dokumentation sowie die existierenden Beispielprogramme reinzuschauen. Es lohnt sich.
Wer übrigens die STFL "C++iger" verwenden will, den kann ich nur auf meinen "stflpp"-Wrapper hinweisen, den man hier (Header) und hier (Source) findet (MIT/X-Lizenz, BTW). Die STFL selbst unterstützt neben C übrigens auch SPL, Ruby, Python und Perl.
Friday, July 28. 2006
This week, I put quite a lot of effort into pushing Liam further in terms of features. It is now a quite nice mail client, already. What you can currently do is send mails, reply to mails, forward mails (even as attachments, just like Thunderbird does it) and attach files to mails with a nice file browser. You can view mails, viewed mails will be marked as seen. You can mark already seen mails as unread. You can mark mails as deleted, and then expunge them from the mailbox. Mails marked as deleted can also be undeleted as long as haven't been expunged. When you reply to a message, it will be marked as "answered" on the server. This is also shown in the mailbox view. You can flag messages. You can define a "header filter" which describes which headers shall be shown in the mail view. If you don't define such a filter, all headers will be shown. And even documentation for everything is available, in DocBook format. This feature list might not look really long, but remember, this is all the achievement of less than a week!
If you want to try out Liam, just check it out from the SVN repo. Liam depends on several external packages, such as Ruby 1.8, STFL and RubyMail. STFL is noteworthy because currently you need this patch to fix a bug in STFL and to add a new widget and probably this and this patch if you have troubles building it. And don't forget to install swig before building STFL as it is required to build the Ruby bindings.
If you managed to get Liam running after all that, look at liamrc.example and create your own ~/.liamrc after it. As usual, feedback is welcome. If you're eager to help with Liam development, don't hesitate to look at the TODO list, hack away and send patches. So far, I would like to thank Michael Prokop for his huge amount of valuable input and for trying out Liam in this early stage.
Sunday, July 23. 2006
Today I spent a few hours of hacking on an IMAP mail client in Ruby which I codenamed "liam" (backwards for mail, haha, how funny). I based it on basically two libraries, namely Ruby's Net::IMAP, which provides a very complete (but a bit complicated) interface to IMAP, and Clifford's STFL, the Structured Terminal Forms Library, which is pretty new, but definitely the best ncurses widget library around.
The functionality is currently really simple: you start the program, it connects to your IMAP server, reads all available mailboxes from it, and lets the user decide which one to open. After selecting the right mailbox from the list, it is opened and the message envelopes from this mailbox are downloaded. Then, the user can again select the message he wants to view. Then, the message is downloaded and the raw message is presented to the user using the less(1) pager. The user can view this message. When he quits less, he is again presented with the list of messages in the mailbox. To return to the list of mailboxes, a simple press of the 'q' key is enough. From there, it is possible to select another mailbox, or to quit liam by again pressing 'q'.
This probably sounds pretty difficult, but it's extremely simple. To visualize this more efficiently, I prepared a simple screencast, which you can view here.
But what is my goal with this prototype? I don't know yet. It is definitely not here to replace mutt-ng (which is not dead yet, I'm currently preparing a "relaunch" since I now know my requirements much better than at the beginning of the project), but it's more a prototype to experiment on the future of terminal-based email clients. Sooner or later, mutt (and mutt-ng) will require a rewrite, and with this prototype I can find out what will be important for a new design, and where the stumbling blocks might be. Anyway, any feedback is welcome.
Update: more hacking on liam (including a new STFL widget), and a new screencast to demonstrate email viewing.
|