applescript
Бессонный хакатон: нажатие на кнопку
Продолжаю писать про свой бессонный хакатон.
В решении, которое я накидал за ту ночь, планировалось нажимать на кнопки эмулятора «ЭплСкриптом» — на «Маке» это логичнее всего, так как этот язык заточен как раз под такие задачи — автоматизацию работы со сторонними графическими приложениями.
Удивительно, но у меня это сделать не получилось — несмотря на то, что я слал кнопки как описано в документации, в эмуляторе ничего не происходило.
Так как время уходило, — сроку я себе выделил до утра, пришлось использовать другое решение — через утилиту cliclick, которая умеет эмулировать нажатие мышкой:
cliclick m:800,630 w:200 c:. w:200 m:+100,+100Я просто закодировал координаты нужной кнопки в том месте, где у меня она была на экране. Одна проблема — кнопка после нажатия «залипала», но я решил тем, что через 200 миллисекунд отводил курсор в сторону — w:200 m:+100,+100 на птичьем языке утилиты.
Уже позже, во время рефакторинга, я обратил внимание, что когда я нажимаю кнопку на своём ноутбуке, то в эмуляторе иногда нажимаются не одна кнопка, а две. Если жать стрелки вниз или вверх, то в эмуляторе происходит нажатие на CS+6 и на CS+7 соответственно, причём оболочка электронного журнала правильно реагирует и на эту комбинацию, и на одиночные «6» и «7».
Для эксперимента попробовал слать вместе с основной клавишей ещё
function press_key {
local key
if [[ "$1" =~ ^[0-9]{2,}$ ]]; then
key="key code $1"
else
key="keystroke \"$1\""
fi
osascript <<PRESS
tell application "System Events" to $key using command down
delay .03
tell application "System Events" to $key
PRESS
}Она определяет что ей передали — код символа или сам символ и «нажимает» запрошенную клавишу. Второй раз я «нажимаю» ту же клавишу уже без модификатора, иначе она
А теперь неспеша всё отрефакторим
Несколько дней назад я рассказывал о своём бессоном хакатоне, — ухаживая за дочкой во время её болезни, я особо не спал, а чтобы не умереть от безделья, придумал себе задачу — за ночь изобрести и реализовать способ вытащить из эмулятора спектрумовский электронный журнал «ZX-Ревю».
Способ я придумал, реализовал и выложил на «Гитхаб». Но мозгу задача понравилось, не могу остановиться и перестать думать о том как всё переделать. Уже начал накидывать новый прототип. Выкладывать целиком пока не буду, там не готово,
Первое, что мне хотелось бы сделать — избавиться от привязки к браузеру. В прошлой реализации я просто активизировал «Сафари», после чего считал, что в активном окне открыт нужный мне эмулятор. Я подумал, что хорошо бы посмотреть какие у нас запущены браузеры и проверить открыто ли
Так как я делаю всё под «Мак», программировать решил на «ЭплСкрипте» — так проще всего.
on is_running(appName)
tell application "System Events" to (name of processes) contains appName
end is_running
set browsers to {"Safari", "Chrome", "Opera", "Firefox", "Edge", "Yandex"}
repeat with browser in browsers
if is_running(browser) then
tell application browser
if exists window "ZX Spectrum online emulator" then
log browser
activate
exit repeat
end if
end tell
end if
end repeatЧто тут происходит? Я перебираю все известные мне браузеры по имени, ищу какие из них запущены, в них смотрю — называется ли активное окно «ZX Spectrum online emulator», если да, остаюсь в нём, сообщая наружу какой из браузеров найден.
Можно было бы ещё перебрать окна каждого из браузеров, но есть опасность остановиться не в том эмуляторе — мало ли, вдруг у человека несколько запущено. Сделать несложно, но кажется это может добавить проблем.
Так как основная оболочка у меня на «Баше», осталось
BROWSER=$(osascript <<BROWSERS 2>&1
# … тут программа на «ЭплСкрипте»
BROWSERS
)В итоге, в переменной $BROWSER будет лежать имя браузера или пусто, если подходящий не найден.
Перенаправление потока 2>&1 необходимо, так как команда log «ЭплСкрипта» выведет имя браузера в поток для ошибок, а конструкция $( … ) ждёт его в стандартном выводе.
AppleScript Tetris ★
Я очень люблю «ненормальное программирование» — где вместо получения результата (такого мне и на работе хватает), приходится преодолевать нетривиальные трудности, придумывать
Недостатка в таких задачах у меня в голове не бывает, но руки приложить к ним получается нечасто. «Тетрис» на ЭплСкрипте — один из примеров такого «приложения рук», прошло три года, но он до сих пор мне так нравится, что я решил рассказать о нём ещё раз.
Для начала немного об ЭплСкрипте. Это скриптовый язык для автоматизации, который встроен в МакОС в незапамятные времена — в «Википедии» написано, что он появился аж в 1993 году, то есть широкоизвестный ПХП его младше аж на два года!
Несмотря на то, что язык уже несколько лет не обновляется, заброшенным его не назовёшь, скорее всего в нём просто нечего обновлять — все задачи, которые он призван решать, он решает, в МакОСи есть даже встроенный визуальный редактор и я знаю ребят, которые делают на нём потрясающие вещи в плане создания
Естественно можно программировать и в обычном текстовом редакторе, программы в этом случае выглядят как текст на немного необычном английском языке:
tell application "Microsoft Word" to quit
display dialog "Hello, world!"
print pages 1 thru 5 of document 2Сильно популярным его не назовёшь, но в своей нише он хорош и продвинутые «маководы» о нём знают. До «Тетриса» я писал на нём «песню о пиве» и интерпретатор Брейфака.
За всё историю существования на ЭплСкрипте никто никогда не писал графических интерактивных игр. Причина проста: в языке нет поддержки графики и опроса клавиатуры. ?, расходимся!
Тем не менее идея создать на нём
Первым придумался способ вывода графики — я подумал, что фигуры можно рисовать свёрнутыми окошками
Для этого, в припципе, всё есть — ЭплСкрипт умеет запускать программы и управлять размерами и расположением окон:
tell application "TextEdit"
set the bounds of 1st window whose id is wid to {x, y, (x + |size|), (y + |size|)}
endКогда я попробовал это запрограммировать, меня ждал сюрприз: оказывается программно размер окна можно стягивать в точки разных размеров (мышкой это сделать не получается), составленные из них стакан и фигуры выглядели вполне приемлемо.
Так как вся графика создаётся окошками приложений, то традиционного окна, в котором всё работает, у программы нет — его роль выполняет рабочий стол. Стакан тетриса прилеплен к верхнему краю и отцентрирован по горизонтали.
В первом же прототипе появилась ещё одна проблема — наверху у «МакОСи» есть строка меню, приложения не могут на неё залезать, при попытке вывести в эти координаты окно, оно «сползает» вниз. В итоге первые несколько точек выводились в других координатах и всё рушилось. Было два решения — центрировать ещё и по вертикали
В итоге, способ придумался. Сразу после запуска я вывожу окно в нулевые координаты и смотрю на сколько пикселей оно съехало, это и будет высотой меню:
-- Get height of menu bar
tell application "TextEdit"
activate
make new document at the front
set the bounds of 1st window to {0, 0, 0, 0}
set coords to bounds of 1st window
close 1st window
end
set minimalY to coords's item 2С графикой разобрались, а что с клавишами? В ЭплСкрипте, как я уже говорил, нет способов опросить клавиатуру, существуют плагины и сторонние решения, но их использование рушит всю идею. Хотелось чистого решения.
Голову пришлось ломать очень долго, пока в один момент я не додумался заиспользовать под это опрос значения громкости — уменьшение и увеличение громкости как кнопки влево/вправо, а отключение динамика — для поворота фигуры.
Для этого на пуске я устанавливаю громкость на середину и в цикле опрашиваю текущее значение. После каждого изменения значение опять сбрасывается на среднее:
on levelReset()
set volume output volume volumeMiddle without muted
end
on checkDirection()
set level to output volume of (get volume settings)
levelReset()
_sign(level - volumeMiddle)
end
on checkRotate()
return output muted of (get volume settings)
endТут _sign — моя собственная функция взятия знака, у ЭплСкрипта нет встроенной.
В получившийся вариант вполне можно играть — я пробовал. Немного странное управление через несколько минут уже перестаёшь замечать, единственное, чего не хватает — кнопки сброса фигуры, но тут уж ничего не попишешь.
Если вдруг
Полный исходный код есть на гитхабе.
AppleScript и Тетрис ★
Я тут книгу читаю про хакеров (в первоначальном смысле слова) — «Хакеры. Герои компьютерной революции». Книга довольно занудная, но тот дух, который она пытается передать, я ещё очень хорошо помню. Ассемблер, такты, хаки, придуманные трудности — всё это было как специи в программировании. Сейчас
И так тоскливо
Трудности, которые мне тут успешно самому себе удалось придумать, заключаются в том, что ЭплСкрипт в этой плоскости очень бедный язык. Если не сказать нищий. У него нет встроенных средств рисования
Для тех, кто не знает, надо наверное пояснить. ЭплСкрипт — это язык автоматизации пользовательских сценариев работы, он умеет управлять приложениями (с разной степенью успеха)
Так как возможности
Дольше всего я ломал голову над управлением, пока не придумал следующее — ЭплСкрипт умеет опрашивать значение громкости, так что клавиши уменьшения и увеличения я приспособил под сдвиг фигуры, а кнопку включения/выключения звука — под поворот.
Над скоростью игры я в некоторых местах поработал, но сильно углубляться не стал — ЭплСкрипт относительно шустрый, основную задержку вносит создание окон, это место вряд ли можно
Кажется это первая и единственная игра в реальном времени на ЭплСкрипте.
Передача по ссылке в AppleScript'е
ЭплСкрипт, конечно, очень странный язык. С одной стороны, ребята явно старались сделать программирование похожим на написание текста на естественном (английском) языке, с другой стороны, все кишочки спрятать не удалось. Любимые мои грабли — это передача значений по ссылке.
У этой штуки есть одна особенность, которую надо знать. Код ниже перебирает список и выводит в консоль всякую информацию об единственном элементе, который в списке содержится:
repeat with itm in {false}
log itm -- Выведет «false»
log class of itm -- Выведет «boolean»
log itm is false -- Выведет… «false», хм
log itm is true -- И снова «false»
end repeatНепонятно, да? Элемент булевского типа, но не «истина» и не «ложь», как так? Дело в том, что этот элемент — ссылка. И прежде чем его
repeat with itm in {false}
log contents of itm is false -- Выведет «true»
end repeatКонтринтуитивно и неясно какую пользу это приносит, но вот так сделано.
99 бутылок: AppleScript, Applesoft BASIC
У меня тут мой ноут поперхнулся чаем, так что я неделю был на стареньком компике под Виндой, почувствовал себя себя как без рук. По этой же причине почти не писал ничего в блог, лишь одним вечером на ноуте жены написал очередные две «Песни про пиво» на языках AppleScript и Applesoft BASIC. Первый — современный язык для автоматизации мелочи на «Маках», второй — древний (1977 год) бейсик,
52. AppleScript — встроенный в операционную систему «МакОС» язык программирования, используемый для мелкой автоматизации. По программе этой не очень очевидно, но язык максимально приближен к английскому, причём многие вещи можно формировать разнообразными способами — так же, как бы это сделал носитель английского языка.
В остальном, это обычный динамический интерпретируемый язык со слабой типизацией. Как видно в одном месте мне пришлось сделать преобразование типа.
(* Песня про пиво. Автор — Евгений Степанищев *)
repeat with beer from 99 to 1 by -1
set b to bottles(beer)
log b & " on the wall, " & b & "."
log "Take one down and pass it around, " & bottles(beer - 1) & " of beer on the wall."
log
end repeat
log "No more bottles of beer on the wall, no more bottles of beer."
log "Go to the store and buy some more, 99 bottles of beer on the wall."
on bottles(beer)
if beer = 0 then
return "no bottles"
else if beer > 1 then
return (beer as string) & " bottles"
else
return "1 bottle"
end if
end bottle51. Applesoft BASIC — Бейсик разработанный компанией Майкрософт, использовавшийся в компьютерах фирмы Эпл, начиная с 1977 года. Ничего особо примечательного, вполне полнофункциональный язык, хоть и не без недостатков тех ранних «бейсиков» — вызов функций по номеру строки, параметров нет, локальных переменных нет, в общем, все прелести.
Я специально использовал конструкцию «ON…GOTO», хоть в ней и не было необходимости — хотел показать аналог конструкции switch. SGN тут — встроенная функция нахождения знака выражения.
000 REM Песня про пиво, автор — Евгений Степанищев
001 HOME: TEXT
010 FOR beer = 99 TO 1 STEP -1
020 b = beer: GOSUB 100
030 ? bottles$; " on the wall, "; bottles$; "."
035 b = beer - 1: GOSUB 100
040 ? "Take one down and pass it around, "; bottles$; " of beer on the wall."
050 ?
060 NEXT
070 ? "No more bottles of beer on the wall, no more bottles of beer."
080 ? "Go to the store and buy some more, 99 bottles of beer on the wall."
090 END
099 REM
100 ON SGN(b-1)+2 GOTO 120, 130, 110
110 LET bottles$ = STR$(b) + " bottles": RETURN
120 LET bottles$ = "no bottles": RETURN
130 LET bottles$ = "1 bottle": RETURNКак мне AppleScript
Миша, мой коллега, под постом, где я написал про интерпретатор Brainfuck на AppleScript с беспокойством осведомился, не подсел ли я на AppleScript.
Краткий ответ: нет.
Длинный ответ таков.
Мне непонятно текущее состояние AppleScript. Очевидно, что концепция «пользователь пишет программы на английском» вполне себе ничего. Для непрограммистов это был бы выход. А для программистов такой язык жутко неудобен. По понятными причинам. Программисты привыкли писать много, а на велиречивом AppleScript писать не каждому понравится (не пишу «никому», потому что существует же Objective C). Все эти сокращения в языке — «собачки», доллары и разные скобочки, их нужно помнить, но они здорово экономят время при написании программы и при её чтении. Потому что они короткие и их больше помещается на экране.
Текущее состояние непонятно, потому что цели своей авторы языка не достигли. До сих пор нужно писать «set Var to Var + 1» и нельзя «set Var to Var plus one», какой же это английский? При этом язык уже полтора года не развивается.
То есть проблема «иногда быстро писать мелкие программы» явно пока не решена. Так как для этого «иногда» надо иметь память робота, чтобы вспомнить через несколько месяцев как в этом языке
Например, чтобы вызвать текстовый редактор и написать там «Hello», нужно сделать следующее:
tell application "TextEdit"
activate
tell application "System Events" to keystroke "hello"
end tellЯ открываю приложение TextEdit (так встроенный в MacOS текстовый редактор называется) и прошу System Events понажимать в редактор несколько клавиш. Выделенное наклонным — это как раз пример такого API. Причём, если написать в кавычки «³˚¹⁄₃¹⁄₂», то программа либо не сделает ничего, либо напечатает в редактор ерунду. Всё устроено так, как будто System Events действительно жмёт клавиши на клавиатуре. А вот эти символы система не знает как ввести. То же будет с китайскими иероглифами, например.
Ещё, когда я только начал знакомиться с AppleScript, у меня было заблуждение, что AppleScript — язык общего назначения, вроде PowerShell или Bash. Ничего подобного, вместо bash в Mac OS есть bash. AppleScript толком не умеет даже получить данные от пользователя, скажем перехватить нажатие клавиши (и нет ничего встроенного, что может ему в этом помочь), а уж о том, как мало он умеет самостоятельно выводить и говорить нечего.
Так что симпатии к языку у меня не появится, думаю. Не для меня он. Но писать я на нём иногда буду, я и на CMD много странного писал в своё время.
Brainfuck на AppleScript
Для развлечения написал интерпретатор языка Brainfuck на AppleScript.
Особенности: для ввода программы используется текстовое поле, которое выглядит как однострочное, но на деле понимает многострочный текст. Операция «,» у меня с буфером, это значит, что при запросе можно вводить сразу строку, при надобности интерпретатор сам будет брать оттуда значения. Если их не хватит, запросит ещё.
AppleScript медленный язык, следовательно и получившийся интерпретатор медленный (никаких оптимизаций я не делал), лучше всего на нём запускать небольшие программы. Например, «Hello, world»:
++++++++[>+++++++++<-]>.<+++++[>++++++<-]>-.+++++++..+++.<
++++++++[>>++++<<-]>>.<<++++[>------<-]>.<++++[>++++++<-]>
.+++.------.--------.>+.Не уверен, что нигде не ошибся Интерпретатор Brainfuck на AppleScript.
#!/usr/bin/osascript
-- AppleScript Brainfuck interpreter
-- Evgeny Stepanischev 1 Jul 2011 //bolknote.ru
set Cells to {}
repeat 30000 times
set end of Cells to 0
end repeat
display dialog "Enter your BF program" default answer ""
set Bf to text items of text returned of result
set Pointer to 1
set CmdPointer to 1
set Output to ""
set Input to {}
set Stack to {}
repeat while CmdPointer ≤ (count of Bf)
set Cmd to item (CmdPointer) of Bf
if Cmd is equal to ">" then
set Pointer to Pointer + 1
if Pointer > 30000 then set Pointer to 1
else if Cmd is equal to "<" then
set Pointer to Pointer - 1
if Pointer < 1 then set Pointer to 30000
else if Cmd is equal to "+" then
set Val to (item (Pointer) of Cells) + 1
if Val is greater than 255 then set Val to 0
copy Val to item (Pointer) of Cells
else if Cmd is equal to "-" then
set Val to (item (Pointer) of Cells) - 1
if Val is less than 0 then set Val to 255
copy Val to item (Pointer) of Cells
else if Cmd is equal to "." then
set Output to Output & (ASCII character item (Pointer) of Cells)
else if Cmd is equal to "," then
if Output ≠ "" then
display alert Output
set Output to ""
end if
if Input is {} then
display dialog "Enter one or more characters" default answer ""
set Res to text returned of result
set item (Pointer) of Cells to ASCII number of character 1 of Res
try
set Input to rest of text items of Res
end try
else
set item (Pointer) of Cells to ASCII number of item 1 of Input
set Input to rest of Input
end if
else if Cmd is equal to "[" then
if item (Pointer) of Cells = 0 then
set CmdPointer to CmdPointer + 1
set Deep to 1
repeat while CmdPointer ≤ (count of Bf)
set Cmd to item (CmdPointer) of Bf
if Cmd is equal to "[" then
set Deep to Deep + 1
else if Cmd is equal to "]" then
set Deep to Deep - 1
if Deep is 0 then exit repeat
end if
set CmdPointer to CmdPointer + 1
end repeat
else
set Stack to (CmdPointer - 1) & Stack
end if
else if Cmd is equal to "]" then
if item (Pointer) of Cells ≠ 0 then
copy item (1) of Stack to CmdPointer
end if
set Stack to rest of Stack
end if
set CmdPointer to CmdPointer + 1
end repeat
if Output ≠ "" then
display alert Output
end if
Запуск сайтов из Spotlight
Занялся потихоньку автоматизацией своих задач на «Маке».
Очень хотелось сделать так, чтобы по короткому имени, набираемому в Spotlight, открывались
Я знаю, что тут много ребят сидят на Windows, поэтому поясню. Spotlight (его аналог) есть на Windows 7 — это поисковая строка, куда можно вбить начальные буквы
В Mac OS X удобно, что это поисковое поле появляется по кнопке Ctrl+Пробел.
Написать плагин к Spotlight мне пока слабо́, а вот набросать на AppleScript программу, открывающую одноимённый сайт — пожалуйста.
Идея такова: кладу на диск одну и ту же программу в нескольких копиях, с разными именами — «wiki», «jira», «calendar» и так далее. Теперь их можно запускать откуда угодно, включая Spotlight. После запуска программа смотрит какое у неё имя, прибавляет к нему префикс/суффикс и проверяет существует ли получившийся домен, если да, то открывает его в браузере.
Программу надо сохранить при помощи редактора AppleScript (AppleScript Editor) с нужным именем (↑⌘S), формат файла должен быть «программа» («application»).
Вот сама программа (наверное маску
-- Ищет по маскам хост, подставляя имя программы,
-- если найден, открывает его в браузере
-- bolknote.ru апрель 2011
tell application "System Events"
set appname to the name of the current application
end tell
repeat with mask in {"%.yandex-team.ru", "www.%.ru", "www.%.com"}
set AppleScript's text item delimiters to "%"
set mask to text items of mask
set AppleScript's text item delimiters to appname
set hostname to "" & mask
if resolve(hostname) then
open location "http://" & hostname
exit repeat
end if
end repeat
on resolve(host)
try
do shell script "host '" & host & "'"
return true
on error
return false
end try
end resolveЭто пока мой первый опыт программирования в AppleScript, так что, возможно,
Добавлено позднее: в комментариях один из читателей предложил более простой и изящный вариант. Ура!