- PVSM.RU - https://www.pvsm.ru -

Сворачивание приложений в Dock для ленивых с помощью AppleScript

Как часто вы пользуетесь опциями некоторых программ (iTerm 2, Total Finder, Adium), которые позволяют показать окно приложения по нажатию на глобальный хоткей и скрыть это приложение при потере фокуса? Лично я — постоянно. А что если некая программа не имеет такого функционала и постоянно маячит перед глазами? Тот же Skype, например. Под катом вариант приведения своего рабочего пространства в порядок.

Открываем и скрываем приложение по хоткею

Начнем с самого простого. Задача: по нажатию на глобальный хоткей из любой программы передать фокус Skype и этим же действием его скрыть. Существует целый парк [1] программ, которые позволяют сделать это очень просто. Однако мы не ищем простых путей и не готовы устанавливать дополнительные программки для этих целей. В данном случае на помощь приходит 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

Как это выглядит

Сворачивание приложений в Dock для ленивых с помощью AppleScript

Данный код честно позаимствован отсюда [2].
Здесь все просто: скрипт проверяет, запущено ли приложение, если нет, то запускает его, иначе либо сворачивает его (если окно приложения находится в фокусе), либо отдает ему фокус (если в данный момент активно другое приложение).

Сохраняем нашу службу, переходим в Системные настройки > Клавиатура > Сочетание клавиш в списке слева выбираем Службы, в списке справа находим только что созданную нами службу и привязываем к ней хоткей. Цель достигнута, однако без одного но здесь не обойдется.

Проблема в том, что вызов службы не привязывается к глобальному хоткею. Службы вызываются из каждого приложения «локально». Например, находясь в данный момент в Safari, в меню программы мы найдем подменю Службы (т.е. Safari > Службы), где нам будет доступна наша служба для запуска. Соответственно запустить ее мы можем вручную из любого приложения, а назначенный нами хоткей имеет меньший приоритет по сравнению с настройками конкретного приложения. Отсюда имеем два выхода: либо назначить для вызова нашей службы сочетание клавиш, которое не используется больше нигде в системе, ни в одной программе, либо прибегнуть к помощи тех самых [1] сторонних программ, которые дадут возможность повесить глобальный хоткей на вызов службы. Лично я пошел по третьему пути и воспользовался возможностями 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:

Как это выглядит

Сворачивание приложений в Dock для ленивых с помощью AppleScript

Сворачивание приложений в Dock для ленивых с помощью AppleScript

В открывшейся папке будет лежать файл 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 equal
). Но в данном примере строки (тип данных text) так и не захотели корректно сравниваться.

На этом я заканчиваю свой рассказ. Много подробностей об AppleScript можно найти в официальной документации [3]. Если кто сомневается в актуальности данного инструмента, то посмотрите в release notes [4] — возможности языка расширяют с каждым релизом OS X. Также поисковики пестрят уже готовыми howto по решению задач с помощью AppleScript.

Спасибо за внимание.

Автор: NayZaK

Источник [5]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/mac-os-x/48804

Ссылки в тексте:

[1] целый парк: http://superuser.com/a/309144

[2] отсюда: http://brettterpstra.com/2011/01/22/quick-tip-applescript-application-toggle/

[3] в официальной документации: https://developer.apple.com/library/mac/documentation/AppleScript/Conceptual/AppleScriptLangGuide/introduction/ASLR_intro.html#//apple_ref/doc/uid/TP40000983

[4] release notes: https://developer.apple.com/library/mac/releasenotes/AppleScript/RN-AppleScript/Introduction/Introduction.html#//apple_ref/doc/uid/TP40000982-CH1-SW1

[5] Источник: http://habrahabr.ru/post/202864/