Monday, December 21, 2009

Hacking the TTouchKeyboard Part I

Hacking the TTouchKeyboard Part I
Hacking the TTouchKeyboard Part II
Hacking the TTouchKeyboard Part III
Hacking the TTouchKeyboard Part IV
Hacking the TTouchKeyboard Part V (not published yet)

The TTouchKeyboard is a a fairly flexible on screen keyboard that supports multiple keyboard layouts but only shipped with the number pad and the standard keyboard layout for a whole lot of input languages. How to create these keyboard layouts has been a mystery, until now. Over the next few posts I'll provide some tools, documentation and sample code to build everything you need to hack the TTouchKeyboard.

The first bit of code I'm going to provide demonstrates listing all the keyboard layouts that are available. It's pretty simple really. A keyboard layout is just a resource with the string "KEYBOARD" in the name.

procedure GetKeyboardLayoutNames(ANames: TStrings);

function EnumResNames(Module: HMODULE; ResType, ResName: PChar;
Nothing: Pointer): Integer; stdcall;
const
sResourceName = 'KEYBOARD';
begin
if Pos(sResourceName, ResName) > 0 then
LayoutNames.Add(ResName);
Result := 1;
end;

begin
LayoutNames := TStringList.Create;
try
EnumResourceNames(HInstance, RT_RCDATA, @EnumResNames, 0);
ANames.Clear;
ANames.AddStrings(LayoutNames);
finally
FreeAndNil(LayoutNames);
end;
end;


Once you have a layout you can load it by calling this little function.

function LoadLayout(const LayoutName: string): TVirtualKeyLayout;
var
Stream: TResourceStream;
TempStream: TStream;
begin
Result := nil;
Stream := TResourceStream.Create(HInstance, LayoutName, RT_RCDATA);
try
Result := TVirtualKeyLayout.Create;
Result.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;


Next I'll show how to iterate the data structures and save it to an XML file for easy text manipulation.

Wednesday, December 16, 2009

Book Review: Physics of Superheros



With Christmas right around the corner I thought I'd share with everyone one of my favorite stocking stuffers, The Physics of Superheros. Now I haven't read the entire book and have only thumbed through the second edition at the local bookstore, but what I have read is awesome.

The author, James Kakalios, is a college physics professor who teaches his physics courses by using examples comic books. How awesome is that! If only this was my text book in High School and College physics. Anyway, it is a worthy read in my opinion and anyone who reads this blog would probably enjoy this book.





Please read my full disclosure of any biases I may have.

Thursday, December 3, 2009

Hang With Cool People

The other day I read that depression is contagious and thought to my self "duh!" Just as you are what you eat you are who you hang out with. Eat too many carrots and you turn orange (seriously, my daughter did this). Hang out with the people you want to be like and you'll be just like them. Who say's you can't learn by osmosis.

Wednesday, December 2, 2009

Glacier National Park

Last summer Glacier Park Magazine editor Chris Peterson engaged in a project to photograph Montana's Glacier National Park over 100 consecutive days. He used a mix of film and digital cameras that would have been used over the course of the Park's 100 years.

Check out the blog here with a post from each day. The photos are awesome.

My personal favorite is Day 62 because I know exactly how Chris feels about that tree. I have many trees and rocks in various parks that I could spend hours at.

Tuesday, December 1, 2009

Full Disclosure

On October 5 2009 the Federal Trade Commission in the United States published an 81 page guideline requiring that all product endorsements and testimonials on web sites, blogs, Facebook, Twitter, and other social media disclose "...their connection, if any, with the advertisers; and also reveal the receipt of free products and or payments from the advertisers" effective today, December 1 2009.

This page will be updated to make clear my commercial relationships going forward and will be permanently linked on the site's side panel, found on the right side of this blog.

I am employed by Embarcadero Technologies Oracle.

I have Amazon, Google and B&H Photo advertisements on this blog and I earn a percentage of the sale if you click on the link and buy the item. I don't do it for the money, I do it to provide the readers of this blog links to products that I think are exceptional. I would not provide a link to the product if I didn't think it was exceptional and I didn't already use it. I really don't earn much but it's something and it is fun to get a check once in a while.

I have had a working relationship with Apple since the release of Aperture. I have done contract photography for them, they have provided me with long term use of software and hardware (although more software than hardware) and paid me money (although I think I have given them more money over the years than they have given me). I have also owned stock in Apple for years. None of this influences what I write here about their products.

I own stock in various companies but mostly are in mutual funds so chances are if there is a stock some fund manager has purchased a small percentage of it with a few of my dollars and I guarantee none of that influences stuff that I write.

Tuesday, November 24, 2009

Debugging Direct2D

When debugging Direct2D the debugger can be really slow, and I've recently discovered the culprit of the poor performance; the Intel graphics driver. The same performance problems happen in Visual Studio so it isn't a C++Builder or Delphi bug. If you are debugging Direct2D just run without debugging or get a system with an NVIDIA or ATI graphics card.

Touch Demo

My Touch Move demo is now complete. Here is a quick list of the four parts:

Part I
Part II
Part III
Part IV

And you can download source code from CodeCentral for all four demos. The download wasn't working but has now been fixed.

After the Thanksgiving Holiday I'll start something new. I was thinking of how to hack the touch keyboard, but if anyone has suggestions regarding things you'd like to know about touch drop me a line either via comments or the contact me link on the right side of this blog and I'll try and make a post out of it.

Monday, November 23, 2009

Unicycling in Africa

My good friend Corbin got married this summer to the lovely Louise (yours truly photographed their wedding) and then they went for their honeymoon in Africa on unicycles! Here's an awesome video that Corbin made:



Here is another video from their tour. My daughter loves this one!

Thursday, November 19, 2009

Touch Demo Part IV

Previously I posted Part I, Part II and Part III of the Touch Demo.

This is the final step of the touch demo where I will be adding the inertia processing and mouse support. Mouse support is easy but requires a minor refactoring of the touch message handler so the mouse can piggy back on it. For CodeRage I did add a touch keyboard but I will leave that as an exercise for the user.

And now you can download source code from CodeCentral for all four demos. Update: The download wasn't working but it has now been fixed.

Inertia processing requires the Manipulations unit. Then the object that is to be manipulated, in this case TGlowSpot, should implement the _IManipulationsEvents interface which is a COM event sync. Here is how that class changed:


TGlowSpot = class(TInterfacedObject, _IManipulationEvents)
private
FInertia: IInertiaProcessor;
FManipulator: IManipulationProcessor;
FInertiaCookie, FManipulatorCookie: LongInt;
FCompleted: BOOL;
public
X, Y, Radius: Integer;
Alpha: Extended;
FadeIn: Boolean;
Color: TColor;
ID: Integer;

protected
procedure DoTouch(const APoint: TPoint; AID: Integer; ATouchMessage: TTouchMessage);

{ _IManipulationEvents }
function ManipulationStarted(X: Single; Y: Single): HRESULT; stdcall;
function ManipulationDelta(X: Single; Y: Single; translationDeltaX: Single;
translationDeltaY: Single; scaleDelta: Single; expansionDelta: Single;
rotationDelta: Single; cumulativeTranslationX: Single;
cumulativeTranslationY: Single; cumulativeScale: Single;
cumulativeExpansion: Single; cumulativeRotation: Single): HRESULT;
stdcall;
function ManipulationCompleted(X: Single; Y: Single;
cumulativeTranslationX: Single; cumulativeTranslationY: Single;
cumulativeScale: Single; cumulativeExpansion: Single;
cumulativeRotation: Single): HRESULT; stdcall;
public
constructor Create(AParent: TWinControl);

procedure Paint(Canvas: TDirect2DCanvas);
procedure Disconnect;
procedure ProcessInertia;
end;
...


Each TGlowSpot needs to hold on to an instance of the inertia processor and the manipulation processor and then connect up to them with the event sync. That is done in the constructor:

constructor TGlowSpot.Create(AParent: TWinControl);
begin
inherited Create;
Alpha := 1;
Radius := 80;
FadeIn := False;
Randomize;
Color := RGB(Random(255), Random(256), Random(256));
ID := -1;
FCompleted := True;
ID := -1;
FCompleted := True;

FInertia := CreateComObject(CLSID_IInertiaProcessor) as IInertiaProcessor;
FManipulator := CreateComObject(CLSID_IManipulationProcessor) as IManipulationProcessor;
InterfaceConnect(FInertia, _IManipulationEvents, Self, FInertiaCookie);
InterfaceConnect(FManipulator, _IManipulationEvents, Self, FManipulatorCookie);

FInertia.put_DesiredDeceleration(0.001);

FInertia.put_BoundaryLeft(200);
FInertia.put_BoundaryTop(200);
FInertia.put_BoundaryRight(AParent.Width - 200);
FInertia.put_BoundaryBottom(AParent.Height - 200);

FInertia.put_ElasticMarginLeft(200);
FInertia.put_ElasticMarginTop(200);
FInertia.put_ElasticMarginRight(200);
FInertia.put_ElasticMarginBottom(200);
end;


And then of the disconnect is done like this:

procedure TGlowSpot.Disconnect;
begin
InterfaceDisconnect(FInertia, _IManipulationEvents, FInertiaCookie);
InterfaceDisconnect(FManipulator, _IManipulationEvents, FManipulatorCookie);
end;


The inertia processing is handled with each WM_TOUCH message that is fired. It's pretty simple really, if there is a move, call the manipulation COM object's ProcessMove, and do the same with down. Up is a bit different because of the inertia but all you need to do is set the velocity and the location.

procedure TGlowSpot.DoTouch(const APoint: TPoint; AID: Integer;
ATouchMessage: TTouchMessage);
var
Vx, Vy: Single;
begin
case ATouchMessage of
tmMove:
begin
X := APoint.X;
Y := APoint.Y;
FManipulator.ProcessMove(AID, APoint.X, APoint.Y);
end;

tmDown:
begin
X := APoint.X;
Y := APoint.Y;
FManipulator.ProcessDown(AID, APoint.X, APoint.Y);
end;

tmUp:
begin
ID := -1;

FManipulator.ProcessUp(AID, APoint.X, APoint.Y);

FManipulator.GetVelocityX(Vx);
FManipulator.GetVelocityY(Vy);

FInertia.put_InitialVelocityX(Vx);
FInertia.put_InitialVelocityY(Vy);

FInertia.put_InitialOriginX(X);
FInertia.put_InitialOriginY(Y);

FCompleted := False;
end;
end;
end;

function TGlowSpot.ManipulationCompleted(X, Y, cumulativeTranslationX,
cumulativeTranslationY, cumulativeScale, cumulativeExpansion,
cumulativeRotation: Single): HRESULT;
begin
Result := S_OK;
end;

function TGlowSpot.ManipulationDelta(X, Y, translationDeltaX, translationDeltaY,
scaleDelta, expansionDelta, rotationDelta, cumulativeTranslationX,
cumulativeTranslationY, cumulativeScale, cumulativeExpansion,
cumulativeRotation: Single): HRESULT;
begin
Inc(Self.X, Round(translationDeltaX));
Inc(Self.Y, Round(translationDeltaY));
Result := S_OK;
end;

function TGlowSpot.ManipulationStarted(X, Y: Single): HRESULT;
begin
Result := S_OK;
end;

procedure TGlowSpot.ProcessInertia;
begin
if not FCompleted then
FInertia.Process(FCompleted);
end;


The last bit of code I want to talk about is the mouse handling. I refactored the handling of the WM_TOUCH message into WMTouch and ProcessTouchMessages. ProcessTouchMessages is the common function that the mouse message can call to get the same inertia behavior.

procedure TTouchForm.WMTouch(var Message: TMessage);

function TouchPointToPoint(const TouchPoint: TTouchInput): TPoint;
begin
Result := Point(TouchPoint.X div 100, TouchPoint.Y div 100);
PhysicalToLogicalPoint(Handle, Result);
end;

var
TouchInputs: array of TTouchInput;
TouchInput: TTouchInput;
Handled: Boolean;
Point: TPoint;
TouchMessage: TTouchMessage;
begin
Handled := False;
SetLength(TouchInputs, Message.WParam);
GetTouchInputInfo(Message.LParam, Message.WParam, @TouchInputs[0],
SizeOf(TTouchInput));
try
for TouchInput in TouchInputs do
begin
Point := TouchPointToPoint(TouchInput);

if (TouchInput.dwFlags and TOUCHEVENTF_MOVE) <> 0 then
TouchMessage := tmMove
else if (TouchInput.dwFlags and TOUCHEVENTF_UP) <> 0 then
TouchMessage := tmUp
else if (TouchInput.dwFlags and TOUCHEVENTF_DOWN) <> 0 then
TouchMessage := tmDown;

ProcessTouchMessages(Point, TouchInput.dwID, TouchMessage);
end;

Handled := True;
finally
if Handled then
CloseTouchInputHandle(Message.LParam)
else
inherited;
end;
end;

function TTouchForm.ProcessTouchMessages(const APoint: TPoint; ID: Integer;
TouchMessage: TTouchMessage): TGlowSpot;
var
Spot: TGlowSpot;
begin
Result := nil;
Spot := FindSpot(ID);

if Spot = nil then
begin
Spot := FindSpot(APoint);

if Spot <> nil then
Spot.ID := ID;
end;

if Spot = nil then
begin
Spot := TGlowSpot.Create(Self);
Spot.ID := ID;
FSpots.Add(Spot);
end;

Spot.DoTouch(APoint, ID, TouchMessage);
Result := Spot;
end;


And last the mouse handling functions with the check for ssTouch in the Shift parameter. If that check wasn't there each message would happen effectively twice because the touch messages also send mouse messages for backwards compatibility with applications that don't support touch.

procedure TTouchForm.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if ssTouch in Shift then Exit;
FMouseDown := True;
ProcessTouchMessages(Point(X, Y), 0, tmDown);
end;

procedure TTouchForm.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
if ssTouch in Shift then Exit;
if FMouseDown then
ProcessTouchMessages(Point(X, Y), 0, tmMove);
end;

procedure TTouchForm.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
Spot: TGlowSpot;
begin
if ssTouch in Shift then Exit;
Spot := ProcessTouchMessages(Point(X, Y), 0, tmUp);
Spot.ID := -1;
FMouseDown := False;
end;


I just realized that there is a bug in the program. It will run just fine but crash pretty bad if it runs too long. I guess I never demoed it very long before. So I'll leave debugging this crash as an exercise for the user.

Tuesday, October 27, 2009

Dell Latitude XT2 XFR Rugged Tablet



Dell just introduced the Latitude XT2 XFR; the rugged version of the XT2 multitouch tablet machine I've mentioned a few times before only with a name that is more of a mouth full. So for those of you waiting for a rugged machine before jumping into multitouch wait no longer. The short spec list is 12 inch multitouch display, 5lbs and 1.5 inches thick and mil spec badassness! It also comes with Vista. I guess Windows 7 isn't rugged certified? Just as well, first thing I do when I get a new machine is format it anyway. For all the awesomeness it'll set you back some serious coin. The base model is $3,600 but I loaded one up for just under $5k! I don't think I can justify one of these to Nick, but damn!

Monday, October 26, 2009

Dell SX2210T - Multitouch Monitors

The first standalone multi-touch monitor is the SX2210T. Looks pretty awesome for those that want to add a multi-touch monitor tho an existing computer.

Updated: Link goes to the product page (which is up now) rather than the shopping cart.

Thursday, October 15, 2009

Delphi Tips and Tricks - Additional Files

One of the most useful features in the Delphi IDE is CTRL + F12, or the "Search for units" dialog. One of the problems we encounter is we often want to look at files that are not in any of the opened projects. So a hidden registry key was created to add a list of files to the "Search for units" dialog. You can specify the text file by creating a string value under HKEY_CURRENT_USER\Software\CodeGear\BDS\\Globals named "AdditionalViewFiles". Set the string to the file you want the IDE to load. Note that IDE environment variables can be used here. For Example I have $(TP) set to "c:\source" and the AdditionalViewFiles string value is set to $(TP)\AdditionalViewFiles.txt.

The text file format is name value pairs like any other INI file where the name is the short name you want to see and the value is the full path to the file (and this will expand IDE environment variables as well). For example:

Foo.pas=$(TP)\somedirectory\Foo.pas