Как часто вы пользуетесь опциями некоторых программ (iTerm 2, Total Finder, Adium), которые позволяют показать окно приложения по нажатию на глобальный хоткей и скрыть это приложение при потере фокуса? Лично я — постоянно. А что если некая программа не имеет такого функционала и постоянно маячит перед глазами? Тот же Skype, например. Под катом вариант приведения своего рабочего пространства в порядок.
Открываем и скрываем приложение по хоткею
Начнем с самого простого. Задача: по нажатию на глобальный хоткей из любой программы передать фокус Skype и этим же действием его скрыть. Существует целый парк программ, которые позволяют сделать это очень просто. Однако мы не ищем простых путей и не готовы устанавливать дополнительные программки для этих целей. В данном случае на помощь приходит Automator с возможностью создать сервис и повесить хоткей на его вызов из любого приложения.
Запускаем Automator, выбираем тип документа Служба. В списке Служба получает выбираем нет входных данных в соседнем списке оставляем в любой программе. Добавляем в нашу службу действие Запустить AppleScript и вставляем следующий код:
set appName to "Skype"
set startIt to false
tell application "System Events"
if not (exists process appName) then
set startIt to true
else if frontmost of process appName then
set visible of process appName to false
else
set frontmost of process appName to true
end if
end tell
if startIt then
tell application appName to activate
end if
Данный код честно позаимствован отсюда.
Здесь все просто: скрипт проверяет, запущено ли приложение, если нет, то запускает его, иначе либо сворачивает его (если окно приложения находится в фокусе), либо отдает ему фокус (если в данный момент активно другое приложение).
Сохраняем нашу службу, переходим в Системные настройки > Клавиатура > Сочетание клавиш в списке слева выбираем Службы, в списке справа находим только что созданную нами службу и привязываем к ней хоткей. Цель достигнута, однако без одного но здесь не обойдется.
Проблема в том, что вызов службы не привязывается к глобальному хоткею. Службы вызываются из каждого приложения «локально». Например, находясь в данный момент в Safari, в меню программы мы найдем подменю Службы (т.е. Safari > Службы), где нам будет доступна наша служба для запуска. Соответственно запустить ее мы можем вручную из любого приложения, а назначенный нами хоткей имеет меньший приоритет по сравнению с настройками конкретного приложения. Отсюда имеем два выхода: либо назначить для вызова нашей службы сочетание клавиш, которое не используется больше нигде в системе, ни в одной программе, либо прибегнуть к помощи тех самых сторонних программ, которые дадут возможность повесить глобальный хоткей на вызов службы. Лично я пошел по третьему пути и воспользовался возможностями Alfred, который позволяет не прибегать к выше описанным действиям и дает возможность показывать и скрывать приложение по нажатию на заданный глобальный хоткей.
Что там с автохайдингом?
Итак, мы научились показывать и скрывать приложение по нажатию на глобальный хоткей, но жать на кнопку, прежде чем переключиться в другое приложение, напряжно. Решим и эту проблему.
Открываем Редактор AppleScript и вставляем следующий код:
property appName : "Skype"
on idle
tell application "System Events"
set focusedApp to (name of the first process whose frontmost is true)
if (focusedApp is not appName) and (exists process appName) and (visible of process appName) then
set visible of process appName to false
end if
end tell
return 0.5
end idle
Жмем Файл > Сохранить, выбираем формат файла — Программа, ставим галку рядом с Оставлять открытым после запуска обработчика, сохраняем программу. После этого в редакторе доступна кнопка Содержание пакета, нажав на нее, справа выползет окно, в котором ищем кнопку с шестерней, в появившемся меню выбираем Показать в Finder:
В открывшейся папке будет лежать файл Info.plist, открываем его в текстовом редакторе, после четвертой строки вставляем:
<key>LSBackgroundOnly</key>
<string>1</string>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSBackgroundOnly</key>
<string>1</string>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>applet</string>
<key>CFBundleIconFile</key>
<string>applet</string>
<key>CFBundleIdentifier</key>
<string>com.apple.ScriptEditor.id.HideSkype</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>HideSkype</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>aplt</string>
<key>LSMinimumSystemVersionByArchitecture</key>
<dict>
<key>x86_64</key>
<string>10.6</string>
</dict>
<key>LSRequiresCarbon</key>
<true/>
<key>WindowState</key>
<dict>
<key>dividerCollapsed</key>
<false/>
<key>eventLogLevel</key>
<integer>2</integer>
<key>name</key>
<string>ScriptWindowState</string>
<key>positionOfDivider</key>
<real>333</real>
<key>savedFrame</key>
<string>55 281 602 597 0 0 1440 878 </string>
<key>selectedTabView</key>
<string>event log</string>
</dict>
</dict>
</plist>
Сохраняем файл.
Вот и все, запускаем наше приложение, добавляем в автозагрузку и радуемся полученному результату. Отныне скайп глаза не мозолит.
Если вам интересны особенности реализации, то читаем дальше.
Разбор полетов
AppleScript для меня оказался открытием, так что буквально пару дней назад я и не знал о существовании этого «чуда» (уж очень он смахивает на язык для домохозяек). Зато предоставляет достаточно возможностей для автоматизации процессов в вашем рабочем окружении. Для выше описанных действий не понадобилась установка инструментов разработчика, а учитывая полученный результат, можно говорить о наличии мощного инструмента, который всегда под рукой.
Попробую разложить по полочкам решение задачи про автоскрытию приложения.
Сначала научимся определять, находится ли конкретное приложение в фокусе, или нет:
set appName to "Skype" --думаю, комментарии не уместны
tell application "System Events" --начинаем работу с объектом application, в данном случае с программой System Events
set focusedApp to (name of the first process whose frontmost is true) --записываем в focusedApp имя приложения, которое в данный момент в фокусе
if focusedApp is appName then
--вот и выяснили, что приложение с именем appName находится в фокусе
end if
end tell
Очень непривычно читать такой код, зато приятно — сомодокументированный код.
Если фокус приложения потерян, то скрываем его:
set appName to "Skype"
tell application "System Events"
set focusedApp to (name of the first process whose frontmost is true)
if (focusedApp is not appName) and (exists process appName) and (visible of process appName) then
set visible of process appName to false
end if
end tell
Вот и решение задачи. Осталось завернуть его в бесконечный цикл.
set appName to "Skype"
repeat while true
tell application "System Events"
set focusedApp to (name of the first process whose frontmost is true)
if (focusedApp is not appName) and (exists process appName) and (visible of process appName) then
set visible of process appName to false
end if
end tell
delay 0.5 --делаем задержку в пол секунды перед следующей итерацией
end repeat
И это уже рабочее решение. Однако если мы оставим его в таком виде, то скрипт просто-напросто зависнет, хоть и будет работать корректно. Решение — вынести цикл в отдельный поток. Но увы, потоков в AppleScript нет. Идем в документацию и открываем для себя хэндлер idle. Хэндлерами в AppleScript называются, по сути, привычные нам процедуры. Особенность хэндлера idle в том, что он вызывается системой каждые 30 секунд, если не возвращает никакого значения, если же он вернет, допустим, 5, то будет вызываться каждые 5 секунд.
on idle
set appName to "Skype"
tell application "System Events"
set focusedApp to (name of the first process whose frontmost is true)
if (focusedApp is not appName) and (exists process appName) and (visible of process appName) then
set visible of process appName to false
end if
end tell
return 0.5
end idle
Также можно отказаться от использования idle и работать с хендлерами run и quit:
property appName: "Skype"
property running: true
on run
repeat while running
tell application "System Events"
set focusedApp to (name of the first process whose frontmost is true)
if (focusedApp is not appName) and (exists process appName) and (visible of process appName) then
set visible of process appName to false
end if
end tell
delay 1
end repeat
end run
on quit
set running to false
end quit
Хочу один обработчик для нескольких программ
Да без проблем:
property appsToHide: {"Skype", "Adium", "Sublime Text 2"}
on idle
tell application "System Events"
set focusedApp to name of the first process whose frontmost is true
repeat with appToHide in appsToHide
if (focusedApp is not in appToHide) and (exists process appToHide) and (visible of process appToHide) then
set visible of process appToHide to false
end if
end repeat
end tell
return 0.5
end idle
Стоит отметить следующий момент: focusedApp is not in appToHide
. Здесь проверяется, содержится ли значение focusedApp
в appToHide
, хотя правильно бы было просто сравнить строки (операторы =, is equal, equals, [is] equal to, is not
). Но в данном примере строки (тип данных
isn't, isn't equal [to], is not equal [to], doesn't equal, does not equaltext
) так и не захотели корректно сравниваться.
На этом я заканчиваю свой рассказ. Много подробностей об AppleScript можно найти в официальной документации. Если кто сомневается в актуальности данного инструмента, то посмотрите в release notes — возможности языка расширяют с каждым релизом OS X. Также поисковики пестрят уже готовыми howto по решению задач с помощью AppleScript.
Спасибо за внимание.
Автор: NayZaK