iKy1e | iOS Developer

Jailbroken Development : Making a Basic Tweak

Now, if you have gone through my previous post you should have Theos setup and know the basic’s of how to hook into iOS.

Making tweaks is different in several respects to writing AppStore apps. When writing AppStore apps, if you manage to use any private API’s, and get them past apple, then you know they could break with any iOS update. With tweaks that is most of your code base (could break in any update).


So this time we’re going to try and create a basic tweak that will display how many unread emails the user has on the lock screen that when tapped will unlock the device and open the mail app.

This will show you how to: lock/unlock the device, get an SBApplication, get a SBIcon for an app and get a UIImage of the apps icon (as well as displaying things on the screen).

Well need:

The first thing we need to do, after we download or class-dump a set of headers, is find which class controls the lock screen. The controller class for the lockscreen is SBAwayController (which like most really important classes in SpringBoard is a singleton “+sharedInstance”).

You’ll need to create a new project with NIC (one of the tools that comes with Theos).

$THEOS/bin/nic.pl
NIC 1.0 - New Instance Creator
------------------------------
  [1.] iphone/application
  [2.] iphone/library
  [3.] iphone/preference_bundle
  [4.] iphone/tool
  [5.] iphone/tweak
Choose a Template (required): 5
#...... (fill in name, your name etc...)

Now open up the Tweak.xm file and start coding! I’m not that good at explaining things in little chunks of code, then explanation, then code, etc… 

So instead I’ve written lots of comments through out the code explaining it as you go through it.

/************* This is equivalent to declaring instance variables in the .h file ***************/
// So we can see if there's an app open
static NSMutableArray *displayStacks;
// To add everything to on screen
static UIWindow *contentWindow = nil;

// List of the display stacks
#define SBWPreActivateDisplayStack        [displayStacks objectAtIndex:0]
#define SBWActiveDisplayStack             [displayStacks objectAtIndex:1]
#define SBWSuspendingDisplayStack         [displayStacks objectAtIndex:2]
#define SBWSuspendedEventOnlyDisplayStack [displayStacks objectAtIndex:3]

// Hook into the lockscreen's controller
%hook SBAwayController

-(void)lock{
    %orig;  // Make sure it actually locks


    // Setup our interface,
    // we don't want to really on the lockscreen existing well the screen is off,
    // so well create our own UIWindow to house it.
    if (!contentWindow) {
        // Create our window
        contentWindow = [[UIWindow alloc] initWithFrame:CGRectMake(0, [UIScreen mainScreen].bounds.size.height*0.4, [UIScreen mainScreen].bounds.size.width, 70)];
        contentWindow.backgroundColor = [UIColor colorWithRed:0.000 green:0.000 blue:0.000 alpha:0.8];
        contentWindow.hidden = NO;
        // Make sure it's high enough to be seen on the lock screen
        contentWindow.windowLevel = UIWindowLevelStatusBar;
        contentWindow.layer.borderColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.5].CGColor;
        contentWindow.layer.borderWidth = 1.0f;
        contentWindow.layer.cornerRadius = 8;

        // Add a gesture recognizer (could be a UIButton but this is simplier).
        UITapGestureRecognizer *singleTap = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_openEmail:)] autorelease];
        [contentWindow addGestureRecognizer:singleTap];


        // Get the SBApplication for the mail app
        SBApplication *app = [[%c(SBApplicationController) sharedInstance] applicationWithDisplayIdentifier:@"com.apple.mobilemail"];
        // Get the SBApplicationIcon for the mail app
        SBApplicationIcon *appIcon = [[objc_getClass("SBIconModel") sharedInstance] applicationIconForDisplayIdentifier:[app displayIdentifier]];
        // Get the UIIcon from the app icon
        UIImage *icon = [appIcon getIconImage:2];
        // Create a UIImageView from that image
        UIImageView *iconView = [[[UIImageView alloc] initWithImage:icon] autorelease];
        iconView.center = CGPointMake(contentWindow.center.y-contentWindow.frame.origin.y, contentWindow.center.y-contentWindow.frame.origin.y);
        [contentWindow addSubview:iconView]; // Add it to the window

        CGRect labelRect;
        labelRect.origin.y = iconView.frame.origin.y;
        labelRect.origin.x = iconView.frame.origin.x+iconView.frame.size.width;
        labelRect.size.height = contentWindow.frame.size.height-(iconView.frame.origin.y*2);
        labelRect.size.width = (contentWindow.frame.size.width-(iconView.frame.origin.x+iconView.frame.size.width))-10;

        // Create the label
        UILabel *unreadEmailLabel = [[[UILabel alloc] initWithFrame:labelRect] autorelease];
        // Setup the label
        unreadEmailLabel.font = [UIFont boldSystemFontOfSize:16];
        unreadEmailLabel.backgroundColor = [UIColor clearColor];
        unreadEmailLabel.textColor = [UIColor whiteColor];
        unreadEmailLabel.textAlignment = UITextAlignmentRight;
        unreadEmailLabel.numberOfLines = 1;
        // If the icon has a badge number
        if ([appIcon badgeValue] != 0) {
            if ([appIcon badgeValue] == 1) {
                unreadEmailLabel.text = @"1 unread email";
            }
            else {
                unreadEmailLabel.text = [NSString stringWithFormat:@"%d unread emails", [appIcon badgeValue]];
            }
        }
        else {
            unreadEmailLabel.text = @"No unread emails";
        }
        // Add it to the window
        [contentWindow addSubview:unreadEmailLabel];
    }
}
-(void)_unlockWithSound:(BOOL)sound isAutoUnlock:(BOOL)unlock{
    %orig; // Call the original method first

    // Remove our window
    contentWindow.hidden = YES;
    [contentWindow release];
    contentWindow = nil;
}

%new
-(void)_openEmail:(UIGestureRecognizer*)gesture{
    if (gesture.state != UIGestureRecognizerStateEnded)
        return;

    // Unlock the device
    [self unlockWithSound:YES];

    // Get the SBApplication for the mail app
    SBApplication *APP = [[%c(SBApplicationController) sharedInstance] applicationWithDisplayIdentifier:@"com.apple.mobilemail"];


    // Active the Mail app
    if ([SBWActiveDisplayStack topApplication] != nil) {
        // An app is already open, so use the switcher animation, but first check if this is the same app.
        if (![[[SBWActiveDisplayStack topApplication] bundleIdentifier] isEqualToString:@"com.apple.mobilemail"]) {
            [(SBUIController*)[objc_getClass("SBUIController") sharedInstance] activateApplicationFromSwitcher:APP];
        }
    }
    else {
        //Else we are on SpringBoard
        [(SBUIController*)[objc_getClass("SBUIController") sharedInstance] activateApplicationAnimated:APP];
    }
}

%end



// This is needed to get the current app
// http://code.google.com/p/iphone-tweaks/wiki/DevelopmentNotes

// An easier (for us) way would be to use libDisplayStack (which can also activate apps)
// https://github.com/Zimm/libdisplaystack/blob/master/Tweak.xm
%hook SBDisplayStack
- (id)init{
	if ((self = %orig))
		[displayStacks addObject:self];
	return self;
}
- (void)dealloc {
    [displayStacks removeObject:self];
    %orig;
}
%end

Notes:

Jailbroken development is very much like normal iOS development. Though as a general rule you’ll need a set of headers for any iOS version you plan to support & preferably a tester running that device to test it actually works.

Method names, always check they still exist in new iOS versions. You can see which look more likely to be changed though, I don’t know if it’s true (haven’t been doing Jailbroken development for long enough, but), I usually try avoiding methods with long lists of arguments or “_” at the start of it’s name.

Also I normally rename any .xm file too .mm and then use “./Make.sh package install” rather then “make package install” to compile it. Make.sh is a little bash script that copies the Tweak.mm file to Tweak.xm and then deletes it after it’s built so it can be compiled and I still get to use Xcode to write my code (though without code auto completion).

#!/bin/bash

ARGS=$*

# Rename the files
cp ./Tweak.mm ./Tweak.xm
echo "Copied Tweak"

# Build
echo ""
echo "||---- Building..."
echo ""
make $ARGS
echo ""
echo "||---- Built!"
echo ""


# Rename the files
rm ./Tweak.xm
echo "Deleted Tweak.xm"


exit 0

  1. stereosmind reblogged this from iky1e
  2. iky1e posted this
To Tumblr, Love PixelUnion