October 31st, 2008

Qt4 in Lisp?!

Imagine Qt4 bindings for Lisp that are:

  1. Officially supported
  2. Thoroughly documented, with tons of examples
  3. Cross-platform
  4. Consistently up-to-date

Keep dreaming, right? Your first thought might be to check CLiki for some Common Lisp bindings. There is indeed a Qt project there. Except at a glance, it's listed as working only for CMUCL. And the last update was 2003. And the download link is 404'd. Oops!

But there's a link to a some QT4 bindings, CFFI style. Much more promising. Except... last update: March 2007. "Project status: dead." Never mind. There's also this and maybe a few others you could Google up, but I haven't tried them and I'm not going to.

Kudos to the people who wrote the above; providing bindings for all of Qt4 is a daunting task and it's no wonder it's not done for Common Lisp. Doesn't change the reality of the fact that Qt4 (like many other libraries) is a no-go in Common Lisp. There's nothing stopping Common Lisp from having great libraries except lack of manpower; but lack of manpower and lack of mature, tested, up-to-date libraries is still a real problem when you want to write an application today.

Enter Clojure

Luckily, it turns out there ARE Qt4 bindings that you can use from Lisp. That Lisp is Clojure, and the bindings are Qt Jambi. Many people are excited about Clojure nowadays, and this is one big reason (of many). It's hard to beat Java when it comes to libraries, and Clojure gets them all for free.

Let's try to write one of the official Qt4 examples / tutorials in Clojure and see how it goes. Keep in mind that I'm still learning Clojure, so this may be far from ideal, but it should give you a bit of a taste of Clojure and Qt Jambi if you've never seen it before.

To spoil the ending a bit: It works! In Vista and Linux (I don't have an OS X box to try, but it'll probably work there too):

Clojure Qt4 demo Clojure Qt4 demo

Setup

To set up Clojure, follow the directions on the Clojure wiki. Be sure to get the SVN version of Clojure, because it's being developed very rapidly and is often updated many times per week. You can also check out some recent posts on Bill Clementson's blog, and this movie on LispCast which walks through setting up Clojure and Emacs/SLIME.

To make Qt Jambi available to Clojure, you may have some options. If you use Linux, your distro may make this available via your package manager. Otherwise go to the Qt download site and download "Qt for Java" for your OS. This is a freaking huge download, but it includes all the documentation and source code and plugins; all you need out of it are a couple of JAR files. There are two JARs you need, one cross-OS and one specific to your OS.

You have to put these JARs on your CLASSPATH so Clojure can find them. There are lots of ways to do this; one way is on the commandline:

java -cp qt-jambi-4.4.3_01.jar:qtjambi-linux32-gcc-4.4.3_01.jar:clojure-lang-1.0-SNAPSHOT.jar clojure.lang.Repl

There's also add-classpath in Clojure, which lets you edit the classpath while Clojure is running. I was unable to get this to work with Qt Jambi, but I didn't try very hard. In reality I just use a shell script so I can dump a bunch of JARs into a directory, and they will all be added to CLASSPATH automatically when I start Clojure.

That's it. Installing libraries in Clojure couldn't be much easier.

The Code

Here's the complete example code for download. Let's walk through it a bit. First we have to import the Qt Jambi apps into Clojure. Qt Jambi is on our CLASSPATH and available to Clojure, but we still have to explicitly import the bits of it we want:

(ns clojure-qt4-demo
  (:import (com.trolltech.qt.gui QApplication QPushButton QFont QFont$Weight)
           (com.trolltech.qt.core QCoreApplication)))

ns creates a new namespace, and provides convenience syntax to import Java classes at the same time. It can also import other Clojure files or Clojure libraries, among other things.

Note immediately how this is nicer than Java. In Java you would be writing something like

import com.trolltech.qt.gui.QApplication;
import com.trolltech.qt.gui.QPushButton;
import com.trolltech.qt.gui.QFont;
...

And so on, over and over. If you're lucky you have a bloated IDE to type those things for you. In Clojure, macros let us factor out the repetition; we name the big long path once, and pluck out the things we want. The other option in Java is to import com.trolltech.qt.gui.*, which pollutes your namespace unnecessarily; Clojure lets us succinctly take just what we want.

One thing I couldn't find documentation for is the way to import a static inner subclass of another class. To do this you have to use syntax like SomeClass$SomeSubClass. My first thought was QFont/Weight or QFont.Weight, but those don't work.

If you're typing all of this at a REPL, you now have to do:

(in-ns 'clojure-qt4-demo)

to switch to the namespace we just created. Next we set up some convenience functions:

(defn init []
  (QApplication/initialize (make-array String 0)))

In Qt4, the first thing you always do is initialize QApplication. QApplication is essentially a singleton class, and it has to be initialized via QApplication.initialize(), a static method call, before you do anything else. In Clojure this becomes (QApplication/initialize), which says to call the function or static method called "initialize" in the namespace called "QApplication". Static methods in Java become functions in a class-namespace in Clojure.

This function expects an array of Strings, which in a normal app would be commandline parameters. In Clojure we can make a native Java Array of Strings via make-array. I just pass in an empty one because I'm running this from a REPL. Note, a native Java Array of Strings is different from a Clojure persistent array, [ ]. This is a bit nasty, but necessary for Java interop. Most of the time you will be able to get by with [ ]. Next:

(defn exec []
  (QApplication/exec))

This function (again a static method call) is generally the LAST thing you do in a Qt application. It starts displaying widgets and fires up the event loop.

A gotcha here is that certain things must happen in a certain order. A Qt app looks something like this:

  1. QApplication.initialize()
  2. Initialize and set up all of your widgets etc.
  3. QApplication.exec()
  4. Either the program exits, or go to step 1 to start over.

For example if you try to exec() before you initialize(), it does nothing at all. If you call initialize() more than once, it dies with a RuntimeException. When I was playing around with this at the REPL, it was very common that I called initialize multiple times by mistake. It's often safe to swallow the exception though.

All of that bookkeeping really wants to be made into a macro. Here's a simple macro:

(defmacro qt4 [& rest]
  `(do
     (try (init) (catch RuntimeException e# (println e#)))
     ~@rest
     (exec)))

This kind of thing lets you avoid the insanity of Java, which forces you to re-type exception-handling code and other boilerplate code over and over and over. You can abstract that all away in Clojure.

Macros in Clojure are much like Common Lisp macros. ~@ is a splicing-unquote, like ,@ in Common Lisp. Another interesting thing here is e#, which is a shorthand way to create a gensym. It creates a unique symbol; this prevents our macro from stepping on the toes of any other symbol called "e".

Using this macro we can finish our simple example:

(defn hello-world []
  (qt4
   (let [app (QCoreApplication/instance)
         button (new QPushButton "Go Clojure Go")]
     (.. button clicked (connect app "quit()"))
     (doto button
       (setFont (new QFont "Deja Vu Sans" 18 (.. QFont$Weight Bold value)))
       (setWindowTitle "Go Clojure Go")
       (resize 250 100)
       (show)))))

The hello-world function is pretty straightforward. It makes a button, sets up an event handler that closes our app when the button is clicked, then sets its title and font, resizes it and shows it. Note how seamless this is. Aside from all the new's and .'s, you could barely tell this was Java. It's all tasty Lisp.

Clojure has a bunch of simple convenience macros that make writing Java less painful, two of which I show here. One of the biggest problems with Java is its extremely verbose and punctuation-heavy C-like syntax. Compare:

button.resize(250, 100);
button.setFont(new QFont("Deja Vu Sans", 18, QFont.Weight.Bold.value()));
button.setWindowTitle("Go Clojure Go");
button.show();

Look how much repetition there is in the code. button.blah, button.blah, button.blah. Repetition is bad. Clojure has the doto macro which says "take this thing, and do a bunch of stuff to it" without having to retype it every time:

(doto button
  (resize 250 100)
  (setFont (new QFont "Deja Vu Sans" 18 (.. QFont$Weight Bold value)))
  (setWindowTitle "Go Clojure Go")
  (show))

Another macro is .. which says "take these things and put dots between everything", chaining method calls. So instead of QFont.Weight.Bold.value(), you can say (.. QFont$Weight Bold value). Handy.

To run this from a REPL, type

(hello-world)


Verdict

This app of course is a silly toy and just scratches the surface. But in my opinion, Clojure already beats all other Lisps by providing access libraries like this one. This is one of its killer features for me. The fact that you can seamlessly and easily use any Java library from Clojure is pretty amazing.

Why would you want to run Qt4 apps from Lisp anyways? Java (and thus Clojure) already have other GUI frameworks, like Swing and SWT and AWT, so why bother with this? Well, a few reasons...

  1. Because I can.
  2. Qt4 has a lot of impressive features nowadays. Run this Qt Jambi example to see some of those features.
  3. Qt's design may appeal more to you than Swing's, which can be clunky at times.
  4. Maybe you want to integrate well into KDE. I first started exploring GUI programming in Clojure when trying to make an icon sit in the system tray, and while Swing can do this, it's a bit painful to get it to look just right. It's trivial to do in Qt4.
  5. If I'm forced to write a Qt4 app, I'd much rather write it in a highly interactive and iterative way using Clojure than struggle with a write/compile/debug/recompile/wait-for-hours cycle. At the very least Clojure would be ideal for prototyping.
  6. Because I can!

Verdict: Clojure rules. But Java interop is just one reason it rules. Clojure has native Perl/Ruby-like reader support for hashes/arrays/sets/regexes/etc., support for easy and safe concurrency, cross-platform-ness galore, and a much more modern feel than Common Lisp. It's being worked on by some very smart people and the community is vibrant, enthusiastic, and welcoming. It has all the strengths of Java and Common Lisp, and very few of their weaknesses.

October 27th, 2008

Westinghouse: behind the scenes (the horrors of a call center)

Recently someone purporting to be a phone rep from a Westinghouse call center left me a comment. After the months of pain, I appreciate getting some more information from behind the scenes. Given how many times I called Westinghouse, chances are good I maybe even talked to this guy once or twice (so hello again, anonymous Westinghouse guy!).

The situation at Westinghouse is largely as I imagined. Here is the comment, and my response follows.

Heads up for everyone ever trying to deal with a Westinghouse RMA.

I “currently” work for the call center for Westinghouse. It is not our fault the service center will not ship you a tv, or even tell us why. Our database only lists information that has been typed in by other tech support reps, and occasionally things like a logged reciept, tracking number, or RMA number. And yes, it is true that we cannot transfer you to a manager, they do in fact have to call you back. Also, we literally do not have access to any contact information for the corporate office or the RMA center in any way, and even if we did, we are expressly forbidden from given it out. You'd have a much easier time searching for the contact on Google. We also have no way to change any of the Warranty policies, or give you free shipping labels for RMA. Those don't even come from a “manager” at the call center, that all is done through corporate.

Basically, 95% of all of the RMA issues arise from the fact that a)the Westinghouse corporate warranty policy does not allow us any deviation on our level and b)the RMA center is the slowest, shittiest facility I have ever dealt with. Last I heard, there were litterally 5 people working there. Also, the fact is, there are far more returned RMA tvs than there are available to ship back to customers, and that is causing most of the shipping problems. But please don't call us at the customer service and expect us to be able get you a tv faster. We are in NY and ME, and the service center is in TX, we cannot “walk downstairs” like some people would like to think.

If you do want to buy a Westinghouse tv for some reason, please PLEASE purchase it at Best Buy. We have an exclusive deal with Best Buy that if your tv breaks during the warranty period, you can call us to get a return authorization and bring it back to the store directly and get an exchange/store credit, even 11 months 3 weeks and 6 days after you bought the tv. Other than that, DO NOT buy one online, and I'm sorry to say it but, even at Woot, E-Cost, or any other online discount store. ESPECIALLY if it is a refurbished television.

And also, please, everyone, read the warranty policy on ANY expensive item you purchase, especially TVs. You'd be surprised what isn't covered under most electronics companies warranties. Almost every major TV manufacturer requires you to ship a defective tv back to them at your cost.

For all I know I can get fired for posted this online, but to be honest at this point, I could give a shit. Sorry everyone who has had to deal with a Westinghouse RMA, but please remember that if you have to call the tech support line, it really is not our fault, and there is almost nothing we can do.

I worked at a call center too (tech support and general billing/service for a residential satellite TV company); I lasted about four months. I remember very well the horrors of that job. Working at a call center should be classified as a form of torture by the Geneva Convention.

The main criteria of whether we were doing our job "properly" was how fast we finished the calls. Call volume mattered, not making customers happy. The second criteria was how closely we followed the scripts. We were told to never, ever deviate from the scripts, even when the scripts were completely bogus, redundant, or insulting to the customer's intelligence. And the scripts ARE insulting; they are written to appeal to the lowest common denominator of customer, by necessity. If I as a phone rep knew how to fix a problem and it deviated from the script, tough crap. Deviate from the script at your own risk. If someone was monitoring that call, you're in trouble.

At any given time, there was approximately one manager per 15-20 phone reps. If a customer asked to speak to a manager, s/he often got put on hold for a good 10-15 minutes by necessity; this is because I was 30 cubicles away looking for someone to take the call. And yet putting people on hold was a HUGE no-no, per company policy. It was a lose-lose situation.

The company under-staffed the center to save money, so the hold queue was always filled to the brim. By the time the customer got to us, they had been sitting and fuming on hold for 15 minutes or more. We also had an offshore call center in general vicinity of India, and those reps were only trained to take the most basic of calls. If there was a tech support issue or anything complex, it was transferred to us (resulting in another 15 minutes of hold before they talked to me). THOSE weren't fun calls.

We had to take calls non-stop for hours, until we got a specified 15-minute or 30-minute break. Breaks were measured DOWN TO THE SECOND. Aside from your breaks, you couldn't stop taking calls even to go to the bathroom or get a sip of water.

Phone reps are hated by customers, because they are the voice of the company for the customer. They are also abused by the company; the pay is terrible, the people doing it are desperate (few people take that job if they can find any other), and the company knows they're replaceable. Attrition rates at call centers are through the roof. Even people how stay at the call center aspire to become managers themselves as fast as humanly possible, so they can stop taking calls, so at any given time, the person you're talking to when you call a call center has probably only been working for 3 or 4 months tops. Phone reps are seen as little better than cattle by their employers.

So I definitely sympathize. In all of my lengthy dealings with Westinghouse, I was always polite, and the phone reps were generally polite back to me.

One thing that absolutely IS the phone reps' fault, though, is when I'm promised a call back, and then I never get one. This happened to me many times when dealing with Westinghouse, and there's no excuse for that. Or when someone says "Call back tomorrow and we'll have more information for you!" knowing full well that they won't. This also happened to me many times. Once the phone reps determined that they had no information for me, I was often told anything that would get me to hang up quickly.

If one girl at Westinghouse hadn't finally taken 15 minutes to somehow track down my first (lost forever) monitor, I'd never have known what happened. I don't know how they did it, but if a phone rep had done that sooner instead of two months of dodging my questions, I may have been able to get UPS to track down the first package.

If someone is unable to help me, they should say "Sorry, I'm completely unable to help you." A little honesty goes a long way. If I'd have contacted the corporate office sooner I could've gotten things done a lot faster, without all the run-around.

But yes, this comment reaffirms what I already suspected. Don't buy expensive electronics online; the price savings aren't worth the disaster you face when it breaks. And remember you're taking a huge gamble when you buy Westinghouse or any other "bargain" (i.e. cheap) brand.

(Read the entire Westinghouse saga, if you dare: The beginning, Update 1, Update 2, Update 3, Update 4, Update 5, Update 6, Update 7, Update 8, Update 9, VICTORY, aftermath.)

October 24th, 2008

Ignorance is bliss

I remember the good old days of web-browsing, when I'd just zoom around the internet without a care in the world. The first time I got a small whiff of something foul was in college one day, when a professor yelled at the class because someone was using telnet to connect to remote machines. "Are you crazy? It sends everything in plaintext! Use SSH!"

Then eventually I put together a website where I had access to the server logs, and oh boy, it was not fun reading those the first time. Line after line of bots in Ukraine, trying to access vulnerable scripts that may or may not exist on my server. Then I checked some of my SSH logs, and saw the same thing. Thus my innocence was destroyed forever.

Nowadays all of my email traffic is SSL-encrypted, and FTP is disabled on every server I have root access to, in favor of SFTP. I cringe every time I set up a wireless network (even WPA is being cracked nowadays). My passwords are long, nearly impossible to remember, and legion. My browser cache is cleared daily. I reject all cookies by default, and Javascript is run on an as-needed basis.

One day I read on Slashdot about Flash cookies. So I had a really bright idea:

ln -s /dev/null ~/.macromedia

That'll teach them! No more flash cookies. (Note, don't do this if you like watching Flash movies in your browser. They stop working on certain websites. Annoying. Next best thing is a plugin to let you delete them.)

The bad thing is that I still don't know very much about network security and privacy. If I knew more, I'm sure I'd worry more. On the other hand, if I was a locksmith, I might feel bad with the quality of the lock on my front door. And yet my house has never been robbed (yet) and my computer has never been hacked (yet). People tend to worry about what's right in front of them. Maybe what you don't know can't hurt you.

But I still cringe when someone sits down in an airport or a Starbucks and logs into their bank account or work email.

October 16th, 2008

Vim - escaping quotes

This Vim regex escapes (by doubling) every double-quote on a line except the first one, last one, or any that are already doubled:

:s/\v(^[^"]*)@<!"@<!""@!([^"]*$)@!/""/g

Sometimes I kind of understand that old humorous quote: Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems. But regexes are still pretty darn useful. I can't imagine a good replacement for them that wouldn't have all the same problems with escaping and magic characters and whatnot, without the replacement being so verbose that no one would ever use them.

October 13th, 2008

Vim “Align” plugin

There are some Vim plugins I don't know how I lived without before I found them. One I found just last week is Align, which turns this:

@label = Label.new(@frame, -1, "Choose an option:", DEFAULT_POSITION, DEFAULT_SIZE, ALIGN_CENTER)
@selector = ListBox.new(@panel, -1, DEFAULT_POSITION, DEFAULT_SIZE, [], LB_SINGLE)
@button = Button.new(@frame, -1, 'OK')

into this:

@label    = Label.new  (@frame, -1, "Choose an option:", DEFAULT_POSITION, DEFAULT_SIZE, ALIGN_CENTER)
@selector = ListBox.new(@panel, -1, DEFAULT_POSITION   , DEFAULT_SIZE    , []          , LB_SINGLE)
@button   = Button.new (@frame, -1, "OK")

These mappings are helpful to quickly align some visually-selected lines of code by comma, left paren or equal sign:

vmap <silent> <Leader>i= <ESC>:AlignPush<CR>:AlignCtrl lp1P1<CR>:'<,'>Align =<CR>:AlignPop<CR>
vmap <silent> <Leader>i, <ESC>:AlignPush<CR>:AlignCtrl lp0P1<CR>:'<,'>Align ,<CR>:AlignPop<CR>
vmap <silent> <Leader>i( <ESC>:AlignPush<CR>:AlignCtrl lp0P0<CR>:'<,'>Align (<CR>:AlignPop<CR>
October 12th, 2008

Emacs annoyance #448,546

From the Emacs docs:

Each buffer has a default directory which is normally the same as the directory of the file visited in that buffer. When you enter a file name without a directory, the default directory is used. If you specify a directory in a relative fashion, with a name that does not start with a slash, it is interpreted with respect to the default directory. The default directory is kept in the variable default-directory, which has a separate value in every buffer.

The command M-x pwd displays the current buffer's default directory, and the command M-x cd sets it (to a value read using the minibuffer). A buffer's default directory changes only when the cd command is used. A file-visiting buffer's default directory is initialized to the directory of the file it visits. If you create a buffer with C-x b, its default directory is copied from that of the buffer that was current at the time.

This is extremely annoying. Vim leaves my working directory the hell alone, why doesn't Emacs? Vim lets you set a global working directory, and selectively (and explicitly) change it on a per-buffer basis. This is what I want.

But in Emacs, every time you open a file, the working directory automatically changes to the directory of that file. If you have multiple files open in Emacs (which of course you do), every time you move the cursor between windows, or look at a new file in your current window, your working directory just changed out from under you.

So say you open some files. Then you want to start SLIME. So you C-x 2 to split and open a new window, in which to start SLIME. Except (annoyance #448,547) Emacs doesn't open a window with a new BLANK file, as Vim sanely does via CTRL-W n; instead it puts the file you're looking at into BOTH windows, which is absolutely never what you want. As a result, the working directory of BOTH windows is the directory of whatever file you were looking at. So every time you start SLIME, you have to make sure you M-x cd back to the proper working directory first, because otherwise your Lisp process is randomly going to start in the wrong directory.

But you can change the directory of the SLIME buffer after you start SLIME. Just M-x cd or ,cd. Except if you're unlucky enough to be using SLIME for Clojure, which may have set its classpath based on its working directory when you started it. In that case you have to restart SLIME.

But if you kill SLIME, Emacs (annoyance #448,548) jams another random file into the window where SLIME just was. So your working directory just changed again! Or did it? Depends what Emacs decided to put into your window. You may think that, while SLIME is running, M-x cd and/or ,cd and then ,restart-inferior-lisp may do what you want, but you would be wrong; it always reverts back to the original working directory from when SLIME was first started.

Lost? Confused? tl;dr? Welcome to Emacs. So now I'm looking through the encyclopedic tangled mess of Emacs documentation to try to figure out how to get Emacs not to change my working directory, ever, unless I say so. This is hindered as always by (annoyance #448,549) Emacs' arcane and non-standard terminology. So far, I have committed to memory that "current directory"/"working directory" in Emacs is instead called "default directory". And you don't open files, you "visit" them. Command line? No, "minibuffer".

October 6th, 2008

Crap… I fixed them

I couldn't give up on my precious Grado SR-80s. Turns out the cable was only broken in three places; nothing a couple hours and a lot of electrical tape couldn't fix. Now I can't justify buying a replacement.

Fixed headphones

They really do sound amazing. If only they weren't so fragile and difficult to transport. And ugly, for that matter.

October 5th, 2008

Grado Labs SR-80: RIP

My precious Grado SR-80 headphones died today. :( I hardly new thee.

This picture may not be suitable for small children or those with a heart condition:

Headphones

These headphones sounded really great, best I've ever owned. They should, for how much they cost. But they were designed so so so poorly. The cups can rotate 360 degrees, which means no matter how well you take care of them, the cables from the cups to the Y-splitter will get twisted. Once I realized this I electrical-taped the cables together to avoid some of the twisting, but it didn't help. No matter how carefully I wrapped the cords up and stored them in my briefcase, 10 minutes later I'd pull them out and they'd look like a tornado hit.

Today I plugged them in and the left cup was sputtering and hissing in its death rattle. I immediately put the headphones on life support and performed emergency surgery, but the left wire snapped in my hands. You can only twist copper so many times before it gives. What followed was a good 45 minutes of hacking away at the plastic Y-splitter to get to the wires. But it was no good. I think something broke in the cup too, and that thing is impossible to get apart no matter how much force I applied. I tried heating it up to melt the glue but that didn't work either. I made it hot enough that the working parts are probably a puddle in there.

I'm almost glad this DIDN'T work, because then I'd be wearing that mess on my head for another year. Now at least I can justify possibly buying a replacement.

I absolutely need music while writing code. Depending on my mood, either angry German music or cheerful Japanese music. Foreign-language music seems to be just the right mix of brain-stimulation without the distraction of needing to pay attention to the lyrics. So yeah, I'm now in dire need a of replacement. I have backups but they're the in-ear bud sort and aren't the comfiest thing for an 8-hour session. Plus they cancel noise too well and I can't hear my boss talking to me.

It took me MONTHS of research to find these, but I can't justify buying another set after these broke in a few short years of heavy daily use. I need something sturdy and comfy that sounds really good. I need good bass in particular. Back to the drawing board I guess.

EDIT: Crap, I fixed them. :(

October 5th, 2008

KDE4 disaster

From reading the bug it sounds like KDE4 is getting close to being ready to hit the tree, which is awesome. Foolishly, I decided to try it early from the overlay last night. It was a total disaster. Things were crashing left and right, panels would resize themselves to be fullscreen (with hilarious results), half of my apps didn't work at all. I found three or four ways to bring down the entire X server. It took me many hours to get KDE3 running again. This is totally to be expected from installing masked packages as I did, so it's my own dumb fault, it was amusing and I wanted to get a taste.

I'm afraid it's going to be inevitably difficult or impossible to migrate cleanly from KDE3 to KDE4. I had the same problem in Kubuntu when I tried a while back. KDE is so huge and so many things link to it or interact with it that it's going to take a year to track down and remove all the cruft after the switch.

I couldn't even import my old KDE3 color schemes or Konsole color schemes into KDE4, which was surprising. QtCurve was un-configurable, dekorator didn't work, and so on. I didn't get far enough to figure out if my preferred icon themes work or not. I didn't realize they broke backwards compatibility to that large an extent, but I maybe it's to be expected.

There were other problems that were seemingly due to the lingering immaturity of KDE4. I can see all the pieces there which are going to allow people to do really neat stuff eventually. In the meantime KDE4 feels horrible.

KDE4 fonts look nice though.

October 4th, 2008

Westingouse: Victory

Monitor

It only took seven months, a report to the BBB, 30 phone calls, a half dozen emails, and threats of reporting Westinghouse to the FTC and CA Attorney General, but they finally sent me back my monitor. It's the one on the right there (with the very slightly crappier picture quality).

I must say, 3840x1200 resolution is pretty much a dream come true. I can have Firefox and Vim/Emacs open and nice and big, and still have room for lots of terminals, and maybe even a file manager or two. Multiple that by eight virtual desktops.

As near I can tell, it appears to e a new unit, not refurbished. If it is refurbished, it's refurbished well enough that I can't tell one way or the other, which is fine. However I'm still expecting this one to die too. My first monitor died in such a way that it wouldn't power on at all. This one is running very very hot, which scares me. I give it 3 months tops.

It makes me sad every time another person posts to my blog that Westinghouse is giving them the same horrible treatment. But hang in there. Lessons learned:

  • Don't buy a crappy brand just because it's cheap. I saved $75 or $100 on this monitor compared to a better brand, but it cost me over seven months of anguish (and $25 in shipping fees to send it back to the factory, and I had to buy a replacement!) Cheap things are cheap for a reason: The company isn't paying anyone to support their customers, or it's cutting corners in the quality of the merchandise, or something similar. Is it worth the gamble?

  • Don't buy anything expensive online unless you have a very clear method of getting it replaced if it breaks. Manufacturer standard warranties are evil. Is the time savings of shopping online rather than going to the local big box electronics store really worth it, if you have to pack up and mail your stuff to Zimbabwe for seven months?
  • UPS sucks. Hey UPS, how about if you don't drop off $500 pieces of electronics on a random person's front porch without a signature next time? I hope whoever ended up with my first monitor is enjoying it. (Actually I hope they die in a fire.)
  • Westinghouse sucks. Suffice it to say I won't buy this brand of anything ever again. And neither will my family or friends, who've heard me complain about this for seven months. And neither will many people reading my blog, I hope. Note to companies: Take care of your customers. A new monitor costs you a few dollars, but treating your customers like garbage costs you FAR MORE in the long run.

(Read the whole crappy story of Westinghouse's dishonesty and horrible customer service: The beginning, Update 1, Update 2, Update 3, Update 4, Update 5, Update 6, Update 7, Update 8, Update 9, VICTORY, aftermath.)