Sunday, August 30, 2009

iKeyEx 0.1-99g is released


  • “Text with traits” is now supported, that means your can add color, change font and text size for each key. However, you still cannot use key with image.
  • Native control keys support. (Left, right, home, page down, etc.)

Wednesday, August 26, 2009

Easier way to get UIView hierarchy.

Before I documented how you can obtain the UIView hierarchy in syslog. It uses custom function, but there is are 2 built-in way to get this.

The first, standard way is to send a GSEvent type #500 to the application, then the dump will be written to /tmp/UIDump in plist format. You can achieve the same by calling in gdb:

call (void)[[UIApplication sharedApplication] _dumpUIHierarchy:0]

When the dump is completed, a Darwin notification "" will be posted.

(You can also take a screenshot with _dumpScreenContents:0 / GSEvent type #501, but the file is in JPEG and pressing Lock+Home isn't that hard...)

(Note: May fail for AppStore apps due to sandboxing.)

GSEvent Recording and Playback in 3.0

iPhoneOS has built-in API for application macro since 2.x. (Of course it was never documented.) (And the API was really changed in 3.0 to adopt for multiple recorders.)

For example, to record everything you've done during the for yourUIApplication:

recorder = [Recorder new];
[yourUIApplication _addRecorder:recorder];
[recorder release];

The command _addRecorder: adds the object to the application's event recorder array. You can remove your recorder at anytime with

[yourUIApplication _removeRecorder:recorder];

An event recorder must conform to the informal protocol

@protocol UIEventRecorder

Here, "event" is a GSEvent converted to a plist using GSEventCreatePlistRepresentation(). You can add the event to an array and save that array for later reference, e.g.

@interface Recorder : NSObject { NSMutableArray* eventList; } @end
@implementation Recorder
-(id)init { if ((self = [super init])) eventList = [NSMutableArray new]; return self; }
-(void)save { [eventList writeToFile:@"events.plist" atomically:YES]; }
-(void)recordApplicationEvent:(NSDictionary*)event { [eventList addObject:event]; }

Now say you want to play back the events you've previously collected. Just use this code:

NSArray* eventList = [NSArray arrayWithContentsOfFile:@"events.plist"];
float playbackRate = 1;
[app _playbackEvents:eventList atPlaybackRate:playbackRate messageWhenDone:target withSelector:@selector(done:)];

The higher the playback rate, the faster the system will run the events.

When the playback is finished, the "done:" selector will be called for "target". This selector must have a signature of


the "detail" argument contains exactly 1 key, "UIApplicationEventRecordingDeliveryTimeUserInfoKey", which points to an array of the time the corresponding event happened.

Tuesday, August 25, 2009

Decompressing .zip files with iWorkImport.framework.

.zip files need to be supported in lots of places on the iPhoneOS. .ipa are zip files, .docx are zip files, .key are zip files. Unfortunately there is no centralized framework or library to handle these zip files. In fact, all Bom.framework, OfficeImport.framework and iWorkImport.framework have their own, private implementation to decompress a .zip file. Out of the three, iWorkImport.framework is the friendliest.

This example code shows how to open a .zip file and read the content:

(Of course, if you write your new application, you should not unzip like this :) use something documented such as ziparchive.)

Monday, August 24, 2009

Trivia for Preference Bundles.

1. How to create that red delete button?

The red delete button in VPN is in fact very easy to implement. All you need to do is add the following code:

#import <UIKit/UIPreferencesDeleteTableCell.h>
@interface PSDeleteTableCell : UIPreferencesDeleteTableCell @end
@implementation PSDeleteTableCell
-(void)setValueChangedTarget:(id)target action:(SEL)action userInfo:(NSDictionary*)info {
[self setTarget:target];
[self setAction:action];
-(UILabel*)titleTextLabel {
UILabel* res = [super titleTextLabel];
res.textColor = [UIColor whiteColor];
return res;

and then in the specifier plist, modify your button as:

{ cell = PSButtonCell;
action = nukeFromOrbit;
label = "Nuke from Orbit;
cellClass = PSDeleteTableCell;

The result is:

2. PSEditableListController

Normally list controllers inherit from the PSListController, but you can make a class inherit from PSEditableListController so that it becomes... editable! Actually, more like deletable. There will be an extra Edit/Done button on the top right hand corner, pressing it will reveal the deletion control. If you actually delete a cell, the corresponding specifier's deletionAction will be called.

3. A list of editing panes

There are several editing panes in Preferences.framework and .app. To use those panes, just use the specifier plist

{ cell = PSLinkCell;
detail = PSDetailController;
pane = PaneName;
... }

  • PSMultiValueEditingPane (Include just a PSSegmentCell, useless by itself.)

  • PSTextEditingPane (A pane containing a text field and a keyboard.)

  • RegulatoryPane (A static image showing the device is FCC approved and in other countries as well.)

  • LegalMessagePane

  • TimeZonePane (Time zone selector. The returned value doesn't contain just time zone, but also city location.)

  • RingtonePane (Note that the ringtone will be played when selected.)

Sunday, August 23, 2009

iKeyEx 0.1-99f Released, now supports .cin IME.

Download: here

Changelog: There were 3 versions in between, "d" was mainly to fix issue 1, "e" was an emergency release to fix a crashing bug on the preferences, and "f" fixes rest of the bugs and improves performance of characters tables.

    • Fixed issue 1. .cin input manager can be used natively with iKeyEx.
    • You can now the method to invoke the keyboard chooser, the disable it completely
    • Fixed a bug that causes crash when a layout.plist using text with traits is encountered. Thanks DB42 (of OpenHebrew) for reporting!
    • UI for cache removal.
    • Basic localization (en, es, zh_TW, zh_CN).

    • Fixed a bug that may cause preferences to crash. Thanks @mikitomo for reporting!
    • Loading characters table is now much faster (from O(n) to O(1)), that the second time you use a .cin input method will load instantly, instead of need to wait for 1 ~ 3 seconds (at a price of ~200 KiB disk space).
    • Name for layout.plist keyboard cache are now correctly generated. Thanks DB42 for reporting!
    • Keyboard list will be properly updated after Mix and match.
    • The counter in "Delete cache" will be properly updated after a cache entry is removed.
    • Candidate list computation won't lock up the application forever (at worst case) anymore.
    • Support for layout.plist on non-default keyboard type is fixed. Thanks DB42 for reporting!

Actually issue 1 was a major reason why the iKeyEx project exists, and now it is finally implemented. The .cin input method is mainly for use in table-based Chinese input method, but it can be used to implement that things too, e.g. the MultiTap method + Text prediction can be made on top of a .cin IME. The technical detail of .cin files and the technology around it can be found here.

The IME support is nowhere versatile. The .cin IME in iKeyEx so far only supports:
  1. Prefix searching (like autocompletion)
  2. Phrase completion

In the future it may also support:
  1. Key charges
  2. Dynamic dictionaries.

Wednesday, August 12, 2009

Embedding a preference bundle, in your app.

If you want to use Preferences.framework in your app, you can do it like this.

First, make a PSListController subclass as you would when creating a Preference Bundle. Then create a subclass of PSRootController, with code like this:
@interface MyRootController : PSRootController
@implementation MyRootController
-(void)setupRootListForSize:(CGSize)size {
PSSpecifier* spec = [[PSSpecifier alloc] init]; = @"Anything";

MyListController* root = [[MyListController alloc] initForContentSize:size];
root.rootController = self;
[root viewWillBecomeVisible:spec];
[spec setTarget:root];
[spec release];
root.parentController = self;

[self pushController:root];
[root release];

Finally, in your app's delegate, write these code:
-(void)applicationDidFinishLaunching:(UIApplication *)application {
window.frame = [UIScreen mainScreen].applicationFrame;
MyRootController* rootCtrler = [[MyRootController alloc] initWithTitle:@"Some Title" identifier:@"com.yourcompany.appName"];
[window addSubview:[rootCtrler contentView]];

Why do this? One major reason it that we can now debug the Pref Bundle in Simulator, and speed up development.

iKeyEx 0.1-99c and QuickScroll 0.1-12d released

iKeyEx 0.1-99c was released, changes were:
  1. Fixed a silly bug causing autocorrection not working for internal input modes.
  2. nlist nows checks for N_ARM_THUMB_DEF just for safety. Probably make it works for 3GS, probably not.
  3. Input modes won't suddenly "reset" now.

QuickScroll 0.1-12d (@r437) fixed a bug where you could scroll horizontally (and actually, vertically too) one pixel too far. Also, it now synchronizes when content size change (e.g. additional content of a web page is loaded, or orientation is changed).

QuickScroll 0.1-12c released (bug fixes)

QuickScroll 0.1-12c was released. The changes include:
  • Can scroll to the top in MobileNotes now.
  • Now the triple tap will iterate all touches, eliminating any possibility that a triple tap (that ought to be caught) is missed.
  • QuickScroll will be suppressed on non-scrollable views.
  • QuickScroll (PDF paging) will be suppressed on PDF files with 0 pages (usually those incomplete files).
  • Japanese localization.

(And the dylib size drained down from 20,000 bytes to 19,968 bytes :( )

0.1-12c was submitted to BigBoss, so it should appear in a few days. Those who can't wait can download from here.

Meanwhile I want to explain one decision. Why triple tap? My initial intension was three-finger touch, but I sometimes hold the device with one hand and the other occupied, so any multitouch ideas are ruled out. But how about, like, triple-tapping the home button? This won't work either, as it is possible that two web views appear on screen simultaneously. This excluded all solutions not using the screen, e.g. using the home/lock buttons, shaking the device, etc.

If you have installed iKeyEx 0.1-99b (make sure you're using 0.1-99b) and experienced Crash and not using 3GS please email me, preferably with a crash log (and syslog if possible).

Tuesday, August 11, 2009

QuickScroll 0.1-12b released, supports PDF paging and activating from anywhere

Download is here, as usual. The seventy people who have installed 0.1-12a should also upgrade for a correct offset calculation in MobileSafari.

Now QuickScroll becomes a 1.5-day project, so there are much more features introduced.


  1. Activate QuickScroll from anywhere, like web pages, long text views, tables, MobileTerminal, SpringBoards, just anywhere you can scroll. (Blame MobileSafari for requiring me to introduce this.)

  2. Jump to any page in a PDF file.

  3. Fixed from 0.1-12: Rotating from portrait to landscape position no longer makes the "Close" button unreachable.

  4. Modified: The alert box is now smaller.

  5. Language support for English, Spanish, Italian (thanks Sagitt for a few translations) and Chinese (T+S).

This version should be stable enough and I think no more features are needed (besides more localizations). The very same package shall appear in Cydia repo soon if I receive no bug reports by tomorrow (14:00 at GMT+8).

P.S. The dylib is exactly 20,000 bytes.

Monday, August 10, 2009

QuickScroll 0.1-12 released

QuickScroll 0.1-12 is released. It's a quick (:p) project done within half a day.

QuickScroll lets you scroll through a Web page (including PDF files and Word documents) quickly. To use:
  1. In a long web page, triple tap on anywhere (hold one finger and triple tap the other to avoid resizing).
  2. A dialog will appear. Drag the green box to scroll around.

Sunday, August 9, 2009

iKeyEx 0.1-99b is released (3.0 only)

Hi girls and guys. 0.1-99f has been released. Maybe check the newest post first before commenting? ;p

iKeyEx 0.1-99b is released. This is a beta version, and the release version will use the version number "0.2".

Compared with the iKeyEx 2.x series, a lot of things are changed that may render an existing keyboard useless.

Firstly, text with traits are not supported yet, meaning keyboard that entirely relying on it such as 1Click@Thai (com.iclick.thai5rowkeyboard) will entirely unusable in this version.

Secondly, libiKeyEx.dylib is rewritten and uses a different API. That means existing keyboard more complex than a standard keyboard, including â„ŹClipboard and 5 Row QWERTY cannot run. If dependence is not a lot, however, transition to 3.x is pretty easy.

Thirdly, the input mode architecture is changed. Previously, an input mode linked to exactly one layout. It is decoupled on 0.1-99b. For example, I can have a 5-row QWERTY keyboard for English, for Japanese and for Chinese simultaneously. You can do it in the Mix & Match options in Settings → iKeyEx. But because a layout no longer completely defines an input mode, installing a keyboard may not be immediately usable.

Example 1: The Deutsch 5 Row Keyboard (com.ipuhelin.keyboards-5rowdeutsch) will be installed using English (US) as input manager. You can fix it by choosing Settings → iKeyEx → Mix and Match → 5RowDeutsch → Input manager → German (Germany).

Example 2: The Dvorak 5Row Keyboard (hk.alim.dvorak5row) was installed without adding itself to the list of input modes. In 2.x you'd add it from International Keyboards, but this is more complicated on 3.x due to the decoupling. The procedure can be summarized into this pic:

In words,
  1. Go to Settings → iKeyEx
  2. Select "Mix and Match",
  3. then "Create", to create a new input mode.
  4. Now, select "Layout",
  5. and change it to "5-Row DVORAK" at the top.
  6. After that, select "Name"
  7. and give it a name. It must not be empty.
  8. Finally, go back to the iKeyEx screen and tap "Keyboard"
  9. You should be able to see the new input mode. Tap on (+) to add it.

I haven't put in any localization yet. They will appear in the next version.

Friday, August 7, 2009

The 8th property list object

We've been long told that there're only 7 types of property list objects: CFString, CFNumber, CFBoolean, CFDate, CFData, CFArray, and CFDictionary. These are the only types that can be encoded into a property list without conversion. At least it's what on the surface. There's in fact an eighth type of property list object, called _CFKeyedArchiverUID, to support the NSKeyed[Un]Archiver.

An archived data is actually a binary property list. The encoded objects are laid out linearly, and each object has a UID numbered serially. Objects can be linked to each other, forming an "Object Graph". The links are encoded as _CFKeyedArchiverUIDs.

A _CFKeyedArchiverUID is essentially a 32-bit integer. Why isn't a CFNumber be used? Because it would then be impossible to distinguish between a regular number and a link.

Becuase _CFKeyedArchiverUID is not publicly documented (you can find its trace in the CF source code), it is a perfect type to conceal these supposed-to-be internal information.

But it introduce problems when handling with plist files that have a _CFKeyedArchiverUID. Firstly, Apple's Property List Editor does not recognize them. And even worse, these UIDs are stripped when saved. You can try this: open any .nib file, without any modification save it as another file. You'll find that the new file becomes smaller.

Also, _CFKeyedArchiverUID does not have a specialized -description method. When you NSLog a dictionary containing with object, a <NSCFType: 0xpointer> will be displayed (although CFShow does show the content).

In XML format, _CFKeyedArchiverUID will be output as
<dict> <key>CF$UID</key> <integer>123</integer> </dict>

This means any dictionaries containing a single entry with key CF$UID and an integer value will be converted to a _CFKeyedArchiverUID.

Wednesday, August 5, 2009

LC_DYLD_INFO_ONLY demystified

One day after I found no useful info on LC_DYLD_INFO_ONLY, there's already a paste showing the answer (this is Google cache, the original one is already invalidated). I have copied it to pastie in case the Google cache is removed. From the comment we clearly see the intent, and hints to how to decode this command.

Using CPRegularExpression (not!)

AppSupport.framework contains a class called CPRegularExpression, which performs regular expression matching. Here is an interface and some sample code.

However, I strongly recommend against using this class. Why? Because the backend of CPRegularExpression is in fact regexec. regexec is underpowered and not fast at all (for commonly encountered regexes, at least). Combined with the dynamic nature of Objective-C it means very slow (you can get an IMP to speed this up though). Instead, you should use the more powerful and faster PCRE (it's of course hard to use, but there're plenty of wrappers).

class-dump-z 0.1-11s released.

class-dump-z 0.1-11s is released. (When iKeyEx 3.0 is released I'll upgrade the versioning to 0.2. Let's hope I won't exhaust all alphabets first.)

Change log:
  • Won't crash on 3.1 binaries now, but external class info is absent. For example, for SpringBoard, SBAppWindow : UIView will be shown as SBAppWindow : XXUnknownSuperclass.
  • Private ivar detection. From whether the ivar symbol is exported we can guess if that ivar is @private/@package or @protected/@public. class-dump-z 0.1-11s now recognizes this piece of information.
  • -h super now works even the method includes non-id/void/SEL types.
  • Argument reordering now works on Linux (again).

Technical detail on why class-dump-z 0.1-11r crash with 3.1 SpringBoard, and why the external class info cannot be extracted in 0.1-11s.

If you use class-dump-z 0.1-11r on the 3.1 SpringBoard, you'll get an exception saying
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_S_construct NULL not valid
Abort trap

This is because a code similar to this is called:
std::string s = NULL;

Debugging reveals such an error occurs at MachO_File_ObjC::get_superclass_name. What happened is that, the definition of SBAppWindow class of 3.1 SpringBoard resolves to:
 class_t OBJC_CLASS_$_SBAppWindow = {
isa: OBJC_METACLASS_$_SBAppWindow,
superclass: NULL,
cache: NULL,
vtable: NULL,
data: (pointer to Data)

The superclass pointer is NULL. Which is fine, as such already happens before 3.1. But how can the system know the superclass if the pointer is NULL? Before 3.1, this location is in fact referenced by an entry in the relocation table. At runtime, the dynamic linker will replace data referenced in the relocation table by the real address.

The problem is the 3.1 SpringBoard has no relocation table! We can check:
Load command 6
cmdsize 80
ilocalsym 0
nlocalsym 1
iextdefsym 1
nextdefsym 1
iundefsym 2
nundefsym 1330
tocoff 0
ntoc 0
modtaboff 0
nmodtab 0
extrefsymoff 0
nextrefsyms 0
indirectsymoff 1138584
nindirectsyms 1943
extreloff 0
nextrel 0
locreloff 0
nlocrel 0

Because of this, class-dump-z 0.1-11r cannot resolve what that address will be, and returns NULL. The code afterward see this NULL unexpected and result in exception.

0.1-11s now checks for this failure, but it's just a bad workaround. Because every externally referred class, like NSObject, UIView, etc. will become XXUnknownSuperclass.

Now, if we don't know the superclass, how can the iPhoneOS be? There, I introduce you the Load Command 0x80000022:
Load command 4
cmd ?(0x80000022) Unknown load command
cmdsize 48
00000000 00000000 00106000 00006c6c 00000000 00000000 0010cc6c 00005324
00111f90 00000198

It is run without the 3.1 SDK. Because this is an unknown command, nm totally fails on the 3.1 SpringBoard.
nm: for architecture armv6 object: SpringBoard malformed object (unknown load command 4)

Googling suggests it appears only on 3.1+ and Snow Leopard+. That means the ABI will be changed on SL? And relocation table won't be used? Maybe. With a 3.1 SDK, we can see that the load command is in fact called LC_DYLD_INFO_ONLY. The content is

struct dyld_info_only_32 {
uint32_t rebase_off;
uint32_t rebase_size;
uint32_t bind_off; // 0x106000
uint32_t bind_size; // 0x6c6c
uint32_t weak_bind_off;
uint32_t weak_bind_size;
uint32_t lazy_bind_off; // 0x10cc6c
uint32_t lazy_bind_size; // 0x5324
uint32_t export_off; // 0x111f90
uint32_t export_size; // 0x198

This load command is not documented on Apple's site either, and google yields no useful result at all. But we can find the string _OBJC_CLASS_$_UIWindow at 0x106ea0 inside the so called "bind" table, so definitely something important can be found here.

Monday, August 3, 2009

Partial support for iKeyEx layout.plist on 3.0

The layout.plist to UIKBKeyboard conversion function is done. As promised, an executable that converts layout.plist to .keyboard files can be downloaded from here.

This program converts a layout.plist into 8 .keyboard files which can be loaded by UIKit natively. For example, if you want to install the Colemak keyboard,
  1. ssh into your device
  2. Download layout-plist-to-keyboards by typing:
  3. Change that file to an executable:
    chmod a+x layout-plist-to-keyboards
  4. Download the layout.plist for Colemak:
  5. Perform the conversion:
    ./layout-plist-to-keyboards layout.plist
  6. Check that there are 8 .keyboard files in the directory.
  7. Move all these keyboards to UIKit.framework (requires root permission):
    mv *.keyboard /System/Library/Frameworks/UIKit.framework/

Now all QWERTY keyboards are replaced by Colemak.

If you don't want to replace the QWERTY keyboard, you can rename them to something else. Say, you'll never use the Thai keyboard, then name them iPhone-Portrait-Thai.keyboard, etc. Now you can activate the Thai keyboard to use the custom one.

This solution is not a replacement of a full iKeyEx. For example, 5-Row QWERTY won't work completely because the custom-code injecting part is not done yet (but Punctuation Mod should work). hClipboard won't work at all since it's no a layout.plist. Other caveats are listed here. Please don't host Cydia packages publicly that uses this solution, because every keyboard will become mutually exclusive now :) Only for personal use.

Saturday, August 1, 2009

class-dump-z 0.1-11r released, supports Windows

A minor revision of class-dump-z was released. The new version:
  • Adds a Windows x86 version.
  • Arguments can be placed arbitrarily (e.g. class-dump-z UIKit -a -A now works).
Updated on Aug 2:
  • If you have installed the iPhone SDK, the -y switch is no longer necessary when using -h super.
  • Also, using -h super won't crash now.

GriP 0.1-11r Released to fix Crashing with Exchange mail accounts

As titled. If you are experience crashes when new mails arrive to an Exchange mail account, please upgrade to GriP 0.1-11r. Detail can be found in issue 242.