У меня периодические возникают разные потребности решения мелких насущных задач в Mac OS X. Для этих целей я обычно делаю небольшие программы, которые «закрывают» потребность частным образом. Но иногда хочется, чтоб программа была универсальной, и ей могли воспользоваться другие люди при необходимости (например «Переlator»). Так получилось и в этот раз…
Я люблю, когда Dock отображается всегда на экране. Но при запуске Симулятора iOS постоянно приходилось включать автоматические скрытие, чтобы симулятор полностью умещался на экране. Появилась задача — автоматизировать этот процесс. За пару дней набросал универсальную программу, с помощью которой можно задать AppleScript на определённое действие любой программы: «Программа запущена», «Программа завершена», «Программа активирована», «Программа деактивирована» и пр.
![](https://habrastorage.org/r/w1560/storage/habraeffect/67/9e/679e04b2d63bfbe361bafd48ebb3e73c.png)
Этот топик я разделю на две части. Одна для пользователей, которые просто хотят пользоваться программой (или ознакомиться). Вторая для начинающих разработчиков – я опишу схему работы программы и предоставлю исходный код. Несмотря на кажущуюся простоту самой программы, исходный код покрывает множество нюансов из различных тематик, которые могут сэкономить существенное количество времени в будущем.
Загрузить программу можно по ссылке (только для 10.6+) (в 18.51 я обновил программу и исходные коды благодаря баг-репорту. Исправил маленький баг, из-за которого помощник не получал сообщения после удаления программы из списка. Издержки кустарного тестирования...).
Программа очень проста в использовании (если имеются навыки работы с AppleScript). Это не отдельное приложение, а панель для «Системных настроек». Два раза щёлкаете по нему мышкой и получаете новую панель «Выполни меня» в «Системных настроек». В левой части находится список программ. А в правой скрипты, заданные для выбранной программы.
При нажатии на кнопку "+" откроется список запущенных программ (имеющих идентификатор – Bundle identifier), из которого мы можем выбрать нужную программу для добавления (если требуемая программа не запущена, то её нужно предварительно запустить).
![](https://habrastorage.org/r/w1560/storage/habraeffect/58/02/5802b4c322ba97fa02aeb603b8e4a941.png)
Всё, что осталось, это задать скрипты на те действия, которые требуются. Например, включать режим автоматического скрытия Dock:
Или запустить какую-то программу:
Или выполнить какой-нибудь консольный скрипт:
Возможности фактически ничем не ограничены — AppleScript позволяет сделать очень многое.
На самом деле программа состоит из двух частей. Панель системных настроек. И специальная скрытая программа помощник – «Выполни меня (помощник)», которая автоматически прописывается в автозагрузку. Она настолько мизерная, что фактически не потребляет системных ресурсов, но именно она отвечает за выполнение скриптов прописанных в панели настроек.
Чтобы удалить программу, просто щёлкните правой кнопкой мыши (или Ctrl + щелчок мышью / на тачпаде) на названии панели «Выполни меня» и выберите «Удалить панель».
![](https://habrastorage.org/r/w1560/storage/habraeffect/c4/4e/c44e2cb3d429c89e055e4a50b82bbad9.png)
Вот ссылка на исходный код проекта.
Вот вариант на github.
Программа состоит из двух частей — панели настроек и программы помощника. Для каждой из них я сделал отдельный проект (в данном случае мне так было удобнее). При необходимости два проекта можно объединить в один с двумя таргетами.
Создание панели для «Системных настроек» мало чем отличается от создания обычной программы. В Xcode уже есть шаблон для этого модуля System Plug-in > Preference Pane. В котором уже добавлен класс на базе NSPreferencePane и интерфейсный (xib) файл к нему. Каждый решает сам, как тестировать этот модуль. Например, можно делать всё в виде обычной программы и лишь на финальном этапе переносить всё в Preference Pane.
Важно понимать, что это не самостоятельная программа, а модуль для «Системных настроек». Это означает, что все методы, которые привязаны к основному bundle не будут корректно выполнены (т.к. основной bundle — это «Системные настройки»).
Мы не можем пользоваться макросом следующего вида:
Нужно пользоваться:
Или мы не можем пользоваться методом NSImage:
Нужно пользоваться, например:
И т.д.
У объекта класса NSPreferencePane есть метод:
Его и нужно использоваться.
В проекте используются две кнопки "+" и "-" для редактирования списка программ.
Инициализация кнопки удаления банальная и простая:
А вот кнопка добавления уже сложнее. Для неё необходимо ввести два подкласса NSPopUpButton и NSPopUpButtonCell. Формально для кнопки с выпадающим списком служит класс NSPopUpButton. Но в голом виде он нам не подходит. Во-первых, не позволяет задать статическую картинку для кнопки (точнее позволяет, но реальный размер кнопки не соответствует реальному размеру статической картинки, что в нашем случае неприемлемо, т.к. у нас две кнопки расположены рядом) — это мы обходим с помощью подкласса NSPopUpButtonCell. Во-вторых, NSPopUpButton не позволяет динамически изменить содержимое меню в момент её нажатия (у нас меню должно формироваться именно в момент нажатия на кнопку) – это мы обходим с помощью подкласса NSPopUpButton.
Чтобы кнопка имела размер один в один с заданной картинкой, создаём класс PopUpCell:
Что означает весь этот код? Всё просто — мы создаём объект класса NSButtonCell и отрисовываем его вместо NSPopUpButtonCell. Т.е. программа рисует NSButtonCell (размер которой может соответствовать один в один с заданной картинкой) вместо NSPopUpButtonCell, но функционально это NSPopUpButton.
Теперь меню… С динамическим меню поступим следующим образом – добавим делегата в NSPopUpButton, который будет выдавать нам меню в тот момент, когда нажата левая кнопка мыши на кнопке.
Создаём подкласс NSPopUpButton:
Таким образом мы объявили протокол делегата, который имеет лишь один метод:
Реализация:
При нажатии мышкой, перед тем, как событие начнёт обрабатываться объектом класса NSPopUpButton, мы устанавливаем меню делегата этому объекту.
Теперь можно добавлять нашу кнопку "+" на нашу панель:
Обратите внимание на «addButton.delegate = self». Мы назначили наш основной класс делегатом к addButton. Для этого мы дополнительно реализуем метод:
в нашем основном классе.
Как получить список запущенных программ для меню?
У объекта класса NSWorkspace есть метод:
который даст нам массив всех запущенных программ (с полной информацией по ним: название, идентификатор, иконка и пр.).
Из этого объекта apps и будет формироваться (см. исходный код) меню для кнопки добавления.
Наш объект NSTableView отображает ячейки с картинкой, заголовком и подзаголовком. Стандартной ячейки такого типа нет, её необходимо сделать самостоятельно.
Объявляем класс AppCell:
Самое главное — это переназначить метод отрисовки ячейки:
Класс ячейки готов, осталось только добавить его в наш объект NSTableView. Существует два способа.
1). Если количество объектов в таблице небольшое, то можно воспользоваться методом делагата (которым является наш основной класс NSPreferencePane) к NSTableView:
2). Если объектов много, то можно задать универсальную ячейку для всего столбца:
и проставлять необходимые значения в методе делегата к NSTableView:
Данные (файл со списком программ и скриптами и иконки) сохраняются в соответствующей папке Application Support (пользовательская Библиотека). Получить путь к этой папке можно (и нужно) следующим образом:
Иконки мы будем хранить в формате PNG. Есть несколько способов получение PNG данных из NSImage, я приведу один из них (самый универсальный), который используется в программе. Для класса NSImage мы введём новый метод. Вот как выглядит его реализация:
Сохранение данных происходит автоматически, когда изменяется ячейка в таблица. Или через 3 секунды после того, как пользователь изменил какой-либо скрипт. Чтобы это сделать, мы воспользуется методом делегата к NSTextView и отложенным выполнением:
Если пользователь изменил какие-либо данные, то через 3 секунды произойдёт автоматическое сохранение. Если в этот период пользователь продолжает редактировать данные, то предыдущий запрос на сохранение отменяется и создаётся новый. Подобный механизм удобно использовать, например, при поиске (чтобы поиск срабатывал лишь через некоторое время после окончания редактирования).
После сохранения извещаем помощника, что данные изменены:
Наша программа умеет самостоятельно запускать помощника (если он не запущен) и умеет добавлять его в автозагрузку. Запуск реализовать очень просто (наш помощник находится внутри bundle):
За управление элементами автозапуска программ (Login Items) отвечает LaunchServices framework. Он написан на Си. Мы сделаем удобную Objective-C обёртку для наших задач. Объявим класс LoginItems:
Это методы класса. Они не привязаны к какому-либо объекту, их можно вызывать просто [LoginItems addApplication:path].
Полную реализацию этих методов можно посмотреть в исходных кодах. Вот как, для примера, реализован метод добавления:
Вот в принципе и вся наша панель.
Теперь помощник… он очень просто. Т.к. помощник не должен быть виден пользователю, в файле описания программы выставляем ключ LSBackgroundOnly в YES. Это означает, что программа не будет отображаться в доке, в окне Force Quit, не будет отображать меню и пр.
Самая главная часть инициализация помощника – это получения сообщений от панели, что настройки изменены (для их перезагрузки), и получение сообщений NSWorkspace о состоянии программ:
И основной метод, который выполняет AppleScript, если программа и её действие соответствует настройкам:
Вот такой небольшой проект, а интересного внутри очень много. Надеюсь, что кому-то этот материал окажется полезен. Я же свою пользу уже получил – Dock автоматически скрывается при активации Симулятора iOS :).
Я люблю, когда Dock отображается всегда на экране. Но при запуске Симулятора iOS постоянно приходилось включать автоматические скрытие, чтобы симулятор полностью умещался на экране. Появилась задача — автоматизировать этот процесс. За пару дней набросал универсальную программу, с помощью которой можно задать AppleScript на определённое действие любой программы: «Программа запущена», «Программа завершена», «Программа активирована», «Программа деактивирована» и пр.
![](https://habrastorage.org/storage/habraeffect/67/9e/679e04b2d63bfbe361bafd48ebb3e73c.png)
Этот топик я разделю на две части. Одна для пользователей, которые просто хотят пользоваться программой (или ознакомиться). Вторая для начинающих разработчиков – я опишу схему работы программы и предоставлю исходный код. Несмотря на кажущуюся простоту самой программы, исходный код покрывает множество нюансов из различных тематик, которые могут сэкономить существенное количество времени в будущем.
Загрузить программу можно по ссылке (только для 10.6+) (в 18.51 я обновил программу и исходные коды благодаря баг-репорту. Исправил маленький баг, из-за которого помощник не получал сообщения после удаления программы из списка. Издержки кустарного тестирования...).
Программа очень проста в использовании (если имеются навыки работы с AppleScript). Это не отдельное приложение, а панель для «Системных настроек». Два раза щёлкаете по нему мышкой и получаете новую панель «Выполни меня» в «Системных настроек». В левой части находится список программ. А в правой скрипты, заданные для выбранной программы.
При нажатии на кнопку "+" откроется список запущенных программ (имеющих идентификатор – Bundle identifier), из которого мы можем выбрать нужную программу для добавления (если требуемая программа не запущена, то её нужно предварительно запустить).
![](https://habrastorage.org/storage/habraeffect/58/02/5802b4c322ba97fa02aeb603b8e4a941.png)
Всё, что осталось, это задать скрипты на те действия, которые требуются. Например, включать режим автоматического скрытия Dock:
tell application "System Events" to set the autohide of the dock preferences to true
Или запустить какую-то программу:
tell application "iTunes" to activate
Или выполнить какой-нибудь консольный скрипт:
do shell script "…"
Возможности фактически ничем не ограничены — AppleScript позволяет сделать очень многое.
На самом деле программа состоит из двух частей. Панель системных настроек. И специальная скрытая программа помощник – «Выполни меня (помощник)», которая автоматически прописывается в автозагрузку. Она настолько мизерная, что фактически не потребляет системных ресурсов, но именно она отвечает за выполнение скриптов прописанных в панели настроек.
Чтобы удалить программу, просто щёлкните правой кнопкой мыши (или Ctrl + щелчок мышью / на тачпаде) на названии панели «Выполни меня» и выберите «Удалить панель».
![](https://habrastorage.org/storage/habraeffect/c4/4e/c44e2cb3d429c89e055e4a50b82bbad9.png)
ДЛЯ НАЧИНАЮЩИХ РАЗРАБОТЧИКОВ
Вот ссылка на исходный код проекта.
Вот вариант на github.
Программа состоит из двух частей — панели настроек и программы помощника. Для каждой из них я сделал отдельный проект (в данном случае мне так было удобнее). При необходимости два проекта можно объединить в один с двумя таргетами.
Создание панели для «Системных настроек» мало чем отличается от создания обычной программы. В Xcode уже есть шаблон для этого модуля System Plug-in > Preference Pane. В котором уже добавлен класс на базе NSPreferencePane и интерфейсный (xib) файл к нему. Каждый решает сам, как тестировать этот модуль. Например, можно делать всё в виде обычной программы и лишь на финальном этапе переносить всё в Preference Pane.
Важно понимать, что это не самостоятельная программа, а модуль для «Системных настроек». Это означает, что все методы, которые привязаны к основному bundle не будут корректно выполнены (т.к. основной bundle — это «Системные настройки»).
Мы не можем пользоваться макросом следующего вида:
NSLocalizedString(NSString *key, NSString *comment)
Нужно пользоваться:
NSLocalizedStringFromTableInBundle(NSString *key, NSString *tableName, NSBundle *bundle, NSString *comment)
Или мы не можем пользоваться методом NSImage:
+ (id)imageNamed:(NSString *)name
Нужно пользоваться, например:
- (id)initWithContentsOfFile:(NSString *)filename
И т.д.
У объекта класса NSPreferencePane есть метод:
- (NSBundle *)bundle
Его и нужно использоваться.
В проекте используются две кнопки "+" и "-" для редактирования списка программ.
Инициализация кнопки удаления банальная и простая:
removeButton = [[NSButton alloc] initWithFrame:NSMakeRect(43, 19, 22, 22)];<br/>
[removeButton setButtonType:NSMomentaryChangeButton];<br/>
[removeButton setImage:buttonImage];<br/>
[removeButton setImagePosition:NSImageOnly];<br/>
[removeButton setBordered:NO];<br/>
[removeButton setTarget:self];<br/>
[removeButton setAction:@selector(removeButtonAction:)];<br/>
[[self mainView] addSubview:removeButton];
А вот кнопка добавления уже сложнее. Для неё необходимо ввести два подкласса NSPopUpButton и NSPopUpButtonCell. Формально для кнопки с выпадающим списком служит класс NSPopUpButton. Но в голом виде он нам не подходит. Во-первых, не позволяет задать статическую картинку для кнопки (точнее позволяет, но реальный размер кнопки не соответствует реальному размеру статической картинки, что в нашем случае неприемлемо, т.к. у нас две кнопки расположены рядом) — это мы обходим с помощью подкласса NSPopUpButtonCell. Во-вторых, NSPopUpButton не позволяет динамически изменить содержимое меню в момент её нажатия (у нас меню должно формироваться именно в момент нажатия на кнопку) – это мы обходим с помощью подкласса NSPopUpButton.
Чтобы кнопка имела размер один в один с заданной картинкой, создаём класс PopUpCell:
@interface PopUpCell : NSPopUpButtonCell<br/>
{<br/>
NSButtonCell *buttonCell;<br/>
}<br/>
<br/>
- (id)initWithimage:(NSImage *)image;<br/>
<br/>
@end<br/>
<br/>
…<br/>
<br/>
@implementation PopUpCell<br/>
<br/>
- (id)initWithimage:(NSImage *)image<br/>
{<br/>
self = [super initTextCell:@"" pullsDown:YES];<br/>
<br/>
buttonCell = [[NSButtonCell alloc] initImageCell:image];<br/>
[buttonCell setButtonType:NSPushOnPushOffButton];<br/>
[buttonCell setImagePosition:NSImageOnly];<br/>
[buttonCell setImageDimsWhenDisabled:YES];<br/>
[buttonCell setBordered:NO];<br/>
<br/>
return self;<br/>
}<br/>
<br/>
...<br/>
<br/>
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView<br/>
{<br/>
[buttonCell drawWithFrame:cellFrame inView:controlView];<br/>
}<br/>
<br/>
- (void)highlight:(BOOL)flag withFrame:(NSRect)cellFrame inView:(NSView *)controlView<br/>
{<br/>
[buttonCell highlight:flag withFrame:cellFrame inView:controlView];<br/>
}<br/>
<br/>
@end
Что означает весь этот код? Всё просто — мы создаём объект класса NSButtonCell и отрисовываем его вместо NSPopUpButtonCell. Т.е. программа рисует NSButtonCell (размер которой может соответствовать один в один с заданной картинкой) вместо NSPopUpButtonCell, но функционально это NSPopUpButton.
Теперь меню… С динамическим меню поступим следующим образом – добавим делегата в NSPopUpButton, который будет выдавать нам меню в тот момент, когда нажата левая кнопка мыши на кнопке.
Создаём подкласс NSPopUpButton:
@protocol PopUpDelegate <NSObject><br/>
@optional<br/>
- (NSMenu *)menuForPopUp;<br/>
@end<br/>
<br/>
<br/>
@interface PopUpButton : NSPopUpButton<br/>
{<br/>
id<PopUpDelegate> delegate; <br/>
}<br/>
<br/>
@property (assign) id delegate;<br/>
<br/>
@end<br/>
Таким образом мы объявили протокол делегата, который имеет лишь один метод:
- (NSMenu *)menuForPopUp<br/>
Реализация:
@implementation PopUpButton<br/>
<br/>
@synthesize delegate;<br/>
<br/>
- (void)mouseDown:(NSEvent *)event<br/>
{<br/>
if([delegate respondsToSelector:@selector(menuForPopUp)])<br/>
{<br/>
[self setMenu:[delegate menuForPopUp]];<br/>
}<br/>
<br/>
[super mouseDown:event];<br/>
}<br/>
<br/>
@end<br/>
При нажатии мышкой, перед тем, как событие начнёт обрабатываться объектом класса NSPopUpButton, мы устанавливаем меню делегата этому объекту.
Теперь можно добавлять нашу кнопку "+" на нашу панель:
addButton = [[PopUpButton alloc] initWithFrame:NSMakeRect(20, 19, 23, 22) pullsDown:YES];<br/>
addButton.delegate = self;<br/>
[addButton setCell:[[[PopUpCell alloc] initWithimage:buttonImage] autorelease]];<br/>
[addButton setMenu:[self menuForPopUp]];<br/>
[[self mainView] addSubview:addButton];
Обратите внимание на «addButton.delegate = self». Мы назначили наш основной класс делегатом к addButton. Для этого мы дополнительно реализуем метод:
- (NSMenu *)menuForPopUp<br/>
{<br/>
...<br/>
}
в нашем основном классе.
Как получить список запущенных программ для меню?
У объекта класса NSWorkspace есть метод:
- (NSArray *)runningApplications
который даст нам массив всех запущенных программ (с полной информацией по ним: название, идентификатор, иконка и пр.).
NSArray *apps = [[NSWorkspace sharedWorkspace] runningApplications];
Из этого объекта apps и будет формироваться (см. исходный код) меню для кнопки добавления.
Наш объект NSTableView отображает ячейки с картинкой, заголовком и подзаголовком. Стандартной ячейки такого типа нет, её необходимо сделать самостоятельно.
Объявляем класс AppCell:
@interface AppCell : NSTextFieldCell<br/>
{<br/>
NSImage *image;<br/>
NSString *title;<br/>
NSString *subtitle;<br/>
}<br/>
...<br/>
@end<br/>
Самое главное — это переназначить метод отрисовки ячейки:
- (void)drawInteriorWithFrame:(NSRect)inCellFrame inView:(NSView*)inView<br/>
{<br/>
//рисуем image<br/>
//рисуем title<br/>
//рисуем subtitle<br/>
…<br/>
}<br/>
Класс ячейки готов, осталось только добавить его в наш объект NSTableView. Существует два способа.
1). Если количество объектов в таблице небольшое, то можно воспользоваться методом делагата (которым является наш основной класс NSPreferencePane) к NSTableView:
- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row<br/>
{<br/>
AppCell* cell = [[[AppCell alloc] init] autorelease];<br/>
[cell setEditable:NO];<br/>
cell.title = [[tableDataSource objectAtIndex:row] objectForKey:@"name"];<br/>
cell.subtitle = [[tableDataSource objectAtIndex:row] objectForKey:@"ID"];<br/>
cell.image = [[tableDataSource objectAtIndex:row] objectForKey:@"icon"];<br/>
<br/>
return cell;<br/>
}
2). Если объектов много, то можно задать универсальную ячейку для всего столбца:
NSTableColumn* column = [[appTable tableColumns] objectAtIndex:0];
[column setDataCell:[[[AppCell alloc] init] autorelease]];
и проставлять необходимые значения в методе делегата к NSTableView:
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex<br/>
{<br/>
AppCell* cell = (AppCell *)aCell;<br/>
[cell setEditable:NO];<br/>
cell.title = [[tableDataSource objectAtIndex:row] objectForKey:@"name"];<br/>
cell.subtitle = [[tableDataSource objectAtIndex:row] objectForKey:@"ID"];<br/>
cell.image = [[tableDataSource objectAtIndex:row] objectForKey:@"icon"];<br/>
}
Данные (файл со списком программ и скриптами и иконки) сохраняются в соответствующей папке Application Support (пользовательская Библиотека). Получить путь к этой папке можно (и нужно) следующим образом:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); <br/>
NSString *appSupportPath = [paths objectAtIndex:0];<br/>
NSString *prefDir = [appSupportPath stringByAppendingPathComponent:@"info.yuriev.OnAppBehaviour"];
Иконки мы будем хранить в формате PNG. Есть несколько способов получение PNG данных из NSImage, я приведу один из них (самый универсальный), который используется в программе. Для класса NSImage мы введём новый метод. Вот как выглядит его реализация:
@implementation NSImage (PNGExport)<br/>
<br/>
- (NSData *)PNGData<br/>
{<br/>
[self lockFocus];<br/>
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0, 0, [self size].width, [self size].height)];<br/>
[self unlockFocus];<br/>
<br/>
NSData *PNGData = [rep representationUsingType:NSPNGFileType properties:nil];<br/>
[rep release];<br/>
<br/>
return PNGData;<br/>
}<br/>
<br/>
@end
Сохранение данных происходит автоматически, когда изменяется ячейка в таблица. Или через 3 секунды после того, как пользователь изменил какой-либо скрипт. Чтобы это сделать, мы воспользуется методом делегата к NSTextView и отложенным выполнением:
- (void)textDidChange:(NSNotification *)aNotification<br/>
{<br/>
[NSObject cancelPreviousPerformRequestsWithTarget:self];<br/>
[self performSelector:@selector(saveCurrentData) withObject:nil afterDelay:3.0];<br/>
<br/>
saved = NO;<br/>
}
Если пользователь изменил какие-либо данные, то через 3 секунды произойдёт автоматическое сохранение. Если в этот период пользователь продолжает редактировать данные, то предыдущий запрос на сохранение отменяется и создаётся новый. Подобный механизм удобно использовать, например, при поиске (чтобы поиск срабатывал лишь через некоторое время после окончания редактирования).
После сохранения извещаем помощника, что данные изменены:
NSString *observedObject = @"info.yuriev.OnAppBehaviourHelper";<br/>
NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter];<br/>
[center postNotificationName:@"OABReloadScripts" object:observedObject userInfo:nil deliverImmediately:YES];
Наша программа умеет самостоятельно запускать помощника (если он не запущен) и умеет добавлять его в автозагрузку. Запуск реализовать очень просто (наш помощник находится внутри bundle):
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:[NSURL fileURLWithPath:helperPath] options:NSWorkspaceLaunchDefault configuration:nil error:NULL];
За управление элементами автозапуска программ (Login Items) отвечает LaunchServices framework. Он написан на Си. Мы сделаем удобную Objective-C обёртку для наших задач. Объявим класс LoginItems:
@interface LoginItems : NSObject<br/>
{<br/>
<br/>
}<br/>
<br/>
+ (void)addApplication:(NSString *)path;<br/>
+ (void)removeApplication:(NSString *)path;<br/>
+ (BOOL)findApplication:(NSString *)path;<br/>
<br/>
@end
Это методы класса. Они не привязаны к какому-либо объекту, их можно вызывать просто [LoginItems addApplication:path].
Полную реализацию этих методов можно посмотреть в исходных кодах. Вот как, для примера, реализован метод добавления:
+ (void)addApplication:(NSString *)path<br/>
{<br/>
LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);<br/>
<br/>
if (loginItemsRef)<br/>
{<br/>
LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, (CFURLRef)[NSURL fileURLWithPath:path], NULL, NULL);<br/>
if (itemRef) CFRelease(itemRef);<br/>
CFRelease(loginItemsRef); <br/>
} <br/>
}
Вот в принципе и вся наша панель.
Теперь помощник… он очень просто. Т.к. помощник не должен быть виден пользователю, в файле описания программы выставляем ключ LSBackgroundOnly в YES. Это означает, что программа не будет отображаться в доке, в окне Force Quit, не будет отображать меню и пр.
Самая главная часть инициализация помощника – это получения сообщений от панели, что настройки изменены (для их перезагрузки), и получение сообщений NSWorkspace о состоянии программ:
NSNotificationCenter *notificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter];<br/>
<br/>
[notificationCenter addObserver:self selector:@selector(didLaunchApplication:) name:NSWorkspaceDidLaunchApplicationNotification object:nil];<br/>
[notificationCenter addObserver:self selector:@selector(didTerminateApplication:) name:NSWorkspaceDidTerminateApplicationNotification object:nil];<br/>
[notificationCenter addObserver:self selector:@selector(didHideApplication:) name:NSWorkspaceDidHideApplicationNotification object:nil];<br/>
[notificationCenter addObserver:self selector:@selector(didUnhideApplication:) name:NSWorkspaceDidUnhideApplicationNotification object:nil];<br/>
[notificationCenter addObserver:self selector:@selector(didActivateApplication:) name:NSWorkspaceDidActivateApplicationNotification object:nil];<br/>
[notificationCenter addObserver:self selector:@selector(didDeactivateApplication:) name:NSWorkspaceDidDeactivateApplicationNotification object:nil];<br/>
<br/>
NSString *observedObject = @"info.yuriev.OnAppBehaviourHelper";<br/>
NSDistributedNotificationCenter *dNotificationCenter = [NSDistributedNotificationCenter defaultCenter];<br/>
<br/>
[dNotificationCenter addObserver: self selector: @selector(loadPreferences:) name:@"OABReloadScripts" object:observedObject];
И основной метод, который выполняет AppleScript, если программа и её действие соответствует настройкам:
- (void)preformScriptOnApp:(NSRunningApplication *)app forKey:(NSString *)key<br/>
{<br/>
if ((!app) || (![app bundleIdentifier])) return;<br/>
<br/>
NSString *bundleID = [app bundleIdentifier];<br/>
<br/>
for (int i = 0; i < [preferences count]; i++)<br/>
{<br/>
if ([[[preferences objectAtIndex:i] objectForKey:@"ID"] isEqualToString:bundleID])<br/>
{<br/>
NSString *script = [[preferences objectAtIndex:i] objectForKey:key];<br/>
<br/>
if (script && ([script length] > 0))<br/>
{<br/>
NSAppleScript *AScript = [[NSAppleScript alloc] initWithSource:script];<br/>
[AScript executeAndReturnError:NULL];<br/>
[AScript release];<br/>
}<br/>
<br/>
break;<br/>
}<br/>
}<br/>
<br/>
}
Вот такой небольшой проект, а интересного внутри очень много. Надеюсь, что кому-то этот материал окажется полезен. Я же свою пользу уже получил – Dock автоматически скрывается при активации Симулятора iOS :).