Skip to content

Full Circle Magazine 36 erschienen

Heute ist die 36. Ausgabe des englischsprachigen Magazins Full Circle Magazine erschienen. Themen dieser Ausgabe sind unter anderem

  • Command and Conquer: Farbige Shell
  • Anleitungen: Python-Programmierung – Teil 10, Bilder retuschieren mit GIMP – Teil 3, Google effizient nutzen
  • Review: Automating Linux & Unix System Administration
  • Interviews: Jo Shields und Michelle Hall
  • Ubuntu Games: Doom 3
  • Top 5: Scan-Anwendungen
  • News, Leserbriefe und mehr

Links: Webseite, Forum und Wiki

Somnia

Titel Somnia
Autor Christoph Marzi
Sprache Deutsch
Genre Fantasy
Herausgeber Heyne, 2008
Seitenanzahl 608

Scarlet Hawthorne taucht ohne Gedächtnis im Battery Park in New York auf. Verfolgt von den Wendigo, Wölfe, die sich aus Eiskristallen bilden, wird sie von Mistress Anthea Atwood aufgesammelt. Zusammen versuchen die beiden Scarlets Gedächtnis wiederzufinden und verfolgen dabei eine rätselhafte Spur von Schlafwandlern und Eistoten.

Somnia ist der vierte Teil der „Trilogie“ ;) um die uralte Metropole (Lycidas, Lilith und Lumen). Wer Nimmermehr und darin die Kurzgeschichte „Scarlet“ gelesen hat, weiß natürlich, wer Scarlet Hawthorne ist. Aber selbst wenn man das weiß, nimmt dies keinesfalls die Spannung.

Kritik kann ich kaum welche anbringen, nicht umsonst habe ich das Buch in weniger als einer Woche durchgelesen. Nach wie vor sind die Floskeln „Es gibt keine Zufälle!“ und „Fragen Sie nicht!“ im Buch vertreten, aber nicht mehr ganz so zahlreich, was wohl auch daran liegt, dass andere Charaktere die Hauptrolle spielen. Und nach wie vor wird die Geschichte aus einer allwissenden Ich-Perspektive von Scarlets Mentorin Mistress Atwood erzählt - mit den gleichen Nachteilen wie in der vorherigen drei Bänden. Aber man kann damit leben.

In „Somnia“ greift Marzi wieder zahlreiche Fabelgestalten auf und verbindet diese (mehr oder weniger geschickt). So haben die Arachniden wieder einen Gastauftritt, ebenso wie die Nekir und einige Engel. Dafür lernt man aber auch neue Gestalten wie die oben erwähnten Wendigo, eine geheimnisvolle Lady Solitaire sowie das Krokodil aus „Peter Pan“ kennen. Und natürlich dürfen auch Mr. Fox und Mr. Wolf nicht fehlen.

Das Ende ist einfach genial und Marzi deutet es im Nachwort schon an, dass Scarlet noch einige Abenteuer in Gotham erleben wird. Wer Fantasy mag und die ersten drei Bände sowieso verschlungen hat, muss „Somnia“ einfach lesen. Aber auch für alle anderen ist das Buch sicherlich ein guter Einstieg in die uralte Metropole.

Neues Spielzeug: Acer TravelMate 8471 Timeline

Es war ein langer Weg, bis ich mein neues Notebook in den Händen halten konnte. Nachdem ich erst das 13-Zoll-Gerät Vostro 1320 im Auge hatte, Dell aber keines herausrücken wollte, bin ich auf das Acer TravelMate 8471-943G25N Timeline gestoßen.

Acer TravelMate 8471 Timeline

Rohdaten

Hier mal die Rohdaten:

ProzessorIntel Core Duo SU9400
Speicher3072 MB
Display14,1 Zoll, matt
Auflösung1366x768
GrafikkarteIntel GMA 4500MHD
Festplatte250 GB, 5400rpm
LaufwerkDVD
Anschlüsse3x USB, 1x VGA, 5in1-Kartenleser, LAN
WLANIntel WiFi Link 5100
Breite/Tiefe/Höhe34,2/23,5/2,6 cm
Gewicht1,93 kg

Checkliste, was funktioniert

Was genau an dem Gerät alles funktioniert, muss ich nun erst langsam

erforschen. Hier meine aktuelle Checkliste:

  • WLAN: Wird erkannt, findet aber keine WLAN-Netze. Kein Test möglich.
  • Webcam: Funktioniert (mit Cheese getestet).
  • Touchpad: Funktioniert.
  • LAN: Funktioniert.
  • Bluetooth: Funktioniert.
  • DVD: Funktioniert (nach libdvdcss-Installation)
  • HD-Video: Funktioniert
  • Musik: Funktioniert (Ton ist aber sehr leise, Kopfhörer besser)
  • Composite: Funktioniert (Xfce-Composite, Compiz nicht getestet)
  • Kartenleser: ungetestet
  • Fingerprint-Sensor: ungetestet
  • Ruhezustand: Funktioniert (Bildschirm flackert dabei aber kurz schwarz)
  • Standby: Funktioniert nicht (geht in Standby, beim Aufwachen schaltet sich Notebook aus)

Tasten

  • WLAN: laut dmesg erkennt er es, die Taste leuchtet aber nicht und WLAN geht auch nicht aus
  • Laustärke: ja
  • Helligkeit: ja
  • Energiespartaste: tut irgendetwas
  • Touchpad: ja
  • Acer-Backup: nein
  • Multimedia-Tasten: ja
  • Systemeigenschaften: nein
  • Bluetooth: ja
  • Bildschirm ausschalten: ja
  • VGA-Ausgang: kein VGA-Monitor, kann also nicht getestet werden
  • Schlafmodus: nein
  • Dollar/Euro-Taste: nein

Besonderheiten

acerhk-Modul

Da mein WLAN erkannt wird, bin ich nicht sicher, ob ich das acerhk-Modul überhaupt bauche. Aber selbst wenn, es lässt sich unter Lucid aufgrund des Bugs 456123 (der seit Karmic, also über einem halben Jahr, drin ist) nicht kompilieren. Interessanterweise kann ich auch die fixed-Version 0.5.35 von Moma mit der gleichen Fehlermeldung nicht kompilieren:

make[2]: *** No rule to make target `kernel/bounds.c', needed by `kernel/bounds.s'. 

Installation

Die Installation war etwas komplizierter, da die versteckte Windows-Wiederherstellungspartition dem Linux-Partitionierer Probleme bereitete. Dieser erkannte (z.B. gparted) keinerlei Partition, sondern für ihn waren die 250 GB nicht zugeteilt:

gparted

fdisk erkannte die Aufteilung dagegen korrekt, was aber nichts brachte:

   Gerät  boot.     Anfang        Ende     Blöcke   Id  System
/dev/sda1               1        1785    14336000   27  Unbekannt
/dev/sda2   *        1785        1798      102400    7  HPFS/NTFS
/dev/sda3            1798       38914   298130432    7  HPFS/NTFS

Ich habe dann testdisk von einer Live-CD gestartet und die Partitionstabelle neu geschrieben. Das Programm konnte aber nur die Einträge für die Wiederherstellungspartition /dev/sda1 und die Boot-Partition /dev/sda2 korrekt erstellen, die Windows-Partition /dev/sda3 (also das ganze System) ging verloren. Beim nächsten Start startete dann das Acer-Recovery-System und stellte die Windows-Installation wieder her. Das System war also nun auf dem gleichen Stand wie nach dem auspacken. Interessanterweise erkannte gparted nun aber die Partitionierung korrekt und ich konnte Xubuntu installieren.

Bootmanager

Durch obiges Installationswirrwarr habe ich nun zwei Windows-Booteinträge in Grub2. Einen für Windows Vista (welches gar nicht existiert) und einen für Windows 7, der auf die korrekte Bootpartition zeigt.

Ansonsten bin ich zufrieden mit dem Gerät. Vor allem Akkulaufzeit und Gewicht sind eine echte Verbesserung zu meinem vorherigen Benq Joybook 5200.

Free Music Charts April 2010

Diese Woche hat darkerradio wieder die Free Music Charts des Monats April vorgestellt.

Die Musik wird im Podcast ausführlich vorgestellt und die Top 15 des aktuellen Monats plus die Neuvorstellungen abgespielt. Auf der Webseite kann (und soll) jeder Hörer seine fünf Lieblingssongs wählen, damit die Charts nächsten Monat wieder mit guter Musik gefüllt sind.

Es gibt bei den gespielten Liedern viele gute Stücke zu hören. Alle Lieder unterliegen einer bestimmten Creative-Commons-Lizenz und können meist bei Jamendo heruntergeladen werden. Der Stil reicht dabei im übrigen von Electro über Pop, Rock, Metal und sonstige Musikrichtungen. Wenn was Gutes dabei ist, kann man den Künstlern auch eine Vergütung zukommen lassen.

Tomaten-Zucchini-Schnitzel

Rezept Tomaten-Zucchini-Schnitzel (3 Personen)
Zutaten 3 Schweineschnitzel
2 Zucchini
1 Dose (400g) gestückelte Tomaten
1 kl. Zwiebel
200 ml Sahne
Oregano, Rosmarin, Salz, Pfeffer
Zeit 45 min

Die Schnitzel waschen, klopfen, halbieren (es sind nun sechs kleine Schnitzel), mit Salz und Pfeffer würzen und dann in heißem Öl von beiden Seiten anbraten und zur Seite stellen.

Die Zucchini waschen, schälen, in ca. 3-4 mm dünne Scheiben schneiden. Diese dann in der gleichen Pfanne wie zuvor braun anbraten, herausnehmen und beiseite stellen.

Die Zwiebel fein hacken und in der Pfanne andünsten. Mit der Sahne ablöschen und die Tomatenstückchen dazu geben. Mit Oregano, Rosmarin, Salz und Pfeffer nach Wunsch abschmecken. Das Ganze köcheln lassen, bis es eindickt oder mit Saucenbinder nachhelfen.

Ein kleine Menge der fertigen Tomatensauce in eine Auflaufform geben, sodass der Boden leicht bedeckt ist. Danach drei Schnitzel in die Auflaufform geben, darüber die Scheiben der ersten Zucchini stapeln, danach die restlichen drei Schnitzel und die Scheiben der zweiten Zucchini. Das alles mit der restlichen Tomatensauce übergießen, sodass alles schön bedeckt ist.

Die Auflaufform in den Ofen stellen und bei 200 Grad für 35 Minuten backen.

Dazu passen ganz gut Bandnudeln.

Nets, Puzzles, and Postmen

Titel Nets, Puzzles, and Postmen
Autor Peter M. Higgins
Sprache English
Genre Sachbuch
Herausgeber Oxford University Press, 2009
Seitenanzahl 247

Was haben Sudoku, das Internet, Zugverbindungen und ein Labyrinth gemeinsam? Sie lassen sich durch Netze und Graphen beschreiben und besser verstehen. Peter M. Higgins beschreibt in seinem Buch auf sehr anschauliche Weise, wie man mit Hilfe der Graphentheorie solche netzartigen Strukturen beschreiben kann.

Dabei wird auf Klassiker wie das Königsberger Brückenproblem, das Traveling Salesman Problem und das Chinese Postman Problem eingegangen, aber auch neue Themen behandelt wie die Lösung eines Sudoku, die sozialen Verbindungen auf Partys und dem Weg aus einem Labyrinth.

Das Buch ist recht anschaulich beschrieben und richtet sich nicht zwingend an studierte Mathematiker, sondern an alle, die ein bisschen Spaß an mathematischen Spielereien haben. Kleine Beispiele in den einzelnen Kapitel sorgen für ein besseres Verständnis und im Anhang gibt es für die Alteingesessenen dann doch noch ausführliche mathematische Erklärungen und Beweise.

Wer sich also für Graphen- und Automatentheorie interessiert – oder einfach sein Sudoku nicht gelöst bekommt – kann ruhig einmal einen Blick in das Buch werfen. In deutscher Sprache ist es leider nicht erhältlich.

Dells Geschäftpraktiken

Wie bereits vor drei Tagen erwähnt, hat mich Dell etwas geärgert. Ich habe daraus die Konsequenzen gezogen und die Bestellung storniert. Was genau war der Grund?

Die gesamte Kommunikation mit Dell lief von Anfang an falsch. Ich hatte Interesse an einem Dell Vostro 1320. Telefonisch konnte ich das Gerät nicht bestellt, da mir der Support-Mitarbeiter sagte, dass das Gerät nicht mehr ausgeliefert wird. Zum Nachfolgegerät Vostro 3300 gibt es aber nirgends irgendwelche Erfahrungsberichte, sodass mir das zu heikel war. Daneben ist der Prozessor für den verbauten Akku in dem Gerät überdimensioniert, sodass die Laufzeit viel zu niedrig ist.

Da man das Vostro 1320 online bestellen konnte, habe ich das am Sonntag, den 21.03.2010, getan. Eine Eingangsbestätigung der Bestellung gab es von Dell umgehend. Am 24.03.2010 hat Dell dann von meinem Konto die 600 Euro für das Gerät per Lastschrift abgebucht. Ohne Vorauszahlung läuft bei Dell leider nichts.

Am 25.03.2010 kam die Bestätigung des Auftrages (nicht des Geldeingangs!) und als voraussichtlicher Liefertermin war der 16.04.2010 angegeben. Eine Woche später, am 31.03.2010, kam dann die Bestellbestätigung (Geldeingang wurde also verzeichnet) und es hieß

„Das voraussichtliche Lieferdatum für diese Bestellung ist am oder vor dem 2010-04-16.“

Da ich bis zum 14.04.2010 noch nichts von Dell gehört hatte, habe ich wie bereits geschrieben den Bestellstatus noch einmal überprüft und der voraussichtlicher Liefertermin war plötzlich der 01.06.2010.

Eine E-Mailanfrage beim Support am gleichen Tag mit Antwort am Folgetag bestätigte den voraussichtlichen Liefertermin, nannte aber keine konkreten Gründe, wieso das Gerät sich um 1 1/2 Monate verspäten sollte. Es wurde nur unspezifisch von wahrscheinlich nicht lagernden Komponenten geredet.

Am 15.04.2010 habe ich dann die Bestellung storniert, was am nächsten Tag mit dem Hinweis bestätigt wurde, dass ich das eingezogene Geld bei meiner Bank widerrufen soll. In meinen Augen eine Unverschämtheit!

Was ist nun also an Dells Handhabung zu bemängeln:

  1. Die Support-Hotline gibt falsche Auskünfte und/oder die Webseite gibt falsche Auskünfte. Zum einen wegen der Auslieferung des Vostro 1320, zum anderen weil die Hotline meinte, dass es das Vostro 1220 nur als spiegelnde Ausführung gibt, was ebenfalls nicht stimmt. (Das Gerät war das „Ersatzgerät“, da es das Vostro 1320 telefonisch nicht gab.)
  2. Man muss per Vorauskasse zahlen, sodass Dell in meinem Fall 2 1/2 Monate 600 Euro behalten hätte, ohne dass ich eine Gegenleistung in dieser Zeit erwarten kann. Wenn sie das mit sagen wir 100 Kunden pro Tag machen, kommt ein stolzes Sümmchen zusammen, was man in der Zeit kurzfristig anlegen kann.
  3. Aufgrund der langen Wartezeit würde mich auch interessieren, wie lange ein Händler rein rechtlich mit der Lieferung in Verzug geraten darf, vor allem in Hinblick auf das bereits bezahlte Geld. Im obigen Fall „fehlen“ mir fast vier Wochen lang 600 Euro auf meinem Konto, über die ich nicht verfügen kann.
  4. Über Lieferverzögerungen wird man nicht informiert! Weder darüber, dass sich eine Lieferung verzögert, noch was die Gründe dafür sind. Man muss jeden Tag auf der Webseite nachschauen und aktiv den Bestellstatus überprüfen.
  5. Die größte Unverschämtheit ist aber, dass ich nun zu meiner Bank laufen darf, um die Abbuchung zu widerrufen. Dell hat meine Kontodaten – schließlich haben sie das Geld dort auch abgebucht – und sie könnten das Geld locker zurück überweisen. Aber auf die Art kann Dell über mein Geld ja noch ein paar weitere Tage verfügen ohne eine Gegenleistung zu erbringen.
  6. Auch hier wäre ich an der rechtlichen Lage interessiert. Ich bin vor allem gespannt, ob mir hierbei bei meiner Bank Kosten entstehen und falls ja, wie ich diese bei Dell einfordern kann.

Alles in allem zählt Dell bei mir ab sofort nicht mehr zu den seriösen Anbietern im Computergeschäft und die Firma ist für bis auf weiteres von meiner Liste der unterstützenswerten Firmen gestrichen. Die Zusammenarbeit mit Canonical wegen Ubuntu ist sicherlich nett – aber auf einem nicht gelieferten Notebook kann ich mit dem folglich nicht gelieferten Ubuntu auch nichts anfangen.

MagDriva 3.2009 (vor 4 Monaten) erschienen

Bereits am 25.12.2009 ist die neueste Ausgabe von MagDriva, dem Mandriva-Community-Magazin, erschienen (die interessanterweise nicht einmal auf der Wiki-Seite erwähnt wird). Das Magazin gibt es als DinA5-Version im Querformat (ca. 3 MB).

Inhalt der Ausgabe 3.2009 sind u.a.

  • Nachrichten zu MandrivaUser.de
  • Vorstellung eines MandrivaUsers
  • Die MUD-Editionen vorgestellt
  • Synergy – Eine Programmvorstellung
  • Conky, der ultimative Systemmonitor – Teil 2
  • x2go – Ein Terminalserver-projekt
  • Serverinformationen grafisch aufbereitet
  • Vorstellung: Ubuntu 9.10 „Karmic Koala“
  • Vorstellung: Fedora 12
  • Berichte von der OpenSource-Expo

Besonders nett fand ich im übrigen den Nachruf auf Yalm. Danke wobo und tuxdriver!

Ich habe nun endlich auch einmal den Newsfeed von MandrivaUser.de abonniert, damit ich solche Ankündigungen nicht immer verpasse. Vor allem ist es sicherlich auch so ganz interessant zu sehen, was die anderen Linux-Communitys so treiben.

Im Forum werden auch noch Beiträge für die MagDriva-Ausgabe 1.2010 gesucht.

Kurz mal aufregen

Am 21.03.2010 habe ich mir bei Dell ein Notebook bestellt, was eigentlich für meine Reisen im Mai herhalten sollte. Nach ca. einer Woche war das Geld vom Konto abgebucht, als voraussichtliches Lieferdatum war der 16.04.2010 angegeben. Ein Monat für den Zusammenbau eines Notebooks fand ich zwar recht viel, aber okay.

Nun habe ich mal den Bestellstatus angeschaut und da steht als voraussichtliches Lieferdatum der 01.06.2010 ... Was machen die denn mit dem Ding? Wird da jeder Kondensator handgefertigt und erst extra für mich aufs Mainboard gelötet? Der Auftrag befindet sich aber immer noch „In Bearbeitung“ seit Ende März. Das heißt, es wurde nicht einmal damit begonnen, das Gerät zu produzieren.

Ich überlege nun, ob ich den Auftrag storniere und mein Geld in ein anderes Gerät investiere, welches ich sicher (!) bis Ende April hier vor mir stehen habe ...

Navigation in der Tcl-Shell

In der Tcl-Shell tclsh ist es ziemlich nervend, dass es keine History gibt, durch die man mit den Pfeiltasten navigieren kann. Ganz unverständlich ist, dass man den Cursor nicht einmal mit den Pfeiltasten nach rechts und links bewegen kann. Es erscheinen hier per Standard nur die Zeichencodes der Tasten: ^[[A^[[B^[[C^[[D.

Im Tcler's Wiki habe ich ein Skript von Adly Abdullah gefunden, was dies ausbessert. Das Skript muss nur per Copy&Paste in eine Datei ~/bin/tclline.tcl (oder wo immer man es haben will) gespeichert und ausführbar gemacht werden. Danach muss noch die Zeile

source ~/bin/tclline.tcl 

in die Datei ~/.tclshrc eingefügt werden.

Unten stehend ist meine Version des Skriptes, da ich noch kleinere Änderungen vornehmen musste/wollte:

  • Prompt beginnt mit "%", nicht ">"
  • [Pos1] und [Ende] haben bei mir andere Zeichencodes
  • [Strg]+[C] und [Strg]+[D] beenden die Shell
  • Navigation über mehrere Wörter per [Strg]+[Cursor rechts] bzw. [Strg]+[Cursor links]
  • beim Beenden der Shell wird der Bildschirminhalt nicht mehr gelöscht

#! /usr/bin/env tclsh
# tclline: An attempt at a pure tcl readline.

# Use Tclx if available:
catch {
  package require Tclx

  # Prevent sigint from killing our shell:
  signal ignore SIGINT
}

# Initialise our own env variables:
foreach {var val} {
  PROMPT "% "
  HISTORY ""
  HISTORY_BUFFER 100
  COMPLETION_MATCH ""
} {
  if {![info exists env($var)]} {
      set env($var) $val
  }
}
foreach {var val} {
  CMDLINE ""
  CMDLINE_CURSOR 0
  CMDLINE_LINES 0
  HISTORY_LEVEL -1
} {
  set env($var) $val
}
unset var val

array set ALIASES {}
set forever 0

# Resource & history files:
set HISTFILE $env(HOME)/.tclline_history
set RCFILE $env(HOME)/.tcllinerc

proc ESC {} {
  return "\033"
}

proc shift {ls} {
  upvar 1 $ls LIST
  set ret [lindex $LIST 0]
  set LIST [lrange $LIST 1 end]
  return $ret
}

proc readbuf {txt} {
  upvar 1 $txt STRING
  
  set ret [string index $STRING 0]
  set STRING [string range $STRING 1 end]
  return $ret
}

proc goto {row {col 1}} {
  switch -- $row {
      "home" {set row 1}
  }
  print "[ESC]\[${row};${col}H" nowait
}

proc gotocol {col} {
  print "\r" nowait
  if {$col > 0} {
      print "[ESC]\[${col}C" nowait
  }
}

proc clear {} {
  print "[ESC]\[2J" nowait
  goto home
}

proc clearline {} {
  print "[ESC]\[2K\r" nowait
}

proc getColumns {} {
  set cols 0
  if {![catch {exec stty -a} err]} {
      regexp {rows \d+; columns (\d+)} $err -> cols
  }
  return $cols
}

proc prompt {{txt ""}} {
  global env
  
  set prompt [subst $env(PROMPT)]
  set txt "$prompt$txt"
  foreach {end mid} $env(CMDLINE_LINES) break
  
  # Calculate how many extra lines we need to display.
  # Also calculate cursor position:
  set n -1
  set totalLen 0
  set cursorLen [expr {$env(CMDLINE_CURSOR)+[string length $prompt]}]
  set row 0
  set col 0
  
  # Render output line-by-line to $out then copy back to $txt:
  set found 0
  set out [list]
  foreach line [split $txt "\n"] {
      set len [expr {[string length $line]+1}]
      incr totalLen $len
      if {$found == 0 && $totalLen >= $cursorLen} {
          set cursorLen [expr {$cursorLen - ($totalLen - $len)}]
          set col [expr {$cursorLen % $env(COLUMNS)}]
          set row [expr {$n + ($cursorLen / $env(COLUMNS)) + 1}]
          
          if {$cursorLen >= $len} {
              set col 0
              incr row
          }
          set found 1
      }
      incr n [expr {int(ceil(double($len)/$env(COLUMNS)))}]
      while {$len > 0} {
          lappend out [string range $line 0 [expr {$env(COLUMNS)-1}]]
          set line [string range $line $env(COLUMNS) end]
          set len [expr {$len-$env(COLUMNS)}]
      }
  }
  set txt [join $out "\n"]
  set row [expr {$n-$row}]
  
  # Reserve spaces for display:
  if {$end} {
      if {$mid} {
          print "[ESC]\[${mid}B" nowait
      }
      for {set x 0} {$x < $end} {incr x} {
          clearline
          print "[ESC]\[1A" nowait
      }
  }
  clearline
  set env(CMDLINE_LINES) $n
  
  # Output line(s):
  print "\r$txt"
  
  if {$row} {
      print "[ESC]\[${row}A" nowait
  }
  gotocol $col
  lappend env(CMDLINE_LINES) $row
}

proc print {txt {wait wait}} {
  # Sends output to stdout chunks at a time.
  # This is to prevent the terminal from
  # hanging if we output too much:
  while {[string length $txt]} {
      puts -nonewline [string range $txt 0 2047]
      set txt [string range $txt 2048 end]
      if {$wait == "wait"} {
          after 1
      }
  }
}

rename unknown _unknown
proc unknown {args} {
  global env ALIASES

  set name [lindex $args 0]
  set cmdline $env(CMDLINE)
  set cmd [string trim [regexp -inline {^\s*[^\s]+} $cmdline]]
  if {[info exists ALIASES($cmd)]} {
      set cmd [regexp -inline {^\s*[^\s]+} $ALIASES($cmd)]
  }
  
  set new [auto_execok $name]
  if {$new != ""} {
      set redir ""
      if {$name == $cmd && [info command $cmd] == ""} {
          set redir ">&@ stdout <@ stdin"
      }
      if {[catch {
          uplevel 1 exec $redir $new [lrange $args 1 end]} ret]
      } {
          return
      }
      return $ret
  }
  
  eval _unknown $args
}

proc alias {word command} {
  global ALIASES
  set ALIASES($word) $command
}

proc unalias {word} {
  global ALIASES
  array unset ALIASES $word
}

################################
# Key bindings
################################
proc handleEscapes {} {
  global env
  upvar 1 keybuffer keybuffer
  set seq ""
  set found 0
  while {[set ch [readbuf keybuffer]] != ""} {
      append seq $ch

      switch -exact -- $seq {
          "\[A" { ;# Cursor Up (cuu1,up)
              handleHistory 1
              set found 1; break
          }
          "\[B" { ;# Cursor Down
              handleHistory -1
              set found 1; break
          }
          "\[C" { ;# Cursor Right (cuf1,nd)
              if {$env(CMDLINE_CURSOR) < [string length $env(CMDLINE)]} {
                  incr env(CMDLINE_CURSOR)
              }
              set found 1; break
          }
          "\[D" { ;# Cursor Left
              if {$env(CMDLINE_CURSOR) > 0} {
                  incr env(CMDLINE_CURSOR) -1
              }
              set found 1; break
          }
          "\[OH" -
          "\[H" -
          "\[7~" -
          "\[1~" { ;# home
              set env(CMDLINE_CURSOR) 0
              set found 1; break
          }
          "\[3~" { ;# delete
              if {$env(CMDLINE_CURSOR) < [string length $env(CMDLINE)]} {
                  set env(CMDLINE) [string replace $env(CMDLINE) \
                      $env(CMDLINE_CURSOR) $env(CMDLINE_CURSOR)]
              }
              set found 1; break
          }
          "\[OF" -
          "\[F" -
          "\[K" -
          "\[8~" -
          "\[4~" { ;# end
              set env(CMDLINE_CURSOR) [string length $env(CMDLINE)]
              set found 1; break
          }
          "\[5~" { ;# Page Up }
          "\[6~" { ;# Page Down }
          "\[2~" { ;# Insert }
          "\[1;5C" { ;# Strg+Cursor right
              jumpToNonAlphaNum 1
              set found 1; break
          }
          "\[1;5D" { ;# Strg+Cursor left
              jumpToNonAlphaNum 0
              set found 1; break
          }
      }
  }
  return $found
}

# return true if word only contains alphanumeric characters
# the empty string will return 0
proc isAlphaNum { word } {
    return [regexp {^\w+$} $word]
}

# jump to the next non alphanumeric character in the current line
# if forward is not 0 we go to the right
# if forward is 0 we go to the left and will stop at the begin of a word
proc jumpToNonAlphaNum { forward } {

    global env
    set found 0

    # if the current string is not a alphanumeric character
    # we must go to the first alphanumeric character and start our search there
    if { ![isAlphaNum [string index $env(CMDLINE) $env(CMDLINE_CURSOR)]] } {
        if { $forward } {
            # search forward
            for { set ii $env(CMDLINE_CURSOR) } { $ii <= [string length $env(CMDLINE)] } { incr ii } {
                if { [isAlphaNum [string index $env(CMDLINE) $ii]] } {
                    set env(CMDLINE_CURSOR) $ii
                    set found 1
                    break
                }
            }
        } else {
            # search backward
            for { set ii $env(CMDLINE_CURSOR) } { $ii >= 0 } { incr ii -1 } {
                if { [isAlphaNum [string index $env(CMDLINE) $ii]] } {
                    set env(CMDLINE_CURSOR) $ii
                    set found 1
                    break
                }
            }
        }
    } else {
        set found 1
    }
    
    if { $found } {
        # now search for the first non alphanumeric character
        set found 0
        if { $forward } {
            # search forward
            for { set ii $env(CMDLINE_CURSOR) } { $ii <= [string length $env(CMDLINE)] } { incr ii } {
                if { ![isAlphaNum [string index $env(CMDLINE) $ii]] } {
                    set env(CMDLINE_CURSOR) $ii
                    set found 1
                    break
                }
            }
        } else {
            # search backward
            for { set ii $env(CMDLINE_CURSOR) } { $ii >= 0 } { incr ii -1 } {
                if { ![isAlphaNum [string index $env(CMDLINE) $ii]] } {
                    set env(CMDLINE_CURSOR) $ii
                    set found 1
                    break
                }
            }
        }
    }
    
    # if not found we jump to the start or end
    if { !$found } {
        if { $forward } {
            set env(CMDLINE_CURSOR) [string length $env(CMDLINE)]
        } else {
            set env(CMDLINE_CURSOR) 0
        }
    }          
}

proc handleControls {} {
  global env
  upvar 1 char char
  upvar 1 keybuffer keybuffer

  # Control chars start at a == \u0001 and count up.
  switch -exact -- $char {
      \u0004 -
      \u0003 { ;# ^c
          doExit
      }
      \u0008 -
      \u007f { ;# ^h && backspace ?
          if {$env(CMDLINE_CURSOR) > 0} {
              incr env(CMDLINE_CURSOR) -1
              set env(CMDLINE) [string replace $env(CMDLINE) \
                  $env(CMDLINE_CURSOR) $env(CMDLINE_CURSOR)]
          }
      }
      \u001b { ;# ESC - handle escape sequences
          handleEscapes
      }
  }
  # Rate limiter:
  set keybuffer ""
}

proc shortMatch {maybe} {
  # Find the shortest matching substring:
  set maybe [lsort $maybe]
  set shortest [lindex $maybe 0]
  foreach x $maybe {
      while {![string match $shortest* $x]} {
          set shortest [string range $shortest 0 end-1]
      }
  }
  return $shortest
}

proc handleCompletion {} {
  global env
  set vars ""
  set cmds ""
  set execs ""
  set files ""
  
  # First find out what kind of word we need to complete:
  set wordstart [string last " " $env(CMDLINE) \
      [expr {$env(CMDLINE_CURSOR)-1}]]
  incr wordstart
  set wordend [string first " " $env(CMDLINE) $wordstart]
  if {$wordend == -1} {
      set wordend end
  } else {
      incr wordend -1
  }
  set word [string range $env(CMDLINE) $wordstart $wordend]
  
  if {[string trim $word] == ""} return
  
  set firstchar [string index $word 0]
  
  # Check if word is a variable:
  if {$firstchar == "\$"} {
      set word [string range $word 1 end]
      incr wordstart
      
      # Check if it is an array key:
      set x [string first "(" $word]
      if {$x != -1} {
          set v [string range $word 0 [expr {$x-1}]]
          incr x
          set word [string range $word $x end]
          incr wordstart $x
          if {[uplevel #0 "array exists $v"]} {
              set vars [uplevel #0 "array names $v $word*"]
          }
      } else {        
          foreach x [uplevel #0 {info vars}] {
              if {[string match $word* $x]} {
                  lappend vars $x
              }
          }
      }
  } else {
      # Check if word is possibly a path:
      if {$firstchar == "/" || $firstchar == "." || $wordstart != 0} {
          set files [glob -nocomplain -- $word*]
      }
      if {$files == ""} {
          # Not a path then get all possibilities:
          if {$firstchar == "\[" || $wordstart == 0} {
              if {$firstchar == "\["} {
                  set word [string range $word 1 end]
                  incr wordstart
              }
              # Check executables:
              foreach dir [split $env(PATH) :] {
                  foreach f [glob -nocomplain -directory $dir -- $word*] {
                      set exe [string trimleft [string range $f \
                          [string length $dir] end] "/"]
                      
                      if {[lsearch -exact $execs $exe] == -1} {
                          lappend execs $exe
                      }
                  }
              }
              # Check commands:
              foreach x [info commands] {
                  if {[string match $word* $x]} {
                      lappend cmds $x
                  }
              }
          } else {
              # Check commands anyway:
              foreach x [info commands] {
                  if {[string match $word* $x]} {
                      lappend cmds $x
                  }
              }
          }
      }
      if {$wordstart != 0} {
          # Check variables anyway:
          set x [string first "(" $word]
          if {$x != -1} {
              set v [string range $word 0 [expr {$x-1}]]
              incr x
              set word [string range $word $x end]
              incr wordstart $x
              if {[uplevel #0 "array exists $v"]} {
                  set vars [uplevel #0 "array names $v $word*"]
              }
          } else {        
              foreach x [uplevel #0 {info vars}] {
                  if {[string match $word* $x]} {
                      lappend vars $x
                  }
              }
          }
      }
  }
  
  set maybe [concat $vars $cmds $execs $files]
  set shortest [shortMatch $maybe]
  if {"$word" == "$shortest"} {
      if {[llength $maybe] > 1 && $env(COMPLETION_MATCH) != $maybe} {
          set env(COMPLETION_MATCH) $maybe
          clearline
          set temp ""
          foreach {match format} {
              vars  "35"
              cmds  "1;32"
              execs "32"
              files "0"
          } {
              if {[llength [set $match]]} {
                  append temp "[ESC]\[${format}m"
                  foreach x [set $match] {
                      append temp "[file tail $x] "
                  }
                  append temp "[ESC]\[0m"
              }
          }
          print "\n$temp\n"
      }
  } else {
      if {[file isdirectory $shortest] &&
          [string index $shortest end] != "/"} {
          append shortest "/"
      }
      if {$shortest != ""} {
          set env(CMDLINE) \
              [string replace $env(CMDLINE) $wordstart $wordend $shortest]
          set env(CMDLINE_CURSOR) \
              [expr {$wordstart+[string length $shortest]}]
      } elseif {$env(COMPLETION_MATCH) != " not found "} {
          set env(COMPLETION_MATCH) " not found "
          print "\nNo match found.\n"
      }
  }
}

proc handleHistory {x} {
  global env

  set hlen [llength $env(HISTORY)]
  incr env(HISTORY_LEVEL) $x
  if {$env(HISTORY_LEVEL) > -1} {
      set env(CMDLINE) [lindex $env(HISTORY) end-$env(HISTORY_LEVEL)]
      set env(CMDLINE_CURSOR) [string length $env(CMDLINE)]
  }
  if {$env(HISTORY_LEVEL) <= -1} {
      set env(HISTORY_LEVEL) -1
      set env(CMDLINE) ""
      set env(CMDLINE_CURSOR) 0
  } elseif {$env(HISTORY_LEVEL) > $hlen} {
      set env(HISTORY_LEVEL) $hlen
  }
}

################################
# History handling functions
################################

proc getHistory {} {
  global env
  return $env(HISTORY)
}

proc setHistory {hlist} {
  global env
  set env(HISTORY) $hlist
}

proc appendHistory {cmdline} {
  global env
  set old [lsearch -exact $env(HISTORY) $cmdline]
  if {$old != -1} {
      set env(HISTORY) [lreplace $env(HISTORY) $old $old]
  }
  lappend env(HISTORY) $cmdline
  set env(HISTORY) \
      [lrange $env(HISTORY) end-$env(HISTORY_BUFFER) end]
}

################################
# main()
################################

proc rawInput {} {
  fconfigure stdin -buffering none -blocking 0
  fconfigure stdout -buffering none -translation crlf
  exec stty raw -echo
}

proc lineInput {} {
  fconfigure stdin -buffering line -blocking 1
  fconfigure stdout -buffering line
  exec stty -raw echo
}

proc doExit {{code 0}} {
  global env HISTFILE
  
  # Reset terminal:
  print "\n" nowait
  lineInput
  
  set hlist [getHistory]
  if {[llength $hlist] > 0} {
      set f [open $HISTFILE w]
      foreach x $hlist {
          # Escape newlines:
          puts $f [string map {
              \n "\\n"
              "\\" "\\b"
          } $x]
      }
      close $f
  }
  
  exit $code
}

if {[file exists $RCFILE]} {
  source $RCFILE
}

# Load history if available:
if {[llength $env(HISTORY)] == 0} {
  if {[file exists $HISTFILE]} {
      set f [open $HISTFILE r]
      set hlist [list]
      foreach x [split [read $f] "\n"] {
          if {$x != ""} {
              # Undo newline escapes:
              lappend hlist [string map {
                  "\\n" \n
                  "\\\\" "\\"
                  "\\b" "\\"
              } $x]
          }
      }
      setHistory $hlist
      unset hlist
      close $f
  }
}

rawInput

# This is to restore the environment on exit:
# Do not unalias this!
alias exit doExit

proc tclline {} {
  global env
  set char ""
  set keybuffer [read stdin]
  set env(COLUMNS) [getColumns]
  
  while {$keybuffer != ""} {
      if {[eof stdin]} return
      set char [readbuf keybuffer]
      if {$char == ""} {
          # Sleep for a bit to reduce CPU time:
          after 40
          continue
      }
      
      if {[string is print $char]} {
          set x $env(CMDLINE_CURSOR)
          
          if {$x < 1 && [string trim $char] == ""} continue
          
          set trailing [string range $env(CMDLINE) $x end]
          set env(CMDLINE) [string replace $env(CMDLINE) $x end]
          append env(CMDLINE) $char
          append env(CMDLINE) $trailing
          incr env(CMDLINE_CURSOR)
      } elseif {$char == "\t"} {
          handleCompletion
      } elseif {$char == "\n" || $char == "\r"} {
          if {[info complete $env(CMDLINE)] &&
              [string index $env(CMDLINE) end] != "\\"} {
              lineInput
              print "\n" nowait
              uplevel #0 {
                  global env ALIASES
                  
                  # Handle aliases:
                  set cmdline $env(CMDLINE)
                  set cmd [string trim [regexp -inline {^\s*[^\s]+} $cmdline]]
                  if {[info exists ALIASES($cmd)]} {
                      regsub -- "(?q)$cmd" $cmdline $ALIASES($cmd) cmdline
                  }
                  
                  # Perform glob substitutions:
                  set cmdline [string map {
                      "\\*" \0
                      "\\~" \1
                  } $cmdline]
                    
                  # Don't substitute * and ~ in braces:
                  foreach x [regexp -inline -all -indices {{.*?}} $cmdline] {
                      foreach {i n} $x break
                      set s [string range $cmdline $i $n]
                      
                      set s [string map {
                          "*" \0
                          "~" \1
                      } $s]
                      set cmdline [string replace $cmdline $i $n $s]
                  }
                    
                  while {[regexp -indices \
                      {([\w/\.]*(?:~|\*)[\w/\.]*)+} $cmdline x]
                  } {
                      foreach {i n} $x break
                      set s [string range $cmdline $i $n]
                      set x [glob -nocomplain -- $s]
                      
                      # If glob can't find anything then don't do
                      # glob substitution, pass * or ~ as literals:
                      if {$x == ""} {
                          set x [string map {              
                              "*" \0
                              "~" \1
                          } $s]
                      }
                      set cmdline [string replace $cmdline $i $n $x]
                  }
                  set cmdline [string map {
                      \0 "*"
                      \1 "~"
                  } $cmdline]
                  
                  # Run the command:
                  catch $cmdline res
                  if {$res != ""} {
                      print "$res\n"
                  }
                  
                  # Append HISTORY:
                  set env(HISTORY_LEVEL) -1
                  appendHistory $env(CMDLINE)
                  
                  set env(CMDLINE) ""
                  set env(CMDLINE_CURSOR) 0
                  set env(CMDLINE_LINES) {0 0}
              }
              rawInput
          } else {
              set x $env(CMDLINE_CURSOR)
              
              if {$x < 1 && [string trim $char] == ""} continue
              
              set trailing [string range $env(CMDLINE) $x end]
              set env(CMDLINE) [string replace $env(CMDLINE) $x end]
              append env(CMDLINE) $char
              append env(CMDLINE) $trailing
              incr env(CMDLINE_CURSOR)
          }
      } else {
          handleControls
      }
  }
  prompt $env(CMDLINE)
}
tclline

fileevent stdin readable tclline
vwait forever
doExit