Monday, July 6, 2009

Text input on 3.0, Part 1.

2 little notes:
  1. GriP 0.1-11o has been released to fix issue 129, and that's the only change.
  2. class-dump-z 0.1-11o has been released for Mac, iPhone and Linux. See this page for a comparison of other class-dumps.




The rest of this text is written at the same time I'm doing the reverse engineering. You may feel confused, because that's how researching goes.


The text input framework is greatly revamped in 3.0. The new UIKit, which is 5 times bigger than 2.0, has 70% of weight taken up by keyboard layouts. And the nontrivial letter-based input managers, once part of UIKit, is moved into its private framework, TextInput.

What was changed? As the first try, let's view the view hierarchy, although this time we can do it in compile time. From the class-dump we know the old UIKeyboard class is still there, so let's run the statement
UILogViewHierarchy( [UIKeyboard activeKeyboard] );
when the keyboard is present.

So we got this with the English keyboard:

<UIKeyboard: 0xd47980; frame = (0 264; 320 216); opaque = NO; layer = <CALayer: 0xd47d00>> ({{0, 264}, {320, 216}}, (null), #0, 3)
..<UIKeyboardImpl: 0xd3da20; frame = (0 0; 320 216); opaque = NO; layer = <CALayer: 0xd37b40>> ({{0, 0}, {320, 216}}, (null), #0, 3)
....<UIKeyboardLayoutStar: 0xd48650; frame = (0 0; 320 216); layer = <CALayer: 0xd4ba40>> ({{0, 0}, {320, 216}}, (null), #0, 3)
......<UIKBKeyplaneView: 0xd59250; frame = (0 0; 320 216); userInteractionEnabled = NO; layer = <CALayer: 0xd59300>> ({{0, 0}, {320, 216}}, (null), #0, 3)
........<UIKBKeyView: 0xd595e0; frame = (1 119; 40 42); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xd596b0>> ({{1, 119}, {40, 42}}, (null), #0, 4)
........<UIKBKeyView: 0xd59af0; frame = (279 119; 40 42); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xd59b60>> ({{279, 119}, {40, 42}}, (null), #0, 4)
........<UIKBKeyView: 0xd59d10; frame = (1 173; 38 42); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xd59d50>> ({{1, 173}, {38, 42}}, (null), #0, 4)
........<UIKBKeyView: 0xd59e20; frame = (41 173; 38 42); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xd59e60>> ({{41, 173}, {38, 42}}, (null), #0, 4)
........<UIKBKeyView: 0xd4dca0; frame = (81 173; 158 42); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xd4dab0>> ({{81, 173}, {158, 42}}, (null), #0, 4)
........<UIKBKeyView: 0xd5bd10; frame = (241 173; 78 42); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xd5bd50>> ({{241, 173}, {78, 42}}, (null), #0, 4)


In fact, all other keyboards — except the handwriting ones, and emoji also — now uses UIKeyboardLayoutStar to manager the layout. (The old UIKeyboardLayoutRoman is still there.)

The 6 UIKBKeyView are the special keys, as shown here:


The class dump of UIKeyboardLayoutStar shows some interesting members, e.g. m_keyboardName, m_keyplaneName, the list of dictionaries, etc. Take the Arabic keyboard as example.

  • m_keyboardName = @"iPhone-Portrait-Arabic"
  • m_keyplaneName = @"Letters"
  • m_states is a dictionary of 35 entries, with keys as UIKBKey to values as NSNumber (integers).
  • m_keyboard is the keyboard (UIKBKeyboard), which contains a lot of geometry info.


If we can replace UIKBKeyboard, then we can make our own layout. Where is that UIKBKeyboard set? Looking at the disassembly of UIKit (which is kinda painful now due to its huge size), we see the relevant part is at -[UIKeyboardLayoutStar setKeyboardName:appearance:]:

NSString* UIKeyboardGetCurrentInputMode();
NSBundle* UIKeyboardBundleForInputMode(NSString* mode);
NSData* GetKeyboardDataFromBundle(NSString* name, NSBundle* kbbundle);
NSBundle* _UIKitBundle();
UIKBKeyboard* UIKBGetKeyboardByName(NSString* name);

@implementation UIKeyboardLayoutStar
-(void)setKeyboardName:(NSString*)name appearance:(UIKeyboardAppearance)appr {
UIKBKeyboard* keyboard = [m_keyboards objectForKey:name];
if (keyboard == nil) {
NSData* keyboardData = GetKeyboardDataFromBundle(name, UIKeyboardBundleForInputMode(UIKeyboardGetCurrentInputMode()));
if (keyboardData == nil)
keyboardData = GetKeyboardDataFromBundle(name, _UIKitBundle());
if (keyboardData != nil) {
NSKeyedUnarchiver* keyboardArchive = [[NSKeyedUnarchiver alloc] initForReadingWithData:keyboardData];
keyboard = [keyboardArchive decodeObjectForKey:@"keyboard"];
[keyboardArchive finishDecoding];
[keyboardArchive release];
} else
keyboard = UIKBGetKeyboardByName(name);
}

if (keyboard == nil)
return;

// the rest are not interesting at the moment.
}
@end


So they defined 3 ways to fetch the UIKBKeyboard, (1) GetKeyboardDataFromBundle from the keyboard bundle (/System/Library/TextInput/TextInput_xx.bundle/), (2) GetKeyboardDataFromBundle from the UIKit bundle, and (3) UIKBGetKeyboardByName.

What does GetKeyboardDataFromBundle do? Basically,
GetKeyboardDataFromBundle (name, bundle) {
Go to the bundle's resource path;
Open ".keyboard";
Return the content of this file as NSData*.
}


But there isn't a single .keyboard file in the TextInput bundles! How about in UIKit.bundle? No, not even one. So the only path taken would be by UIKBGetKeyboardByName. And what does it do? Search from a function table with that name, and call that function. This probably explain the 70% mass of UIKit — these are functions to define the keyboards. Why they don't use the .keyboard files is a mystery, but from this it seems we can exploit the format to let iKeyEx run on it.

2 comments:

  1. Sounds like good news, glad to see you working on this as 5row keyboard and hclipboard (for keeping a set of templates etc.) are two of the reasons I haven't upgraded to 3.0 yet.

    Also, are you planning on updating your calculator search bundle for spotlight to 3.0 final as it looked pretty handy.

    Thanks for all your cool improvements!

    ReplyDelete
  2. KennyTM, has you can see on grip´s page comments, this new version has big problems...

    ReplyDelete