«Съешь меня»… нет, не так… «Выполни меня»!
У меня периодические возникают разные потребности решения мелких насущных задач в Mac OS X. Для этих целей я обычно делаю небольшие программы, которые «закрывают» потребность частным образом. Но иногда хочется, чтоб программа была универсальной, и ей могли воспользоваться другие люди при необходимости (например «Переlator»). Так получилось и в этот раз…
Я люблю, когда Dock отображается всегда на экране. Но при запуске Симулятора iOS постоянно приходилось включать автоматические скрытие, чтобы симулятор полностью умещался на экране. Появилась задача — автоматизировать этот процесс. За пару дней набросал универсальную программу, с помощью которой можно задать AppleScript на определённое действие любой программы: «Программа запущена», «Программа завершена», «Программа активирована», «Программа деактивирована» и пр.
Этот топик я разделю на две части. Одна для пользователей, которые просто хотят пользоваться программой (или ознакомиться). Вторая для начинающих разработчиков – я опишу схему работы программы и предоставлю исходный код. Несмотря на кажущуюся простоту самой программы, исходный код покрывает множество нюансов из различных тематик, которые могут сэкономить существенное количество времени в будущем.
Загрузить программу можно по ссылке (только для 10.6+) (в 18.51 я обновил программу и исходные коды благодаря баг-репорту. Исправил маленький баг, из-за которого помощник не получал сообщения после удаления программы из списка. Издержки кустарного тестирования. ).
Программа очень проста в использовании (если имеются навыки работы с AppleScript). Это не отдельное приложение, а панель для «Системных настроек». Два раза щёлкаете по нему мышкой и получаете новую панель «Выполни меня» в «Системных настроек». В левой части находится список программ. А в правой скрипты, заданные для выбранной программы.
При нажатии на кнопку "+" откроется список запущенных программ (имеющих идентификатор – Bundle identifier), из которого мы можем выбрать нужную программу для добавления (если требуемая программа не запущена, то её нужно предварительно запустить).
Всё, что осталось, это задать скрипты на те действия, которые требуются. Например, включать режим автоматического скрытия 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 + щелчок мышью / на тачпаде) на названии панели «Выполни меня» и выберите «Удалить панель».
ДЛЯ НАЧИНАЮЩИХ РАЗРАБОТЧИКОВВот ссылка на исходный код проекта. Вот вариант на github.
Программа состоит из двух частей — панели настроек и программы помощника. Для каждой из них я сделал отдельный проект (в данном случае мне так было удобнее). При необходимости два проекта можно объединить в один с двумя таргетами.
Создание панели для «Системных настроек» мало чем отличается от создания обычной программы. В Xcode уже есть шаблон для этого модуля System Plug-in > Preference Pane. В котором уже добавлен класс на базе NSPreferencePane и интерфейсный (xib) файл к нему. Каждый решает сам, как тестировать этот модуль. Например, можно делать всё в виде обычной программы и лишь на финальном этапе переносить всё в Preference Pane.
Важно понимать, что это не самостоятельная программа, а модуль для «Системных настроек». Это означает, что все методы, которые привязаны к основному bundle не будут корректно выполнены (т.к. основной bundle — это «Системные настройки»).
Мы не можем пользоваться макросом следующего вида:
У объекта класса NSPreferencePane есть метод:
Его и нужно использоваться.
В проекте используются две кнопки "+" и "-" для редактирования списка программ.
Инициализация кнопки удаления банальная и простая:
А вот кнопка добавления уже сложнее. Для неё необходимо ввести два подкласса NSPopUpButton и NSPopUpButtonCell. Формально для кнопки с выпадающим списком служит класс NSPopUpButton. Но в голом виде он нам не подходит. Во-первых, не позволяет задать статическую картинку для кнопки (точнее позволяет, но реальный размер кнопки не соответствует реальному размеру статической картинки, что в нашем случае неприемлемо, т.к. у нас две кнопки расположены рядом) — это мы обходим с помощью подкласса NSPopUpButtonCell. Во-вторых, NSPopUpButton не позволяет динамически изменить содержимое меню в момент её нажатия (у нас меню должно формироваться именно в момент нажатия на кнопку) – это мы обходим с помощью подкласса NSPopUpButton.
Чтобы кнопка имела размер один в один с заданной картинкой, создаём класс PopUpCell:
Что означает весь этот код? Всё просто — мы создаём объект класса NSButtonCell и отрисовываем его вместо NSPopUpButtonCell. Т.е. программа рисует NSButtonCell (размер которой может соответствовать один в один с заданной картинкой) вместо NSPopUpButtonCell, но функционально это NSPopUpButton.
Теперь меню… С динамическим меню поступим следующим образом – добавим делегата в NSPopUpButton, который будет выдавать нам меню в тот момент, когда нажата левая кнопка мыши на кнопке.
Создаём подкласс NSPopUpButton:
При нажатии мышкой, перед тем, как событие начнёт обрабатываться объектом класса NSPopUpButton, мы устанавливаем меню делегата этому объекту.
Теперь можно добавлять нашу кнопку "+" на нашу панель:
в нашем основном классе.
Как получить список запущенных программ для меню?
У объекта класса NSWorkspace есть метод:
Из этого объекта apps и будет формироваться (см. исходный код) меню для кнопки добавления.
Наш объект NSTableView отображает ячейки с картинкой, заголовком и подзаголовком. Стандартной ячейки такого типа нет, её необходимо сделать самостоятельно.
Объявляем класс AppCell:
Класс ячейки готов, осталось только добавить его в наш объект NSTableView. Существует два способа.
1). Если количество объектов в таблице небольшое, то можно воспользоваться методом делагата (которым является наш основной класс NSPreferencePane) к NSTableView:
Данные (файл со списком программ и скриптами и иконки) сохраняются в соответствующей папке Application Support (пользовательская Библиотека). Получить путь к этой папке можно (и нужно) следующим образом:
Если пользователь изменил какие-либо данные, то через 3 секунды произойдёт автоматическое сохранение. Если в этот период пользователь продолжает редактировать данные, то предыдущий запрос на сохранение отменяется и создаётся новый. Подобный механизм удобно использовать, например, при поиске (чтобы поиск срабатывал лишь через некоторое время после окончания редактирования).
После сохранения извещаем помощника, что данные изменены:
Это методы класса. Они не привязаны к какому-либо объекту, их можно вызывать просто [LoginItems addApplication:path].
Полную реализацию этих методов можно посмотреть в исходных кодах. Вот как, для примера, реализован метод добавления:
Вот в принципе и вся наша панель.
Теперь помощник… он очень просто. Т.к. помощник не должен быть виден пользователю, в файле описания программы выставляем ключ LSBackgroundOnly в YES. Это означает, что программа не будет отображаться в доке, в окне Force Quit, не будет отображать меню и пр.
Самая главная часть инициализация помощника – это получения сообщений от панели, что настройки изменены (для их перезагрузки), и получение сообщений NSWorkspace о состоянии программ: