The announcement of Microsoft Surface in the last months has intrigued me. Their videos demonstrate very clever use of the multiple touch screen technology, and have encouraged me to do a bit of research. By the looks of things, a multi-touch display is not that hard to build...
In February 2006 at the TED conference in Monterey, California, Jeff Han demonstrated some of his research into multi-touch displays. The video of his talk was the first time I had seen the true HCI capabilities of multi-touch technology. The software he demonstrated was inherently easy to use, as it required no mode switching like conventional WIMP interfaces do. If you wanted to move something, you didn't need to select the Hand tool and click and drag something across the screen with a cursor, you would just touch the item on the screen and drag it out of the way. If you wanted to make something bigger, you could simply pull the corners of the item apart, rather than searching for the zoom tool. The elegance of this solution is obvious.
With the public announcement of Microsoft begining production of Microsoft Surface, the demo videos show more powerful user interface concepts. When you combine the technology of multi-touch with other facilities such as barcodes, RFID, Bluetooth and WiFi, you can make the screen interact with objects other than fingers. Their demonstration videos show the display interacting with digital cameras to show all the photos stored in the camera's memory as soon as it is placed on the table. It interacts with products in a store, showing feature comparisions between different models as soon as they are placed on the table. You can share music and media by dragging content from your Zune or iPod to your friends one. You can even pay the bill at a restaurant by dragging and dropping the menu items onto your credit card. However, with a pricetag of around USD$11,000, this feature set comes at a price...
So, I've been looking into building my own multi-touch display, so that I can play with this cool technology, and by the looks of it, it isn't to hard to do. Wikipedia is always a good starting point and this lead me to Harry van der Veen's blog where he describes how to build a multi-touch display based on FTIR technology. The basic premise is that you shine infra-red light into an acrylic or perspex panel from the edges, and watch the panel surface from an IR sensitive camera. Most of the time, this light will bounce around inside the panel and won't be visible by cameras looking at the surface, but when something touches the surface, some of the light gets reflected out of the panel. This appears on the view from the camera as a blob of light where the contact is made. You can see this technology in operation on Jeff Han's website, the website of Perceptive Pixel (the company he created) and on this website. You can also see Steve Hodges from Microsoft Research in Cambridge demonstrate his team's research into adding multi-touch capabilities to a standard laptop. While they don't elaborate on the design too much, it does appear to be based on FTIR technology.
Jeff Han also demonstrates the use of LEDs for multi-touch sensing. It is a little known fact that while LEDs are produced primarily for emitting light, they can also be used to detect it. Jeff uses this principle to show that a matrix of LEDs can be driven in such a way that they effectively do both at the same time. I can imagine this principal working well for the likes of an LCD backlight, but at this stage the cost would probably be prohibitive.
Capacitive touch screens are another way to retrofit multi-touch to an existing display, but I haven't been able to find much information about them. Most existing capacitive touch screens are limited to a single point of contact. Having said that, the Apple iPhone is believed to be based on capacitive multi-touch technology.
You can find a good history of multi-touch techology and various implementations at Bill Buxton's website.
So without further ado, I'm off to build myself "a big-ass table"...
Sunday, June 24, 2007
Wednesday, December 27, 2006
Ok, so it may sound like a harsh comment, but there have been many times when I've found rather dodgy Visual Basic code in business critical applications. Unfortunately, the ease of programming with VB means that users do not have to have any knowledge of good programming practice, or even a good knowledge of VB to be able to create the next wizz bang application. Show these to your boss, and mention the fact that it took you less than a day to develop and you'll get a promotion in no time!
So why on earth do we have programmers?
To those who don't know me too well, you might think that I'm bagging Visual Basic as a language, but this is quite the opposite. The language is brilliant for RAD (Rapid Application Development) due mainly to its readability and (relatively) concise code. I use VB pretty much every day in my current workplace, and I have a library of VB code that does some pretty nifty stuff.
With Microsoft including a full Visual Basic for Applications development environment in their Microsoft Office suite, they have made it easy for users to play with. This is a good thing. However, users have a natural tendency to assume a great knowledge of programming simply because they know what a macro is.
This is even true for other programmers with little or no VB experience. All too often, companies use C/C++ developers to develop VB applications. This is fine if the developers have a genuine interest in VB and want to learn it as a language, but all to often they try to apply C/C++ concepts where they shouldn't or needn't be applied.
I put to you this block of sample VBA code that I found today in a Microsoft Access application at work. Its task is simple: to display a File Open dialog, prompting the user to select a file to open. In this case, the user wasn't a complete idiot, as it appears that they had some familiarity with the Windows API, although this could well have been an Experts Exchange copy-and-paste.
Option Compare Database 'Use database order for string comparisons
Option Base 0
Declare Function upg_lstrcpy Lib "Kernel32" Alias "lstrcpy" _
(ByVal lpDestString As Any, ByVal lpSourceString As Any) As Long
lStructSize As Long
hwndOwner As Integer
hInstance As Integer
lpstrFilter As Long
lpstrCustomFilter As Long
nMaxCustFilter As Long
NFilterIndex As Long
lpstrFile As Long
nMaxFile As Long
lpstrFileTitle As Long
nMaxFileTitle As Long
lpstrInitialDir As Long
lpstrTitle As Long
Flags As Long
nFileOffset As Integer
nFileExtension As Integer
lpstrDefExt As Long
lCustData As Long
lpfnHook As Long
lpTemplateName As Long
Declare Function upg_GetOpenFileName Lib "COMMDLG32.DLL" _
Alias "GetOpenFileName" (OPENFILENAME As upg_OPENFILENAME) As Integer
Global Const upg_OFN_READONLY1 = &H1
Global Const upg_OFN_OVERWRITEPROMPT1 = &H2
Global Const upg_OFN_HIDEREADONLY1 = &H4
Global Const upg_OFN_NOCHANGEDIR1 = &H8
Global Const upg_OFN_SHOWHELP1 = &H10
Global Const upg_OFN_ENABLEHOOK1 = &H20
Global Const upg_OFN_ENABLETEMPLATE1 = &H40
Global Const upg_OFN_ENABLETEMPLATEHANDLE1 = &H80
Global Const upg_OFN_NOVALIDATE1 = &H100
Global Const upg_OFN_ALLOWMULTISELECT1 = &H200
Global Const upg_OFN_EXTENSIONDIFFERENT1 = &H400
Global Const upg_OFN_PATHMUSTEXIST1 = &H800
Global Const upg_OFN_FILEMUSTEXIST1 = &H1000
Global Const upg_OFN_CREATEPROMPT1 = &H2000
Global Const upg_OFN_SHAREAWARE1 = &H4000
Global Const upg_OFN_NOREADONLYRETURN1 = &H8000
Global Const upg_OFN_NOTESTFILECREATE1 = &H10000
Global Const upg_OFN_SHAREFALLTHROUGH1 = 2
Global Const upg_OFN_SHARENOWARN1 = 1
Global Const upg_OFN_SHAREWARN1 = 0
'SNIPPED MANY LINES OF CODE
' Developed by: (NAME REMOVED TO PROTECT THE GUILTY), Inc.
'SNIPPED MANY MORE LINES OF CODE
Function upg_GetDBFileName$(stype As String, sOpenPrompt$)
Dim szFilter As String
Dim Retval As Variant
Dim wFlags As Integer
Dim varFileName As Variant
Dim szDirectory As String
' Specify that the chosen file must already exist if stype <>NEW
If stype = "new" Then
wFlags = 0
sPrompt = "New Database"
wFlags = upg_OFN_FILEMUSTEXIST1 Or upg_OFN_HIDEREADONLY1
sPrompt = sOpenPrompt
szDirectory = "C:\"
'* Define the filter string and allocate space in the "c" string
'* Duplicate this line with changes as necessary for more file templates.
szFilter = szFilter & "Access Database (*.mdb;*.mdd)" & Chr$(0) & "*.mdb;*.mdd" & Chr$(0)
' Now actually call to get the file name.
varFileName = upg_OpenCommDlg(szDirectory, szFilter, wFlags, "MDD", sPrompt)
If Not IsNull(varFileName) Then
' strip the trailing nulls
varFileName = Left(varFileName, InStr(1, varFileName, Chr(0)) - 1)
varFileName = ""
upg_GetDBFileName = varFileName
Function upg_OpenCommDlg( _
szInitialDir As String, szFilter As String, wFlags As Integer, _
szDefExt As String, szTitle As String) As Variant
Dim OPENFILENAME As upg_OPENFILENAME
Dim szFileName As String
Dim szFileTitle As String
Dim szTitleStr As String
Dim szCurDir As String
Dim wAPIResults As Integer
CRLF$ = Chr$(13) & Chr$(10)
'* Allocate string space for the returned strings.
szFileName = Chr$(0) & Space$(255) & Chr$(0)
szFileTitle = Chr$(0) & Space$(255) & Chr$(0)
'* Give the dialog a caption title.
szTitleStr = szTitle & Chr$(0)
'* Set up the defualt directory
szCurDir = szInitialDir & Chr$(0)
'* Set up the data structure before you call the GetFileNameName
OPENFILENAME.lStructSize = Len(OPENFILENAME)
' This ought to be a legal hWnd, but maybe there's no form open. So we'll
' use 0 here. Windows doesn't seem to mind.
OPENFILENAME.hwndOwner = 0
OPENFILENAME.lpstrFilter = upg_lstrcpy(szFilter, szFilter)
OPENFILENAME.NFilterIndex = 1
OPENFILENAME.lpstrFile = upg_lstrcpy(szFileName, szFileName)
OPENFILENAME.nMaxFile = Len(szFileName)
OPENFILENAME.lpstrFileTitle = upg_lstrcpy(szFileTitle, szFileTitle)
OPENFILENAME.nMaxFileTitle = Len(szFileTitle)
OPENFILENAME.lpstrTitle = upg_lstrcpy(szTitleStr, szTitleStr)
OPENFILENAME.Flags = wFlags
OPENFILENAME.lpstrDefExt = upg_lstrcpy(szDefExt, szDefExt)
OPENFILENAME.hInstance = 0
OPENFILENAME.lpstrCustomFilter = 0
OPENFILENAME.nMaxCustFilter = 0
OPENFILENAME.lpstrInitialDir = upg_lstrcpy(szCurDir, szCurDir)
OPENFILENAME.nFileOffset = 0
OPENFILENAME.nFileExtension = 0
OPENFILENAME.lCustData = 0
OPENFILENAME.lpfnHook = 0
OPENFILENAME.lpTemplateName = 0
'* This will pass the desired data structure to the Windows API,
'* which will in turn it uses to display the Open Dialog form.
wAPIResults = upg_GetOpenFileName(OPENFILENAME)
upg_OpenCommDlg = IIf(wAPIResults = 0, Null, szFileName)
WTFs in order:
- What do you plan to achieve with the lpstrcpy function?
lpstrcpy is a kernel function to copy a string from one location to another. It happens to return a pointer to the destination string when successful. These idiots are using a string copy function to return a handle to a string. In VB. Idiots.
- Why are the lpstr's in upg_OPENFILENAME declared as long integers?
Ok, so an lpstr is a long integer pointer to a string. So a C programmer would declare it as a long. But strings in VB are stored with pointers, just like they are in every other language I know. So you can declare lpstr's as Strings and save a lot of time and effort.
The only noticable difference is that VB strings are not null terminated, while most Windows API functions expect them to be. This is easily resolved by appending the ASCII character &H0 (stored in the VB constant vbNullChar) to the end of the string before calling the API function.
- Who on earth still uses dollar signs, percentages and ampersands to declare the data types of BASIC variables?
Last time I used things like "name$" and "id%" was when I was coding in GW-BASIC and QuickBASIC. Yes, in MS-DOS. Correct me if I'm wrong, but since at least version 4 of Visual Basic (ie: pre-VBA in MS Office) you have been able to use the As keyword to declare the data type of a variable. So "Dim name$" becomes "Dim name As String" and "id%" becomes "Dim id As Integer". Much more readable, and the added bonus is if you decide to change the data type of an existing variable, you don't need to find and replace all existing instances of the variable name. Sweet, huh?
- What's with the stype parameter to upg_GetDBFileName$?
Why use a string to represent a parameter that can only have two values: new or old? That's what a boolean is for. Declare the parameter as "bNewFile As Boolean" or similar. Or drop the parameter completely and display "New Database" if the sOpenPrompt$ parameter is blank.
- What's with the variants?
In Visual Basic, simple data types are not derived from the object data type. This is because BASIC was never designed as an object-oriented language. Visual Basic has the variant data type as hack to allow programmers to store either simple data types or an object in the same variable. For instance, a variant can contain a string, an integer or a recordset. This is similar to declaring a variable as as object in languages such as Java. Like the object type in Java, you should avoid it's use at all costs. If you know what the data type of a variable will be at development time, there is no excuse to use variants. Variants = BAD.
In this case, variants have been used to allow the upg_OpenCommDlg function to return Null. While it may be common for C functions to return null, this is not common practice in VB. This is because the Null data type is used (particularly when dealing with databases) to represent an unknown value. In this case, if the call to the Windows API returns zero, (ie: the dialog box was cancelled or closed) then the filename is empty, but it is not unknown. The function should return "" (an empty string) or to be more correct, the VB value Empty. This would allow the function to be defined as returning only strings, not variants. (For more information on the difference between Null and Empty in VB see Eric Lippert's post on MSDN.)
What makes this all the more entertaining is that the function upg_GetDBFileName$, (which is the only function in the application to call upg_OpenCommDlg,) checks to see if the value is Null and, if so, returns an empty string. Why not do this in upg_OpenCommDlg and save the effort of an extra IF statement?
- Why specify the initial directory to be C:\?
Never assume that C:\ exists. The user may have their CD drive as C:\, or they may not have access to view C:\ due to security settings.
And if you don't specify a folder when calling methods of the Common Dialog API, it will default to the user's home directory, which is likely to be the location of any document they are about to open anyway.
- Why declare CRLF$?
This is pointless for the following reasons:
1. vbCrLf is a Visual Basic constant that would replace it.
2. They don't use it in this code any way.
- What's with the Chr$(0)'s?
How about using vbNullChar and save yourself a function call or two. The two methods are equivalent, so this is only a minor WTF, but constants are always a good idea.
- What's with the null characters at the end of the null strings?
The programmers allocate szFileName as empty space to store a string using the common method of Space(255) to return a string of 255 space characters. In this instance, they've decided to improve on that by making it 257 characters with a null at each end. Why? Who knows. The API function will see that the first character is null, and consider it to be a zero length string. Upon the function's return, the memory allocated by the Space(255) function may be overwritten if the API decides to change the value of this variable. So the end null character is pointless.
Note that the Space function in VB is not a memory function that allocates space of 255 bytes, it is a string function that returns a string containing the specified number of ASCII space characters. The actual character is irrelevant when calling the API, as it will get overwritten anyway.
- What's going on with hwndOwner?
The Common Dialog API in Windows provides standard looking File Open/File Save/Print and Color Chooser dialog boxes. Because it creates these dialog windows from outside your application, hwndOwner is used to pass a handle to a window representing the owner of the dialog box. In most applications, this will be the handle of the window containing the button or menu item that triggered the dialog box to be displayed. This allows Windows to ensure that the dialog box stays modal in that application until such time as it has been closed. This also reduces the chances of having an orphaned dialog box drifting through the Z-order as you Alt-Tab between applications.
In most VB applications, your call to a wrapper function such as upg_OpenCommDlg would contain a parameter of type Form. Every window in VB is a Form. Every Form as a property called hWnd which returns the underlying handle used by Windows as an identifier of that window. Therefore, you can provide the Common Dialog API with an hWnd to keep the relationship between the application and the dialog box.
In this application, as noted in the comments, the developers weren't sure if a window would currently be displayed by their application, so they default the value of hWndOwner to zero, making the dialog box an orphan. Initially, their comment sounded beliveable. This function is buried deep in the code of the application, and could theoretically be called at anytime. However, this is a GUI application. All GUI applications have windows, and at least one of those windows will always be loaded. That window may not be visible, but Windows will still know it exists, so it will have an hWnd.
To make the matter worse, because this application is a Microsoft Access database, they could have easily used the Application.hWndApplication function to return the handle to the Microsoft Access window, irrespective of what is happening in the database itself.
- (See WTF 1 regarding the blatant abuse of the lpstrcpy function)
While this appears to be old code that may have been written for Microsoft Access '97, I thought it would be good to show how I would do this in recent versions of Microsoft Access:
Function PromptForDBFileName(Optional sOpenPrompt As String = "") As String
Dim fd As FileDialog
Set fd = Application.FileDialog(msoFileDialogFilePicker)
fd.Title = IIf(sOpenPrompt = "", "New Database", sOpenPrompt)
fd.Filters.Add "Access Database (*.mdb;*.mdd)", "*.mdb;*.mdd"
If fd.Show Then PromptForDBFileName = fd.SelectedItems(1)
Note that this function replaces all the code shown above.
I'm glad I've gotten that off my chest. Now to tidy this mess up...
Tuesday, September 12, 2006
CAUTION: POTENTIAL SPOILERS!
I went with a mate to see Snakes on a Plane (official site or IMDB) over the weekend. Well, what can I say?
I'd previously heard mixed reviews about this movie, some saying it was as cheap as the title made it out be, while others saying that it was actually a jolly good flick. This movie had the ability to be a well produced, well acted, suspenseful work of art. Unfortunately, it failed on almost all counts.
Samuel L. Jackson's character is made out to be the hero of the story, when in reality Nathan Phillips ("Sean Jones"), Kenan Thompson ("Troy McDaniel"), Julianna Margulies ("Claire Miller") could each have been the hero, as they all play a more valuable role in the movie than Jackson. (According to my mate, the original scripts were titled "Snakes on a Plane" and when the producers went to rename it, Jackson refused and asked it to be left with the crummy title.)
As usual, Rachel Blanchard plays a dizzy blonde ("Mercedes Harbont", another Paris Hilton), while Flex Alexander plays a professional musician ("Three G's"). Neither of these characters have any depth, nor do they perform any useful role in the story line, apart from perhaps Mercedes' dog.
The computer graphics were obvious (the boa constrictor scene is a perfect example) and the plot lines were flawed:
- How did snakes manage to get into the cockpit at the same time that they entered the rear cabin, without any appearing in the forward cabin or first class?
- How did the boa constrictor start off in the light fitting? After the boa gets out, there is a scene that shows quite clearly that the light fitting is a sealed unit.
- Surely pheromones would just make the snakes amorous, not angry?
- Isn't it a coincidence that the lifeboat is EXACTLY the same shape as the stairwell?
- Blowing a whole in the side of the plane would not be high on my list of suggestions as a possible way to resolve the snake situation. Not only would it reck the structural integrity of the plane, but the passengers would be unable to hold themselves in, and there would be no air to breath!
Nathan Phillips ("Sean Jones"), Kenan Thompson ("Troy McDaniel"), Julianna Margulies ("Claire Miller") clearly produced the most polished performances in the whole movie and they deserve congratulations for bring some real believability to the whole thing.
While the movie has some unexpected surprises (ie: snakes jumping out from seemingly innocuous places), it is very predictable and amateurish. I don't know what the budget was, but I'd hazard a guess at $1.95 (ooo, enough for a Big Mac!). With movies costing $14 to view these days, I came out feeling very short changed...
Posted by X-Cubed at 5:45 PM
Thursday, July 27, 2006
I recently tried out the Hamachi VPN client, but I was disappointed to see that the free version of Hamachi wouldn't run as a Windows Service, like OpenVPN.
It is possible to use an application like SrvAny, from the Windows NT Resource Kit to overcome this, but this is beyond your average user.
But within a half hour, I had created a simple little service launcher for it, using Visual Studio. This neat little app appears as a Windows service, and requires no configuration. When it starts, it checks the registry to determine where Hamachi is installed, then spawns Hamachi. When the service is stopped, it automatically kills the Hamachi process. Likewise, if the instance of Hamachi dies for whatever reason, the service will stop. This means that you can use Windows' inbuilt automatic service restart facilities to restart the service if Hamachi dies.
The downside is that the application cannot be configured, because there is no visible GUI. This isn't much of a problem as you can configure the application by running it normally and then closing the application, before you start the service.
This software is available as freeware from the link below.
Download the Hamachi Background Service launcher (10KB)
Posted by X-Cubed at 6:30 PM
Tuesday, June 27, 2006
At last, the Digium TDM400P has received a Telepermit to allow its use in New Zealand. This means that those who are interested in all things VoIP can now try it out for real with their own PABX! For those who are unawares, Asterisk is the way to go, and if you don't want to hack around getting it installed try the Asterisk@Home distro. To source the hardware I can recommend nicegear, and for New Zealand-based support, check out their sister site the New Zealand Asterisk Users Group. We've got a PBX set up at home now, so I thought it would be useful to share the experience.
If you intend on using Asterisk with a landline, make sure you contact your telephony provider to arrange Disconnect Supervision. Basically, the Digium board relies on this extra signalling to know when an incoming landline caller hangs up the phone. If you don't do this, then you'll find that the Digium board won't release the line when remote callers hang up.
If you are a Telecom customer, you need to ring Telecom Business on 126 and ask for "Clear Forward and Answer Reversal". It is available at no cost to business customers. Note that Telecom Residential customers may not be eligible for this service. However, you can try asking for it. Don't call 123 as they won't know what the hell you're on about, call 126 instead.
When installing the hardware drivers, make sure you use the patched version of the driver, as the current Zaptel driver build is not Telepermit compliant. Check out the bottom of the nicegear home page for more information. Make sure that you use the extra command line switches too. More information is available on ASTUG.
If you don't like the multitude of configuration files, you might want to look at using Asterisk RealTime to pull the configuration out of a MySQL database or similar. If you decide to go down this track, make sure you implement RealTime before you get stuck into configuring Asterisk, as it can be painful to retrofit. Note also that RealTime does not seem to be supported by any of the free front-ends for Asterisk, such as FreePBX, so all the configuration might need to be done manually or by using custom scripts.
While not for the faint hearted, Asterisk offers the coolest functionality and utimately the best value for money. After all, you can't beat free!
Posted by X-Cubed at 9:20 PM
Monday, May 29, 2006
Saturday, May 13, 2006
I have been watching with interest, the debate in the United States about the use of electronic voting systems during government elections. I personally believe that it is possible to have an electronic voting system that is a suitable replacement for the traditional paper-based methods, but the current system is a complete pig's ear.
For some reason, Diebold Election Systems was chosen as the preferred provider of the technology for the US Elections. Previous to this, the only systems that I associated with Diebold were the photocopier credit systems used at schools, universities and libraries, to allow you to pay for photocopies using your membership card. But it turns out that they also manufacture Automated Teller Machines and other self-service electronic kiosks. If the security of their ATM's is anything like the security of the voting systems, then banks have a lot to worry about.
Diebold were pulled up in July 2003, after it was discovered that encryption of the data collected by the systems was poor at best, and that voters could create their own smart cards, enabling them to vote more than once. And this was discovered after the voting software itself was released publicly on a Diebold website!
Then in September 2004, the state of California accused Diebold of using uncertified software on the machines. This resulted in a US$2.6 million settlement.
But even better than that, is the latest debarcle. A recent Slashdot article lead me in the direction of this blog entry by Bruce Schneier, world-reknowned computer security researcher. According to a recent report by Black Box Voting, the Diebold devices run a basic build of Microsoft Windows CE. While the details are sketchy, it would appear that the Diebold devices ship with Windows Plug'n'Play support enabled...
The devices have dual PCMCIA slots underneath the unit (indicated by B in the picture). The basic idea is that any voter with a PCMCIA memory card (or any other memory card with a PCMCIA adapter), can probably update the software on the machine at their will, and remove the card without anybody knowing that the software was changed.
Or for a more sophisticated attack, voters can use a standard Philips head screwdriver to disassemble the unit, and install an MMC/SD card in the hidden, internal SD slot (located just above the CMOS battery in the picture). Again, this card could simply be a memory card, containing a custom application, or it could be an SD WiFi card, providing a way to remotely trigger an attack. An external inspection of the unit would not show any indication of this type of attack.
Or if all this sounds too easy, you can replace the entire bootloader on the machine with your own, by simply having a file with the appropriate name sitting on a removable memory card while the machine is booting up. Although there are integrity checks in the boot loader, it sounds like they are limited to checking the filename, the size of the file and possibly the file header.
My, my, so many choices! It sounds like even school kids and script kiddies could crack these things.
Posted by X-Cubed at 1:05 PM