{{{ ZX2C4 }}}

As many of you know, the KDE Project is transitioning to using Git with Gitolite and CGit. As such, I thought I’d update my aging Gitweb/posix-permissions installation of git to use CGit and Gitolite, and now my public git repository is kicking away. (If you’d like commit access any place or would like to host your own repo on my server, drop me a line.)

Since Gitolite manages git repositories, it has the option of generating the necessary information for Git’s shipped gitweb. This includes making a static list of repository names that should be included in gitweb as well as optionally adding the gitweb.owner entry inside .git/config and the description file at .git/description. The static list of repository names is boring and standard and easy. The owner and description specifications are standards set by the Git project for this kind of information. Hence, Gitolite supports interfacing with them.

Meanwhile, CGit uses its own configuration format for determining the owner and description and repository path. For interfacing with Gitolite, in the past I have created a hook that writes out a CGit-formated configuration file, which is then included in the main cgitrc with the include directive. Essentially I had to do this:

gitcode@starfox ~ $ cat web/cgit/generaterepos.sh 
#!/bin/sh
 
cd $(dirname "$0")
rm -f repos.tmp
 
cat ~/projects.list | while read gitname; do
        name=${gitname%.*}
        fullpath=/home/gitcode/repositories/$gitname
        owner=$(git --git-dir=$fullpath config --get gitweb.owner)
        desc=$(cat $fullpath/description)
        (
                echo repo.url=$name
                echo repo.name=$name
                echo repo.path=$fullpath
                echo repo.desc=$desc
                echo repo.owner=$owner
                echo repo.enable-log-filecount=1
                echo repo.enable-log-linecount=1
        ) >> repos.tmp
done
 
mv repos.tmp repos
 
gitcode@starfox ~ $ tail -n 1 web/cgit/cgitrc 
include=/home/gitcode/web/cgit/repos
 
gitcode@starfox ~ $ cat repositories/gitolite-admin.git/hooks/post-update.secondary 
#!/bin/sh
exec /home/gitcode/web/cgit/generaterepos.sh

This worked decently, but it was cumbersome and ugly, and was likely not to scale as features in both Gitolite and CGit are added and changed. Luckily, CGit supports the scan-path option, which builds an internal list of repositories automatically by scanning a directory for git folders. One such solution for integrating with Gitolite would be to simply point scan-path at Gitolite’s repository directory. This works fine, but it has three main shortcomings, which I’ve addressed this in a generic non-Gitolite-specific way in three patches. Let’s walk through them one by one.

projects-list

We don’t want all Gitolite repositories showing up on CGit, and Gitolite provides a generic mechanism for controlling this: it writes a list of all the repositories selected for Gitweb to a file called projects.list. It’s just a flat file with each repository’s name written on a new line:

CheeseWhiz.git
Geoemail.git
MyCoolThangs.git

So, what about augmenting CGit’s scan-path feature with another setting called “project-list” that points to this file? That’s what this patch does. If project-list is set before scan-path is set, then scan-path only scans the git folders at project-list/${a line in the project-list file}. Problem solved, and this is a pretty generic way of doing it too.

git-suffix

Most people store git repositories on disk at MyGitRepository.git. Notice the .git ending. However, most people prefer to see it listed as just “MyGitRepository” and they especially would like to clone it at gituser@domain.com:MyGitRepository, without needing the .git ending. Usually, CGit’s scan-path infers the repository name directly from the folder name. This patch adds a setting called “remove-suffix” that, if set to 1 (default is 0) before scan-path is set, will remove the .git suffix from the repository name and url while still pointing to the correct physical path. This as well is fairly generic and not specific to Gitolite or Gitweb, but rather Git’s usual conventions.

config-owner

CGit’s scan-path infers the owner of the repository from the posix owner’s UID name. But there is an additional Git standard for overriding this for any interface: the “gitweb.owner” configuration key in .git/config, which Gitolite understands and respects, as well as Gitweb. This patch simply calls Git’s internal C functions for fetching this information from the current repository’s config, and prefers this as the owner to the posix owner’s UID name. If gitweb.owner is not set in the configuration, it falls back to the posix owner’s UID name. This is a standard Git behavior. This occurs only for scan-path — cgitrc specified owners are preferred over these former two, obviously. Again, this configuration standard has been determined by the Git project, and both Gitolite and Gitweb respect it. So, this patch adds support inside CGit for it.

it works

Now instead of the include and the ugly set of scripts and hooks, I can just place this at the bottom of my cgitrc:

remove-suffix=1
project-list=/home/gitcode/projects.list
scan-path=/home/gitcode/repositories

and this integrates perfectly with Gitolite. All is harmonious in the Git universe.

On top of all this, I’ve cooked up a wicked good .htaccess file for CGit that allows me to have anonymous http pull at the same time as it rewrites the CGit urls to be pretty. Check it out:

Options FollowSymlinks ExecCGI
 
DirectoryIndex cgit.cgi
Allow from all
Order allow,deny
 
RewriteEngine on
 
SetEnv GIT_PROJECT_ROOT=/home/gitcode/repositories
 
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d 
RewriteRule "^(.*)/(.*)/(HEAD|info/refs|objects/(info/[^/]+|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx))|git-(upload|receive)-pack)$" /git-http-backend.cgi/$1.git/$2 [NS,L,QSA]
 
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^.* /cgit.cgi/$0 [L,PT,NS]

A strange combination of stopping internal redirects and partial rewritings and odd stop conditions has made it so that the request gets forwarded and reformatted to git-http-backend if and only if it is first valid with cgit.cgi. Is this crackable? Can anyone figure out a backdoor to grab a repository that isn’t in projects.list?

I’ve also written a super generic script for uploading new repositories to my gitolite/cgit installation. From a git working directory, I run ~/Projects/uploadNewGit.sh "This is a description of my new git repo.", and wham-shabam, all the permissions get set and everything is uploaded just fine. Here is uploadNewGit, the latest version of which you can always find in my GitTools repository:

#!/bin/sh
 
GITOLITE_ADMIN="$HOME/Projects/gitolite-admin"
 
gitdir=$(readlink -f "$(pwd)")
name=`basename "$gitdir" | cut -d / -f 2 | cut -d ' ' -f 1`
description="$1"
 
if [ ! -d "$gitdir/.git" ]; then
        echo Not a git repo.
        exit 1
fi
if [ -z "$description" ]; then
        echo You need to specify a description argument.
        exit 1
fi
 
pushd "$GITOLITE_ADMIN/conf" > /dev/null
echo "Writing config..."
(echo
echo "  repo    $name"
echo "          RW+CD   =   $(whoami)"
echo "          R       =   @all"
echo "          $name \"$(git config --get user.name)\" = \"$description\"") >> gitolite.conf
git commit -a -m "Adding $name to repository."
git push
popd > /dev/null
 
url=`git --git-dir=$GITOLITE_ADMIN/.git remote -v | grep push | cut -f 2 | cut -d ' ' -f 1 | sed "s/$(basename $GITOLITE_ADMIN)/$name/"`
git remote add origin $url
git push origin master
git push --all
git push --tags

(As a side note, I’m not really sure the best way to quote commands inside of commands with variables that have spaces. something=$(command $(othercommand $argument)) has issues if argument has a space or if othercommand produces something with a space or if command produces something with a space (not totally certain about the latter two — I should check). But I can’t do this: something=”$(command “$(othercommand “$argument”)”)” because of obvious quoting problems. What’s the common solution to this? I’ve been using an awkward combination of the backtick operator `…` and the $(…) syntax but the backtick has some weird rules too. What’s the deal? Can somebody point me in a good place to read about this?)

Anyway, most of what I’ve written about in this post is new to me. Or at the very least, I’m a bit uneasy. So if you have any suggestions, by all means please tell me. I’m looking forward to seeing what the KDE sysadmins do in the end. Hopefully the CGit authors accept my patches.

July 29, 2010 · 4 comments


Four months ago I promised to make a dictionary KRunner plugin. I’ve finally started to write it.

It’s currently in kdereview and will hopefully be included with KDE SC 4.6.

It functions as simply as I wanted it to back in March: you hit alt+f2, type “define {your word}”, and presto, the results are there.

Unfortunately, it wasn’t as easy as it ought to have been. I utilize the same data source as the dictionary plasmoid, which is a Plasma::DataEngine, and as it turns out, DataEngine has a few issues with threading, which KRunner relies on. It’s also built around signal/slot async requests, while KRunner uses a blocking thread for computation. I ended up having to invoke a QMetaMethod to shuffle things to the right thread and use a QMutex for synchronization. What a hassle. Nevertheless, the dictionary plugin seems to work pretty well.

Anything I should add to it? Ability to choose alternative trigger words to “define”? Some kind of loading indicator? If you have the time, try out the code and let me know what you think.

Update: Some of you in the comments and on IRC have asked me the best way to try this out immediately. Here are the commands:

svn co svn://anonsvn.kde.org/home/kde/trunk/kdereview/plasma/runners/dictionary dictionary-krunner
cd dictionary-krunner
cmake . -DCMAKE_INSTALL_PREFIX=/usr #change prefix accordingly if your distro is funky
make
sudo make install
kbuildsycoca4
kquitapp krunner
krunner
July 29, 2010 · 6 comments


QGraphicsItem::setFlag(ItemIsMovable) is a wonderful feature. With one line of code, an item becomes dragable with the mouse for repositioning. Unfortunately, things get a bit tricky when reparenting during drags.

Let’s say you have two QGraphicsItem called plate1 and plate2, in a subclass called PlateGraphicsItem. Inside plate1 and plate2, you have a few child items, food1, food2, food3, food4, etc, in a subclass called FoodGraphicsItem. Foods are inside of plates. What if we want to move one piece of food from one plate to another, smoothly dragging it there? Intuitively, we should be able to do this (after remembering to run setFlag(ItemSendsGeometryChanges)):

QVariant FoodGraphicsItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
	if (change == ItemPositionChange && scene()) {
		QRectF newRect = mapRectToScene(boundingRect());
		newRect.moveTo(parentItem()->mapToScene(value.toPointF()));
		foreach (QGraphicsItem *item, scene()->items()) {
			if (item != parentItem() &&
			    qgraphicsitem_cast< PlateGraphicsItem* >(item) &&
			    item->mapRectToScene(item->boundingRect()).contains(newRect)) {
				setParentItem(item);
				break;
			}
		}
	}
	return QGraphicsItem::itemChange(change, value);
}

As the user drags the food, we see if it’s boundingbox fits inside the bounding box of a different plate. If it does, we switch parents. But this doesn’t happen. As soon as the item is reparented, it leaps across the screen to the same relative position it was in while inside of the old plate. If it was on the bottom-left corner of the old plate, it moves to the bottom-left corner of the new plate. Not what we want. So it seems like we should be able to modify the inside of that loop like this:

				QPointF oldPos = scenePos();
				setParentItem(item);
				return QVariant(item->mapFromScene(oldPos));

But alas, this does not work either. As soon as you move the mouse again (while still dragging), the item leaps to where it was before the above fix. And remember: this leap puts the item far away from underneath the mouse cursor. So it works initially, but the next time Qt’s MouseMove event handler is called, we’re SOL.

After digging through the Qt source for quite a bit of time, it turns out that Qt keeps track of where the mouse was pressed originally, in the scene coordinates, and uses this to compute the new location relative to the old parent coordinates. Not good. Mixing coordinate systems like this is not okay once the item is reparented. It turns out, we have to simulate a mouse release event to clear Qt’s internal state. But this doesn’t completely do it, because the mouse event still has the scene coordinate of where the button was pressed, which means we also need to intercept and tamper this information before Qt sees it. Essentially what this amounts to is reversing Qt’s coordinate equation, solving for zero, and adding back the location that we want. And this has to happen every time the user moves the mouse, but it should only happen once the item has been reparented. And if the item is reparented back to the original plate after a series of drags, we still have to do this transformation, since we have already cleared Qt’s internal state, which means we need to store the click location once we reparent.

So all and all, this horrible hack amounts to adding this (and making the above modification):

void FoodGraphicsItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
	//BIG PHAT UGLY HACK!
	if (event->buttonDownScenePos(Qt::LeftButton) == m_manualMouseReleaseAt ||
	    !parentItem()->contains(parentItem()->mapFromScene(event->buttonDownScenePos(Qt::LeftButton)))) {
		m_manualMouseReleaseAt = event->buttonDownScenePos(Qt::LeftButton);
		QGraphicsSceneMouseEvent mouseRelease(QEvent::GraphicsSceneMouseRelease);
		QGraphicsItem::mouseReleaseEvent(&mouseRelease);
		event->setButtonDownScenePos(Qt::LeftButton, parentItem()->mapToScene(pos() + transform().map(event->buttonDownPos(Qt::LeftButton))));
	}
	QGraphicsItem::mouseMoveEvent(event);
}

This works, but it’s awful. Really awful. And it could break in future Qt versions. But for now, it works.

So I’m wondering — is this behavior by design, or have I uncovered an odd Qt bug?

Update: It looks like others have encountered the same problem. Well, here’s a work around. But is there a more correct solution?

June 22, 2010 · 4 comments


KRunner needs a dictionary plugin. Work flow:

  1. alt + f2
  2. type “define “
  3. ctrl + v
  4. awe at simplicity

While we’re at it, a translation plugin would be nice too.

I will make the dictionary runner. Standby.

March 10, 2010 · 6 comments


I’m drawn to Europe, and this summer I want to live there, especially France or Italy. Europe is also expensive, which means I have to find a job over there. The problem is, I don’t have any “connections” in the European programming world to set me up with a coding gig. Parisian CraigsList is mostly empty and I’m not really familiar with how folks find jobs over there. I have a damn good resume, but nobody to send it to.

So, I turn to the interwebs for help: How do I find a summer coding job in Europe? Anyone suggest any companies or individuals to contact, or know of any European coding gigs search sites? Any KDE/Qt companies based in Paris or Italy or anyplace European I should contact? I’ll be in Paris next week traveling, so perhaps I’d be able to meet some folks in person while I’m over there.

March 7, 2010 · 10 comments


For the last 10 years, I’ve used zx2c4.com for my e-mail, and have had it forwarded to whichever provider I was using, which most recently happens to be gmail. Ten years ago, it seemed like a good idea to have “catch-all” e-mail, whereby anything you put at zx2c4.com would be sent to me. This was great for a while, and on every site I would visit that required an e-mail, I’d give them a new one. JasonHatesWhenNikeDotComSpamsHim [at] zx2c4.com I would use for nike.com, and then I’d know for certain if they ever sold my e-mail address. For more serious sites, I would just use their site name or something easy to remember about their site. For personal communications, all my friends knew this quality of zx2c4.com, and would send things to anything they wanted — YouAreAGoofBall [at] zx2c4.com, for example.

This was all fun and games and helped filter out some spam throughout the years, but it seems like it’s time I settle down on one e-mail address. I would also like to find a different domain than zx2c4.com for personal communications that’s much easier to remember (any suggestions?). The problem is – it’s very difficult to switch away from catch-all e-mail. My initial idea was to gather up all the e-mails of individuals who have sent e-mail to something other than my main e-mail address, and automate the process with a form letter sent via SMTP that says something like “Dear joe@shmoe.com, You have sent letters to asdfasf [at] zx2c4.com, thatthang [at] zx2c4.com, jojomojito [at] zx2c4.com. Please note that starting May 1, 2010, only whatIDecide [at] zx2c4.com will be valid. Thank you, Jason”. And then painstakingly go to all of the websites with logins and change my e-mail address for every.single.solitary.one one.by.one. At first this all seemed doable.

But then I started investigating…. Since I started using GMail in March of 2005, I have received e-mail from 1,299 different e-mail addresses on 267 different zx2c4.com e-mail addresses, not to mention lost e-mail from the 5 years prior. I built this python script to give me a good layout of what’s up:

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
from imaplib import IMAP4_SSL
from optparse import OptionParser
from email.parser import HeaderParser
from email.message import Message
from email.utils import getaddresses
import sys
import os.path
import os
 
def main():
	parser = OptionParser(usage="%prog --username/-u USERNAME --password/-p PASSWORD --mode/-m MODE [--domain/-d DOMAIN] [--cachedir/-c CACHEDIR]", description="Downloads gmail message headers and determines the set of all e-mail addresses on DOMAIN at which people have emailed you.")
	parser.add_option("-u", "--username", action="store", type="string", metavar="USERNAME", help="Gmail username")
	parser.add_option("-p", "--password", action="store", type="string", metavar="PASSWORD", help="Gmail password")
	parser.add_option("-d", "--domain", action="store", type="string", metavar="DOMAIN", help="Domain name")
	parser.add_option("-c", "--cachedir", action="store", type="string", metavar="CACHEDIR", help="The directory to cache fetched headers for subsequent runs")
	parser.add_option("-m", "--mode", action="store", type="string", metavar="SENDERSFILE", help="If the mode is \"to\", this prints a list of all the emails you've received email on. If the mode is \"from\" this prints a list of everyone who has sent you email. If the mode is \"frombyto\" this prints a list of all the addresses that have emailed you sorted by the address at which you received email. If the mode is \"tobyfrom\" this prints a of all the addresses you have received e-mail from sorted and duplicated by who sent the e-mail.")
	(options, args) = parser.parse_args()
	if options.username == None or options.password == None:
		parser.error("Username and password are all required.")
	if options.mode != "from" and options.mode != "to" and options.mode != "frombyto" and options.mode != "tobyfrom":
		parser.error("You must specify a mode.")
	if not options.username.lower().endswith("@gmail.com") and not options.username.lower().endswith("@googlemail.com"):
		options.username += "@gmail.com"
	if options.cachedir != None and not os.path.exists(options.cachedir):
		try:
			os.makedirs(options.cachedir)
		except:
			sys.stderr.write("Could not make cache dir. Skipping cache.\n")
			options.cachedir = None
 
	imap = IMAP4_SSL("imap.gmail.com")
	imap.login(options.username, options.password)
	imap.select("[Gmail]/All Mail", True)
	typ, data = imap.search(None, 'ALL')
	if typ != "OK":
		sys.exit(("Could not search properly: %s" % typ))
	emailAddresses = {}
	data = data[0].split()
	length = len(data)
	counter = 0
	parser = HeaderParser()
	for num in data:
		counter += 1
		if options.cachedir != None:
			cachePath = os.path.join(options.cachedir, num)
		else:
			cachePath = None
		if cachePath != None and os.path.exists(cachePath):
			message = parser.parse(open(cachePath, "r"))
		else:
			try:
				typ, data = imap.fetch(num, '(RFC822.HEADER)')
			except:
				sys.stderr.write("Failed to fetch ID %s\n" % num)
				continue
			if typ != "OK":
				sys.stderr.write("Failed to fetch ID %s: %s\n" % (num, typ))
				continue
			if cachePath != None:
				try:
					f = open(cachePath, "w")
					f.write(data[0][1])
					f.close()
				except:
					sys.stderr.write("Could not write cache for %s" % num)
			message = parser.parsestr(data[0][1], True)
		tos = message.get_all('to', [])
		ccs = message.get_all('cc', [])
		resent_tos = message.get_all('resent-to', [])
		resent_ccs = message.get_all('resent-cc', [])
		all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs)
		for address in all_recipients:
			if len(address) == 2 and (options.domain == None or address[1].endswith(options.domain)):
				to = address[1].lower()
				fros = getaddresses(message.get_all('from', []))
				fro_addresses = set()
				for addr in fros:
					if len(addr) == 2:
						fro_addresses.add(addr[1].lower())
				if options.mode == "to" or options.mode == "tobyfrom":
					if to not in emailAddresses:
						emailAddresses[to] = set()
					emailAddresses[to] = emailAddresses[to].union(fro_addresses)
				elif options.mode == "from" or options.mode == "frombyto":
					for fro in fro_addresses:
						if fro not in emailAddresses:
							emailAddresses[fro] = set()
						emailAddresses[fro].add(to)
				sys.stderr.write("[%s of %s]: Message to %s from %s.\n" % (counter, length, address[1], fro_addresses))
		if len(all_recipients) == 0:
			sys.stderr.write("[%s of %s]: Message has empty To header.\n" % (counter, length))
	imap.close()
	imap.logout()
	if options.mode == "to" or options.mode == "from":
		for addr in emailAddresses.keys():
			print addr
	elif options.mode == "tobyfrom" or options.mode == "frombyto":
		for to, fro in emailAddresses.items():
			print to
			for f in fro:
				print "\t%s" % f
 
if __name__ == '__main__':
	main()

The situation seems daunting. That is so many email addresses, so many senders, so many website logins to change, so many people who have to update their address books. So what do I do? What will I do? How important is it to switch away from catch-all? I might just be locked in for life.

March 4, 2010 · 11 comments


Clementine is a half port half rewrite of Amarok 1.4, with its kdelibs dependencies stripped and all the code updated to use Qt4. Only in its 0.1 version, it works incredibly well. I’ve been looking for something like this since Juk became crusty and Amarok became too much and Exaile seemed slow and gtkish. Clementine is my new default player.

An ebuild is already available for Gentoo.

March 2, 2010 · 10 comments


A hot topic in the community is wireless management. There’s a whole lot of buzz about NetworkManager, Wicd, dbus, frontends, PolicyKit, plasmoids, and the whole modicum of dizzying names and acronyms. Let me tell you about my mobile laptop’s wifi setup and why it’s easier and slimmer than any of the classic bloat.

I use wpa_supplicant’s optional wpa_gui. It’s a tiny Qt app that has a tray icon and a command line switch to start in the tray. Wpa_supplicant is required for all modern wireless connections and is always running in the background no matter what. Wpa_gui simply connects to wpa_gui’s socket and tells it what to do. I like having wpa_gui in my system tray so that I can reconfigure wifi networks easily.

zx2c4@ZX2C4-Laptop ~ $ cat ~/.kde/Autostart/wpagui.sh
wpa_gui -t

And check it out:

A simple, somewhat ugly, but extremely functional info display. I can connect to new networks with a simple double click:

And presto it connects to the wifi network. I can also configure all of the highly advanced encryption profiles that wpa_supplicant supports. All of this is easily accessible in my tray:

If I did not want wpa_gui -t running all the time, I could pretty easily make this into a little quick launch plasma button, and it would start up nearly as fast, because wpa_gui is so light weight.

This is how I do wireless. I have never had any trouble, and I can connect to wifi networks anywhere I go with ease. It remembers the connections and the priorities that I assign, and I have not seen any system simpler or easier than this.

For wired networking, netplug calls my ethernet setup scripts when I plug in an ethernet cable. No tinkering required. For my cellphone internet via bluetooth, I run “pon nokia” and my ppp chatscript does all the rest. This could easily be tied to a little menu button in my launcher.

I’m bloat free, and networking dynamically on the go with my laptop does not require any advanced timely tinkering.

Why are you all using NM, wicd, etc instead of good ol’ wpa_gui?

January 25, 2010 · 32 comments


When I first started using Linux way back when, I had only MP3s, and Juk was the perfect simple interface for me. Then I started to acquire a good amount of AACs and seeing that m4a support was a long way from taglib, I was forced to switch to Amarok. For a while I bathed in Amarok’s advanced feature set, but at the end of the day, I always longed for my simple Juk interface. For a while I even used GogglesMM, which is a wonderful project written in FOX. But alas FOX was ugly and didn’t integrate with Qt/KDE, so I’ve stayed with Amarok. But finally in the latest KDE SC beta, Juk supports AAC, corresponding with taglib 1.6′s optional addition! Everyone admire:
Long Live Juk!
It’s pretty and works well. I haven’t tried it out tons yet, but so far as I can tell, Juk does almost exactly what I want a music manager to do for me, or what I wanted it to do for me when I was 14 years old.

The system tray has issues, there’s no support for the disc number, there are some issues with the search filter and multiple fields, I haven’t tested the file renaming capabilities, collection scanning is odd, it still relies on Qt3Support, there is no shortcut handler for scrolling to the current track, and it’s got a lot of unfortunate tiny quirks. But all of this is to be expected of software that’s been basically forgotten about since KDE 3.4.

So after exams are over, I’m going to start investigating Juk’s codebase and seeing if it’s worth it to whip this great player into shape for the prime. It looks promising so far, though I haven’t tested it very completely yet.

December 17, 2009 · 12 comments


Middle School

The last time I bought a laptop for myself was in 7th grade, 2001. I was 13 and I used all my savings to buy a $3,700 Dell Inspiron 8100 running a Pentium 3 processor with a whopping 512mb of ram, a 64mb graphics card, and a gigantic 60gb hard drive. What an incredible machine this was.

eBay

Finally it’s time I buy myself a new computer. I recently acquired an Alienware M17x that’s pretty high end, but I’m not a gamer, so I’m selling it on eBay and am going to use the cash to finance the purchase of a brand new Dell Studio 1747. This was my first time selling something big on eBay, and at first I screwed up quite a bit — I listed it at $2,200, which is the price I want in the end for it. No one bid (except for a Romanian scammer offering me $3,500), and a friend of mine convinced me to list it at $0.01 – one penny – with no reserve price, and let the market determine its value. I was weary, but so far the auction has gotten to $1,500, which I’m pretty pleased with. eBay is addictive though — I constantly monitor the auction to see how much I’m going to be receiving. It’s taken me over; it’s awful. I must never play the stock market for this reason. To satisfy my obsession with the bidding, I’ve hacked together in a minute a short python script running in screen that sends me a text message every time the bid increases:

#!/usr/bin/env python
import urllib2
import os
import time
last = 0
while True:
        print "Checking auction at %s:" % time.asctime(),
        page = urllib2.urlopen("http://cgi.ebay.com/ws/eBayISAPI.dll?ViewItem&item=230409562381").read()
        index = page.find("US $") + 4
        now = int(page[index:page.find(".", index)].replace(",", ""))
        print now
        if now > last:
                os.system("echo 'As of %s, auction is at %s' | sendmail MYTELEPHONENUMBER@txt.att.net" % (time.asctime(), now))
                last = now
        time.sleep(45)

Aye yie yie.

The Specs

But I’m quite excited about the new laptop I’ve ordered:

The specs are:

  • Intel Core i7 820QM 1.73GHz (3.06GHz Turbo Mode, 8MB Cache)
  • 8GB ram, DDR3, 1333MHz, 2 DIMM
  • ATI Mobility Radeon HD4650 with 1GB ram
  • Two 500GB 7200rpm hard drives
  • 17.3in 1920×1080 Screen
  • BluRay Burner
  • 4 Year CompleteCare Warranty

With an awesome discount by asking the phone sales manager’s manager’s manager, this all cost before tax $2,350. Still expensive, but I’m hoping that eBay auction will mostly pay for it.

The Interesting Part: Configuration

So while I wait for the new machine to arrive, I have to start planning what I’m going to put on it, and how I’m going to do it.

It has a fast processor and a lot of ram. It seems, however, that it has more ram than processor, in a sense. What I mean is that with 8 gigs of ram, it seems like I might as well keep all programs running at all times. The problem could be though – maybe too many idle processes will gradually eat away at CPU. I’m not sure. Will it?

What do you think? With so much ram, I also need to decide what I’m going to do about swap/hibernation. Do I use a separate partition of 4gigs for swap? Or do I go with the swapfile approach? Or do I not even use a swap at all, because 8gigs of ram is a lot? For hibernation, do I use what’s in 2.6.32 by default or do I patch in TuxOnIce? Decisions, decisions.

I’ll probably go with Gentoo, because it’s customizable and I’ve used it for years. It has its shortcomings, however, and I’m curious about Exherbo, which claims to do what Gentoo does but better, or a customizable binary distro like Arch, or maybe even make the huge leap to an advanced popular distro like Fedora. I’ll probably just stay with Gentoo though, unless somebody can make a really good case to do otherwise. Flamewars, commence.

Of course, I’ll be using KDE SC 4.4 Beta 2 by the time the computer arrives. By the way, I hate adding the “SC” in there, but I think PlanetKDE would come after me if I neglected it. Branding shmanding. But anyway, KDE should fly on the new machine.

The wifi card supports 802.11n, but is unfortunately one of the Broadcom cards that is only supported by the hybrid-proprietary wl driver. Also, in my experience, FGLRX, ATI’s proprietary driver, outperforms RadeonHD by a long shot, so I’ll be using FGLRX too. Shucks. At least ATI’s own John Bridgman has pointed us to a solution for the KWin resizing compositing problem.

The Big Issue: Data Organization

The biggest issue, however, is what I am going to do with 1TB of hard drive space. How do I divide it up? The breakdown of the situation is this:

Storage:

  • Internal 500gb
  • Internal 500gb
  • External 500gb (via USB2)
  • Online server with lots of gb (via ssh+rsync)

Data:

  • 26gb of pictures (and growing)
  • 67gb of music
  • 2gb of code
  • 1gb of documents
  • 15gb of protools archives
  • 10gb of goofy home videos from middle school
  • 1gb of old school work
  • 2gb of middle school junk archives
  • 1gb of old code projects
  • 300gb of downloaded junk

So what are my options for all of this? First of all, which file system? I’ll probably go with ext4, because it seems like the best you can get on Linux right now. How do I organize the data? Some possible schemes are:

  • Many operating systems on Internal1, all data on Internal2, data backed up to both Online and External
  • Linux in Internal1 along with main data (pics, docs, music, code), with downloaded junk on Internal2, and archives of old stuff on External, with important stuff backed up to Online
  • Linux in Internal1 along with main data (pics, docs, music, code), with downloaded junk and archives of old stuff on Internal2, with important stuff backed up to both Online and External
  • Use LVM to concatenate the two drives
  • Use LVM for RAID-1
  • Use LVM for RAID-0
  • Use LVM for RAID-10

What else? What do I do here?!?

I’m really not quite sure. What do you think? What else should I be thinking about? Location of home directory? The additional power consumption of a second HD and hdparm -Y? I want to configure this system perfectly; I need all the help I can get.

December 13, 2009 · 40 comments


MessagePopper on Symbian and Linux

Qt 4.6 was released, and Alessandro put together some awesome tutorial videos for setting up a Qt/Symbian development platform.

The installation was not so smooth, however. I have a Nokia 6650, branded and locked down by AT&T, which makes it awful. It’s filled with bloatware I can’t remove and there are all sorts of restrictions in the operating system, especially related to app installation. It would not let me install PIPS, which is required for OpenC and Qt, and gave me either a nasty error from Qt’s demo installer or from the standalone installer an error message like “Component already built-in. You’re screwed.” So I had to jailbreak the phone, by installing HelloCarbide to allow me to install CapsOn/Off to allow me to insert a hacked installserver.exe so that it would let me install PIPS. Whew that took a long time. Debug deployment is also broken with a message like “General OS error.” No clue. It will sometimes work if installed manually, but basically, AT&T’s restrictions make development pretty rough.

Nevertheless, I prevailed in porting my classic Qt example app: MessagePopper. The only modifications were having the main window show with showMaximized() instead of show() for Q_OS_SYMBIAN and focusing a text input, because evidently if a focused widget is hidden and no other visible widget is programmatically focused, the user cannot select anything. The huge addition was adding a function call to ask Symbian for a network connection, and this required including from Qt’s FTP demo the sym_iap_util.h header and qmake pro additions. Oy. I wish this part were built in to QtNetwork.

It worked beautifully though, and here’s a video demonstrating the app:

(link to youtube original)

December 5, 2009 · 6 comments


I just compiled Chromium, and it rocks. The download manager is better than Firefox, the design is cleaner, the JavaScript performance is a 4 times better, and overall it just seems more solid. The web works better in it. My only complaints so far are some weird font rendering issues and a lack of extensions (adblock, customize google, gmail notifier, live http headers, modify headers, open in browser, right-click-link, skipscreen, useragent switcher…). I’ll stick with Firefox until there is good extension support for Chromium.

I did a JS performance test of QtWebKit, KHTML, Gecko, and V8, and here are the results:
Sunspider Benchmark

Evidently KHTML has a lot of catching up to do, per usual.

October 23, 2009 · 25 comments


Playing with the N900
I played with the N900 today at the Nokia store in NYC and was thoroughly disappointed. From reading all those fanboy posts out there, I expected the most awesome telephone ever, but instead I was presented with the following cons:

  • It is clunky. Really clunky. It’s very thick and does not fit into the pocket well.
  • The keyboard is too tiny. Maybe this just takes some getting used to, but I couldn’t do it.
  • No multi-touch. The little swirling gesture to zoom is a cop-out for a much needed multi-touch screen.
  • Unintuitive user-interface. Some things made sense, but generally operations took one too many clicks. Making phone calls should be easy and fast.
  • The user interface does not rotate.
  • The web browser is turkey slow, even for cached pages.
  • Ovi maps is uglier than GMaps, and again – no multi-touch.

Oh well. I guess I’ll wait until the N900+1, especially considering that Maemo 5 will soon be obsolete.

Or maybe I’m just approaching it the wrong way? I dunno. After all, it does run Linux, can run KDE, and is extremely open… but still: it’s lacking heavily in several areas.

October 14, 2009 · 12 comments


N900The N900 looks good, and later today I plan to go down to NYC’s Nokia store to investigate. However, as Ars reports, the N900 is the 4th step in a 5 step program to develop a mainstream smart phone, and Maemo 5 is last platform before Qt domination comes to Nokia.

So should I wait for the N900+1? $649 is a lot to shell out for a device that is running on a platform that is being retired (“moved to community support”). Besides, I’m a Qt guy, not a Gtk wizard, and any programming I do with the device will most likely be with Qt. Will Maemo 6 be available for the N900? The N900 does not, as far as I can tell, have a multi-touch screen, which means it won’t be able to use Qt 4.6+’s multi-touch gesture support.

On the other hand, how much longer until a Maemo 6 device is released? Probably over a year from now. I also fear, perhaps without good reason, that Maemo 6 (and perhaps even Maemo 5 — I’ll find out later today when I try it out) will have some kind of new-age high-tech web-service-enabled interface that is going to be more difficult to use than my current old school phone. I dunno.

So what should I do? Go for the N900/Maemo5 if it looks decent, or wait who knows how long for the N900+1/Maemo6?

PS: What’s the story with libdui or DirectUI? I can’t find much about this online.

PPS: Is there a future relationship between Plasma and Maemo beyond neat hacks?

October 14, 2009 · 11 comments


After a much heated discussion about how to fix the horrible resizing and performance bug with FGLRX and KDE4, no one knew where to start looking. The X team had to do a little digging; the KDE4 team needed to change somethings; the FGLRX warehouse needed to get their shit together and listen to the users… bla bla bla the flame wars raged on, fingers were pointed, and nothing ever got done.

That is, nothing got done until a lone user piped up with a workaround. Here is what he wrote in the comments of that blog post:

Hi, I have been pissed off by this problem a long time and assumed it was ATI’s fault. Tonight I made one last effort before ordering a Nvidia graphics card. And I was successful.

I am running catalyst 9.8 using a Radeon 3850 and have had this re-size/maximize problem as long as long as I have used KDE4. To solve the problem I needed to modify a file in xorg-server. in the code directory it is called ./composite/compalloc.c. Here I commented out most of a function called compNewPixmap. Everything below these lines:

pPixmap->screen_x = x;
pPixmap->screen_y = y;

all the way down to (but not including) the last line:

return pPixmap;

After this I am running KDE4 with all desktop effects that I want and without any lag in resizing/maximizing.
I am running Gentoo, so I just updated the xorg-server source package file and put it back into the source repository, rebuilt the manifest and emerged it again. Voila!

Voila indeed. The patch he’s talking about looks like this (thanks to this Russian blog I can’t read):

--- composite/compalloc.c.orig  2009-09-08 02:54:28.657143479 +0700                              
+++ composite/compalloc.c       2009-09-08 02:55:42.835357653 +0700                              
@@ -484,64 +484,6 @@                                                                             
     pPixmap->screen_x = x;                                                                      
     pPixmap->screen_y = y;                                                                      
 
-    if (pParent->drawable.depth == pWin->drawable.depth)                                        
-    {                                                                                           
-       GCPtr   pGC = GetScratchGC (pWin->drawable.depth, pScreen);                              
-                                                                                                
-       /*                                                                                       
-        * Copy bits from the parent into the new pixmap so that it will                         
-        * have "reasonable" contents in case for background None areas.                         
-        */                                                                                      
-       if (pGC)                                                                                 
-       {                                                                                        
-           XID val = IncludeInferiors;                                                          
-                                                                                                
-           ValidateGC(&pPixmap->drawable, pGC);                                                 
-           dixChangeGC (serverClient, pGC, GCSubwindowMode, &val, NULL);                        
-           (*pGC->ops->CopyArea) (&pParent->drawable,                                           
-                                  &pPixmap->drawable,                                           
-                                  pGC,                                                          
-                                  x - pParent->drawable.x,                                      
-                                  y - pParent->drawable.y,                                      
-                                  w, h, 0, 0);                                                  
-           FreeScratchGC (pGC);                                                                 
-       }                                                                                        
-    }                                                                                           
-    else                                                                                        
-    {                                                                                           
-       PictFormatPtr   pSrcFormat = compWindowFormat (pParent);                                 
-       PictFormatPtr   pDstFormat = compWindowFormat (pWin);                                    
-       XID             inferiors = IncludeInferiors;                                            
-       int             error;                                                                   
-                                                                                                
-       PicturePtr      pSrcPicture = CreatePicture (None,                                       
-                                                    &pParent->drawable,                         
-                                                    pSrcFormat,                                 
-                                                    CPSubwindowMode,                            
-                                                    &inferiors,                                 
-                                                    serverClient, &error);                      
-                                                                                                
-       PicturePtr      pDstPicture = CreatePicture (None,                                       
-                                                    &pPixmap->drawable,                         
-                                                    pDstFormat,
-                                                    0, 0,
-                                                    serverClient, &error);
-
-       if (pSrcPicture && pDstPicture)
-       {
-           CompositePicture (PictOpSrc,
-                             pSrcPicture,
-                             NULL,
-                             pDstPicture,
-                             x - pParent->drawable.x,
-                             y - pParent->drawable.y,
-                             0, 0, 0, 0, w, h);
-       }
-       if (pSrcPicture)
-           FreePicture (pSrcPicture, 0);
-       if (pDstPicture)
-           FreePicture (pDstPicture, 0);
-    }
     return pPixmap;
 }

This patch works like a charm. All of the FGLRX resizing/maximizing bugs disappear. Not only that, but things like clicking on the K menu are suddenly a lot faster… KDE4 doesn’t seem laggy and now has the performance I’ve expected all along. The effects look great, and my transparent terminal is a delight.

There is, however, a bit of garbage that shows up occasionally, and perhaps there’s a good use for the code that was removed in the patch. Why is it only FGLRX that benefits from removing this code? I don’t know much about XOrg internals, but I’m guessing it has to do with some sort of sometimes-required allocation that causes a readback in the FGLRX driver but not in other drivers. What’s the deal? Is fixing this problem as simple as committing this patch and then fixing the garbage error? Or is the code that was removed necessary, and really the problem lays with FGLRX? What to do at this point?

September 7, 2009 · 18 comments


I’ve taken then plunge: I just installed PulseAudio (PA) and related tools (on Gentoo). A lot of users are vehemently anti, stating numerous complications and bugs, but its potential advantages for networked audio are attractive.

Evidently KMix is supposed to support PA, but it still only shows the alsa devices. I suspect this is related to when PA is loaded, and currently I do not have any user-space loader. What’s the optimal way for this to happen in KDE? An autostart entry? Symlinking some mysterious file to kde’s env directory? All the forum posts are for older KDE versions. Phonon works so far though. All the PA apps are GTK/GConf based, and aren’t very KDE friendly, and generally it seems like KDE has neglected PA support, or PA has neglected KDE support. They simply aren’t very pretty together. What are the plans for integrating KDE and PA a little bit more closely?

I’m using a Macbook as a print server, printing to it from my Linux box, and everything is simple because they both use CUPS. However, I’m having a bit of trouble streaming audio to it. I can’t seem to build PA on OSX, and I don’t even think OSX is officially supported by PA. I tried installing ESD using MacPorts and using PA’s bridge for that, but it played a half a second of sound before skipping. ESD doesn’t do good latency calculations. PA supports streaming to Airport Express, so I thought I’d try out Airfoil Speakers, but unfortunately, Airfoil uses a different protocol. So I’m not sure what to do now… How do I send PA to my Macbook?

PA also doesn’t work well with Skype, and I anticipate some other problems as well. What a hassle. Why the bad integration with KDE? Why the numerous bugs? Any PA tips from veteran readers?

August 28, 2009 · 18 comments


A certain company needed a way to easily get text-based subtitle files in a batch fashion. The DVDs were available for ripping such subtitles, but the necessary OCR work proved too time-consuming and cumbersome, even with solutions like SubRip. So, I figured that 1000s of individuals on the Internet already gladly spend their days OCRing DVD subtitles into text formats, and sure enough databases like Subscene exist. So, I built a program to wrap Subscene’s web interface, and what better way to do this is there than QtWebKit?

Subtitler's Subscene Interface

I capture the downloads by using QtWebKit’s unsupportedContent signal and parse the downloads by using QNetworkAccessManager’s finished signal. I then run the zip archive through QtIOCompressor‘s code for unzipping zips, then extract and parse the subtitles.

A significant issue is that a lot of the subtitles on Subscene are from torrents that have videos which are cut off at the beginning. The solution is to shift each subtitle in the file by a constant time interval if neccessary, so I built an interface to easily see and shift timing:

Subtitler's Preview Interface

As you can see, it’s possible to shift all the subtitles. Phonon made this page a breeze. One significant challenge was writing a search algorithm that would find the correct Subtitle based on a time falling in a certain range. It needed to be super speedy and designed for sequential searches. I came up with the following, which could use a bit of improvement:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
QString SubtitleParser::subtitleAtTime(quint64 time)
{
	QLinkedList<Subtitle>::const_iterator i;
	bool forward = true;
	for (i = m_searchPosition; i != m_subtitles.constEnd(); ) {
		if (i->isContainedIn(time)) {
			m_searchPosition = i;
			return i->text();
		}
		if (i->isAfter(time)) {
			if (i == m_subtitles.constBegin())
				break;
			--i;
			forward = false;
		} else {
			if (!forward)
				break;
			++i;
		}
	}
	return QString(' ');
}

Another challenge was dealing with QWizard and QWizardPage, particularly issues with fields and custom buttons. The field/property system is just too cumbersome to work with; there ought to be a better way of establishing centralized data with straight up pointers. The buttons on QWizard all want to be global, so adding a custom button to a single page proved a bit hackish. It’s a cool API, but just a little bit limiting. If anyone has some suggestions, let me know.

As always, the code is available on my gitweb, and I encourage you to try it out, read the code, and send me suggestions in the comments below.

Though this was designed to work on Linux, Qt, being delightfully cross-platform, also makes it possible to run the Subtitler on OSX. Check out the OSX screencast:

Direct YouTube Link

August 13, 2009 · 4 comments


Update: check out this solution.

Amongst users of the proprietary ATI driver (FGLRX), it’s a well known problem that KDE4′s KWin’s desktop effects are unusable due to turtle slow window resizing. Unfortunately, bug reports and forum posts fail to come up with a clear answer as to why this happens, who is responsibility for fixing it, and which parts of the stack need to adapt for the bug. If you’re not already familiar with the problem, observe:


Direct YouTube Link

As you can see, the three second delay in window resizing, maximizing, and (perhaps) restoring makes compositing-enabled KWin unusable. Note that the problem still persists when disabling the “show window content while resizing” option.

It’s been particularly difficult to track down the route of this problem and who should be fixing it. From a lengthy discussion on Phoronix’s Forums, ATI’s “Bridgman” user acknowledges the problem but claims “It’s not a driver issue AFAIK, so not sure why we would mention it in the release notes.” He goes on to indicate that the problem was introduced during a hack to ensure KWin compatibility on Intel cards, to work around a glitch with garbage appearing on the screen, and a lengthy debate over the “107_fedora_dont_backfill_bg_none.patch” patch. Somewhere the stack was patched to support Intel cards at the expense of ATI cards, according to ATI’s “Bridgman”.

Over on the KDE bugzilla, we’re met with a curt response from the KWin team: “Driver bug then.” The bug is marked as “RESOLVED” and “UPSTREAM”.

And elsewhere on the Internet, we get a helpless string of “still not fixed” messages, over on the unofficial FGLRX bugzilla, other Phoronix forum posts, and distro forums.

So I ask my readers: who is responsible for this bug and how will it be fixed? If indeed it is neither FGLRX nor KWin and the problem exists in XOrg, why has XOrg been patched to support Intel cards at the expense of ATI cards? Is there anything FGLRX or KWin could be doing to mitigate the problem? Potential workarounds? Users have complained of this problem for over a year now perhaps, but not once has leadership from anywhere offered a compelling explanation or plan for the future. What to do?

Leave comments. The current discussion seems to be fruitful.

August 2, 2009 · 47 comments


I’ve been working for AnyClip on a Boxee application. Corporate explanation:

AnyClip will launch later this year as a comprehensive library of legally-licensed scenes from Hollywood movies. While we’re still in active negotiations with the rights holders of the films, this preview-only Boxee application begins to show the potential behind tying such a powerful platform to applications like Boxee. Ultimately, applications like AnyClip for Boxee will enable people to relive any moment, from any film, on any platform.

I submitted it to the Boxee Dev Challenge, and polls are open for voting. Please vote for AnyClip here.

AnyClip for Boxee

June 16, 2009 · (No comments)


For those of you who long for the old Amarok 1.4 two-column layout, it is now possible to sort of simulate this behavior in Amarok 2.1 by careful dragging of splitters and customization of the playlist:
Layout of Amarok 1.4 with Amarok 2

It is also (nearly) possible to emulate the layout of Juk, iTunes, Banshee, or Rhythmbox:
Amarok 2 Can Look Like Juk, iTunes, Banshee, or Rhythmbox... sort of

April 13, 2009 · 30 comments


As promised, I’ve written in under 100 lines of JavaScript (QtScript) a service for Amarok that plugs into ZX2C4 Music (ReadMe for ZX2C4 Music, Source for ZX2C4 Music). Obligatory screenshot:
Amarok Scriptable Service for ZX2C4 Music
The Amarok scripting API is very slick, especially the scripted services API.

It is not now configurable and is rather inefficient because getlisting.php does not support very complex queries (though I do take advantage of its sorting on lines 54-57), but this will be changed by the time I release an installable version of the script. Currently, it downloads a listing of the entire collection during the first request (lines 27-33), and then just reads from an in-memory database (multidimensional array) to query the different levels (lines 34-95). TMI: too much iteration.

Unfortunately there are some kinks, as Phonon often has trouble playing URLs and does not always show buffering information correctly (both gstreamer and xine backends). Look at lines 65 and below of getsong.php: this should be a working HTTP streaming implementation, but Phonon doesn’t dig it, for whatever reason. Any pointers here? The same issue crops up in the standalone qt player for ZX2C4 Music. Strange. Also, I use QTextDocument to convert HTML entities back to normal text (lines 14-22), but it’s very slow. Is there a better way to be doing this? Finally, callbackData (line 73) refuses to store an array, which means I have to use a nasty splitter; this is a bug presumably.

For the ZX2C4 Music hackers out there, here’s the source code to play with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
Importer.loadQtBinding("qt.core");
Importer.loadQtBinding("qt.network");
Importer.loadQtBinding("qt.gui");
 
function ZX2C4Music()
{
	ScriptableServiceScript.call(this, "ZX2C4 Music", 3, "Browse ZX2C4 Music", "The entire ZX2C4 collection, at your finger tips.", false);
}
var songs = null;
var delayedArgs = null;
function receiveDatabase(reply)
{
	songs = eval(reply)[1];
	var decoder = new QTextDocument();
	for (var i = 0; i < songs.length; i++)
	{
		for (var j = 0; j < songs[i].length; j++)
		{
			decoder.setHtml(songs[i][j]);
			songs[i][j] = decoder.toPlainText();
		}
	}
	onPopulate(delayedArgs[0], delayedArgs[1], delayedArgs[2]);
}
function onPopulate(level, callback, filter)
{
	if (songs == null)
	{
		delayedArgs = [level, callback, filter];
		new Downloader(new QUrl("http://music.zx2c4.com/getlisting.php?username=TOP&password=SECRET&language=javascript"), receiveDatabase);
		Amarok.Window.Statusbar.shortMessage("ZX2C4 Music collection is loading...");
		return;
	}
	if (level == 0)
	{
		var splitCallback = callback.split("&*/ZX2C4MUSICSPLITTER/*&");
		var notFirst = false;
		for (var i = 0; i < songs.length; i++)
		{
			if (songs[i][4] == splitCallback[0] && songs[i][3] == splitCallback[1])
			{
				notFirst = true;
				var item = Amarok.StreamItem;
				item.level = 0;
				item.itemName = songs[i][2];
				item.playableUrl = "http://music.zx2c4.com/getsong.php?username=TOP&password=SECRET&transcode=false&hash=" + songs[i][0];
				item.album = songs[i][3];
				item.artist = songs[i][4];
				item.track = songs[i][1];
				item.infoHtml = "";
				item.callbackData = "";
				script.insertItem(item);
			}
			else if (notFirst)
			{
				break;
			}
		}
	}
	else if (level == 1)
	{
		var lastAlbum;
		for (var i = 0; i < songs.length; i++)
		{
			if (callback == songs[i][4] && lastAlbum != songs[i][3])
			{
				lastAlbum = songs[i][3];
				var item = Amarok.StreamItem;
				item.level = 1;
				item.itemName = lastAlbum;
				item.playableUrl = "";
				item.infoHtml = "";
				item.callbackData = callback + "&*/ZX2C4MUSICSPLITTER/*&" + lastAlbum;
				script.insertItem(item);
			}
		}
	}
	else if (level == 2)
	{
		var lastArtist;
		for (var i = 0; i < songs.length; i++)
		{
			if (lastArtist != songs[i][4])
			{
				lastArtist = songs[i][4];
				var item = Amarok.StreamItem;
				item.level = 2;
				item.itemName = lastArtist;
				item.playableUrl = "";
				item.infoHtml = "";
				item.callbackData = lastArtist;
				script.insertItem(item);
			}
		}
	}
	script.donePopulating();
}
var script = new ZX2C4Music();
script.populate.connect(onPopulate);
April 12, 2009 · 1 comment


At our KDE 4.2 release party, many of you voiced desire to have more frequent NYC meetings to talk about all things KDE. So, what dates work for those of you still interested?

Update: Possibly this coming week we’ll meet. Anyone know of good coffee shops in Manhattan that have wifi? Or perhaps I can procure a spot in a Columbia building…

April 9, 2009 · 6 comments


Dear PlanetKDE,

I’m just a student, but I can build just about anything with code. The problem is that people in the industry disregard me because I’m young. I also doesn’t have many “contacts” or “networks” for shmoozing up. I am, however, looking for a lucrative freelance coding job for this summer.

So I’m wondering – amongst those of you who have been in a similar predicament, how have you gone about finding programming gigs?

A related question: does anybody hire KDE programmers?

Sincerely,
Unemployed

April 8, 2009 · 6 comments


Dear PlanetKDE/Lazyweb,

I am curious about the future of KHTML in the face of WebKit. Three questions come to mind:

  1. Why does KDE use KHTML when WebKit is faster, more compatible, etc?
  2. Why does Konqueror use KHTML as default instead of WebKit?
  3. Will KHTML be moved out of the main tree?

As far as I can see, it is commonly accepted fact that WebKit is a faster and more compatible engine than KHTML. WebKit was ported to Qt in 4.4, and Qt 4.5 provides some critical enhancements. From these two points, the three questions above naturally follow. If the situation for KHTML was completely hopeless, then it never would have made KDE 4.0 or the present API would be converted into a wrapper for QtWebKit. But this is not the case, so presumably the KHTML team has high hopes for the project. I wonder what the team’s response is to these questions. Undoubtedly they have thought a lot about KHTML vs WebKit and find WebKit a worthwhile project. What’s the deal?

February 17, 2009 · 58 comments


Anybody interested in some sort of NYC release event for 4.2? If so, leave a comment and maybe if there’s enough interest we’ll organize something.

Update:
There will be a KDE 4.2 release event at the Hungarian Pastry Shop on 1030 Amsterdam Avenue (between west 110th and 111th) at 8pm on January 28th. Visit wiki information.

January 25, 2009 · 12 comments