theos: пишем твик для iOS SpringBoard

от автора

Доброго скороновогоднего вечера уважаемым хабралюдям!

Сегодня я расскажу о создании твика для iOS SpringBoard с помощью theos. Зачем? В качестве интересного рисёрча и тренировки. В конце туториала мы получим примерно такую штуку прямо на экране блокрировки нашего i-девайса:


Создание проекта и настройка theos

Начинаем: создаём пустую папку, в неё кидаем теос (я кинул в виде гитового сабмодуля).

Далее, создаём новый проект с помощью NIC:

iHabrTweak git:(master) theos/bin/nic.pl  NIC 2.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 Project Name (required): iHabrTweak Package Name [com.yourcompany.ihabrtweak]: com.silvansky.ihabr Author/Maintainer Name [Valentine Silvansky]: silvansky [iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]:  Instantiating iphone/tweak in ihabrtweak/... Done. 

Теперь у нас есть папка ihabrtweak, в которой и лежат нужные нам файлики.

iHabrTweak git:(master) ✗ cd ihabrtweak  ihabrtweak git:(master) ✗ ls Makefile Tweak.xm control iHabrTweak.plist theos 

Теперь запускаем make и видим ошибки: не всё так просто! Наша система не до конца готова к испытанию на theos.

Что ж, надо вводить настройки, необходимые для нормальной сборки:

export ARCHS=armv7 export TARGET=iphone:latest:4.3 export THEOS="`pwd`/theos" export SDKVERSION=6.0 export THEOS_DEVICE_IP=192.168.2.2 

ARCHS нам указывает, что собирать будем только для armv7, а на armv6 забьём. TARGET нам указывает, что собирать будем для iOS с использованием последнего (в системе) SDK и с совместимостью с версии 4.3. Остальные три самоочевидны.

ihabrtweak git:(master) ✗ make Making all for tweak iHabrTweak...  Preprocessing Tweak.xm...  Compiling Tweak.xm...  Linking tweak iHabrTweak...  Stripping iHabrTweak...  Signing iHabrTweak... ihabrtweak git:(master) ✗ ls .theos/obj  Tweak.xm.o iHabrTweak.dylib 

Теперь у нас есть наша замечательная динамическая библиотека, которая пока совсем ничего не умеет делать! Зато мы можем установить наш твик на девайс:

ihabrtweak git:(master) ✗ make package Making all for tweak iHabrTweak... make[2]: Nothing to be done for `internal-library-compile'. Making stage for tweak iHabrTweak... dpkg-deb: building package `com.silvansky.ihabr' in `./com.silvansky.ihabr_0.0.1-1_iphoneos-arm.deb'. ihabrtweak git:(master) ✗ make package install Making all for tweak iHabrTweak... make[2]: Nothing to be done for `internal-library-compile'. Making stage for tweak iHabrTweak... dpkg-deb: building package `com.silvansky.ihabr' in `./com.silvansky.ihabr_0.0.1-2_iphoneos-arm.deb'. install.copyFile "./com.silvansky.ihabr_0.0.1-2_iphoneos-arm.deb" "com.silvansky.ihabr_0.0.1-2_iphoneos-arm.deb" root@192.168.2.2's password:  com.silvansky.ihabr_0.0.1-2_iphoneos-arm.deb 100% 1454 1.4KB/s 00:00     install.exec "dpkg -i com.silvansky.ihabr_0.0.1-2_iphoneos-arm.deb" root@192.168.2.2's password:  Selecting previously deselected package com.silvansky.ihabr. (Reading database ... 2516 files and directories currently installed.) Unpacking com.silvansky.ihabr (from com.silvansky.ihabr_0.0.1-2_iphoneos-arm.deb) ... Setting up com.silvansky.ihabr (0.0.1-2) ... install.exec "timeout 10s sbreload || ( ( respring || killall -9 SpringBoard ) && launchctl load /System/Library/LaunchDaemons/com.apple.SpringBoard.plist )" root@192.168.2.2's password:  launchctl unload SpringBoard.plist waiting for kill(29) != 0... 

Собственно, твик готов! Ставится, но ничего не делает. Будем это править. Начнём с теории theos-а и его твиков.
Как вы уже заметили, в проекте у нас есть файл Tweak.xm, являющийся нашим главным исходником.

На данный момент в нём всё закомментировано, а сам комментарий является частичной документацией. Собственно, этот файл является шаблоном для генерации конечного .mm файла. Рассмотрим некоторые полезные макросы этого шаблона:

%hook и %end

Основа твиков в theos — хуки. Они основаны на богатейшем рантайме языка Objective-C, позволяющем подмену методов у произвольного класса. Собственно, используется это так:

%hook SomeClass  -(void)someMethod {      // some code goes here }  %end 

Здесь Мы внедряем (подменяем) метод «someMethod» у класса «SomeClass». К примеру, мы можем внедрять наш код в SpringBoard, например, можем добавлять свои вьюшки на экран блокировки.

%orig и %new

Что ж, метод мы переопределили, ну а как вызвать оригинальный-то? Да тоже очень просто! Для этого есть макрос %orig. Будучи вызванным без параметров, этот макрос перенаправляет функции-оригиналу те же параметры, что и пришли в наш хук. Но можно и передать любые свои:

%hook SomeClass  - (id)initWithFrame:(CGRect)frame {     id result = %orig;     // some custom code     return result; }  - (id)initWithName:(NSString *)name {     id result = %orig(@"customName");     // some custom code     return result; }  %end 

Если простые определения методов внутри хуков переопределяет уже имеющиеся, то для добавления новых методов можно использовать макрос %new. По сути, это разделитель между методами, которые мы подменяем, и методами, которые мы добавляем. ВСЕ методы, идущие после %new, будут именно добавлены. Пример:

%hook SomeClass  - (void)someOldMethod {     // some code here }  %new  - (void)someNewMethod {     // some more code here }  %end 

Но с таким подходом мы не сможем вызвать наш новый метод из переопределённого: theos трактует ворнинги как ошибки и не даст собрать проект. Ведь мы наш метод не объявили! Но это поправимо, просто добавим вот это в наш файлик:

@interface SomeClass(NewMethods) - (void)someNewMethod; @end; 

%log

Макрос %log позволяет записать в системный лог факт вызова функции. Обычно используется для отладки.

Другие макросы можно посмотреть здесь.

Пишем что-то полезное

В комплекте к theos-у мы получаем хедеры системных фреймворков. В нашем проекте они лежат в theos/include. Если же не лежат, не забываем сделать так:

cd theos git submodule init git submodule update 

Там находим папку SpringBoard, а в ней — кучу хедеров. Что ж, пройдёмся по именам классов. Приметим интересный класс SBAwayView, который как раз и является основной вьюшкой экрана блокировки. Что ж, будем ставить хуки именно в него. Для начала надо бы поймать момент его создания:

#import <SpringBoard/SBAwayView.h> #import <UIKit/UIKit.h>  %hook SBAwayView  -(id)initWithFrame:(CGRect)frame { 	id result = %orig; 	if (result) 	{ 		// here goes the code... 	} 	return result; }  %end 

Можем поставить %log и убедиться после сборки-установки, что этот метод действительно вызывается. Теперь мы можем добавлять новые вьюшки! Только куда? Давайте будем их добавлять на фоновую картинку. Находим ivar UIImageView *_backgroundView у класса SBSlidingAlertDisplay, от которого наследуется SBAwayView, там же находим метод -(CGRect)middleFrame;. Но как нам получить значение ivar-а? Погуглим. Найдём функцию MSHookIvar, которая всё и сделает:

#import <SpringBoard/SBAwayView.h> #import <UIKit/UIKit.h> #import <substrate.h>  %hook SBAwayView  -(id)initWithFrame:(CGRect)frame { 	id result = %orig; 	if (result) 	{ 		CGRect labelRect = [self middleFrame]; 		labelRect.origin.y = labelRect.origin.y + 20.f; 		labelRect.size.height = 50.f; 		UILabel *habrLabel = [[[UILabel alloc] initWithFrame:labelRect] autorelease]; 		habrLabel.text = @"Hello, Habr!"; 		habrLabel.textColor = [UIColor colorWithRed:155.f/255.f green:182.f/255.f blue:206.f/255.f alpha:1.f]; 		habrLabel.opaque = NO;  		habrLabel.textAlignment = UITextAlignmentCenter; 		habrLabel.font = [UIFont boldSystemFontOfSize:36]; 		habrLabel.backgroundColor = [UIColor clearColor]; 		UIImageView *backgroundView = MSHookIvar<UIImageView *>(self, "_backgroundView"); 		[backgroundView addSubview:habrLabel]; 	} 	return result; }  %end 

Запускаем и наслаждаемся зрелищем!

Теперь усложним задачу. Будем загружать картинку! В теории всё просто: вместо UILabel создаём UIImageView. А откуда картинку брать?

Картинку надо бы положить в бандл SpringBoard.app, а лучше, если картинка туда сама скопируется во время установки пакета. Для этого мы реорганизуем структуру проекта: создадим папку Layout, в ней — папку DEBIAN, куда переместим уже имеющийся файл control, рядом с папкой DEBIAN сделаем System/Library/CoreServices/SpringBoard.app, куда и поместим нашу картинку:

SpringBoard.app git:(master) pwd /Users/silvansky/Projects/iHabrTweak/ihabrtweak/Layout/System/Library/CoreServices/SpringBoard.app SpringBoard.app git:(master) ls habr_logo_hat.png 

Теперь можно и написать финальный новогодний код:

#import <SpringBoard/SBAwayView.h> #import <UIKit/UIKit.h> #import <substrate.h>  #define IMG_WIDTH   150.f #define IMG_HEIGHT  186.f  %hook SBAwayView  -(id)initWithFrame:(CGRect)frame { 	id result = %orig; 	if (result) 	{ 		CGRect imageRect = [self middleFrame]; 		imageRect.origin.y = imageRect.origin.y + 20.f; 		imageRect.origin.x = (imageRect.size.width - IMG_WIDTH) / 2.f; 		imageRect.size.width = IMG_WIDTH; 		imageRect.size.height = IMG_HEIGHT; 		UIImageView *habrLogoView = [[[UIImageView alloc] initWithFrame:imageRect] autorelease]; 		habrLogoView.image = [UIImage imageNamed:@"habr_logo_hat"]; 		UIImageView *backgroundView = MSHookIvar<UIImageView *>(self, "_backgroundView"); 		[backgroundView addSubview:habrLogoView]; 	} 	return result; }  %end 

И — любуемся на получившуюся красоту:

Полный исходник, как обычно, прошу брать на гитхабе.

Всех с наступающим! Радости и удач в следующем году! =)

ссылка на оригинал статьи http://habrahabr.ru/post/164341/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *