бессонный хакатон
Бессонный хакатон: где экран?
Продолжаю писать о рефакторинге кода, написанного в свой бессонный хакатон. На этот раз хочется избавиться от подобранных вручную координат экрана эмулируемого компьютера — это неаккуратно и не универсально, — в другом браузере эти координаты скорее всего будут другими.
У меня
Такое решение наводило на меня тоску, но потом вспомнилось, что в графической библиотеке ПХП есть фильтр, который выделяет края и я решил глянуть не будет ли от него пользы.
Код для наложения фильтров выглядит следующим образом:
$im = imagecreatefrompng($screenshot_file);
imagefilter($im, IMG_FILTER_GRAYSCALE);
imagefilter($im, IMG_FILTER_EDGEDETECT);Мне казалось, что на получившемся изображении можно искать четыре точки экрана на известном расстоянии, но оказалось, что у такого метода есть ложные срабатывания. Поэтому я стал перебирать на экране все горизонтальные белые линии, пока не встречаю две параллельные нужной мне длины.
Таким образом очень надёжно находится граница экрана эмулятора, от которой на фиксированном расстоянии находится искомый экран. В итоге получается вот такой код:
const FRAME_W = 1039;
const FRAME_H = 808;
const FRAME_COLOR = 0xFFFFFF;
const FRAME_BORDER = 30;
$im = imagecreatefrompng($_SERVER["argv"][1]);
[$w, $h] = [imagesx($im), imagesy($im)];
imagefilter($im, IMG_FILTER_GRAYSCALE);
imagefilter($im, IMG_FILTER_EDGEDETECT);
for ($y = 0; $y <= $h - FRAME_H; $y++) {
for ($x = 0; $x <= $w - FRAME_W; $x++) {
$is_frame = imagecolorat($im, $x, $y) === FRAME_COLOR;
for ($i = 1; $i<FRAME_W; $i+=2) {
$is_frame =
$is_frame &&
imagecolorat($im, $x + $i, $y) === FRAME_COLOR &&
imagecolorat($im, $x + $i, $y + FRAME_H - 1) === FRAME_COLOR;
if (!$is_frame) {
break;
}
}
if ($is_frame) {
printf(
"%d,%d\n",
intdiv($x, 2) + FRAME_BORDER,
intdiv($y, 2) + FRAME_BORDER
);
exit(0);
}
}
}
exit(1);У меня тут всё в удвоенных координатах, которые я потом делю на два, чтобы получить реальные, потому что у меня экран «Ретина». Надо бы найти
Бессонный хакатон: нажатие на кнопку
Продолжаю писать про свой бессонный хакатон.
В решении, которое я накидал за ту ночь, планировалось нажимать на кнопки эмулятора «ЭплСкриптом» — на «Маке» это логичнее всего, так как этот язык заточен как раз под такие задачи — автоматизацию работы со сторонними графическими приложениями.
Удивительно, но у меня это сделать не получилось — несмотря на то, что я слал кнопки как описано в документации, в эмуляторе ничего не происходило.
Так как время уходило, — сроку я себе выделил до утра, пришлось использовать другое решение — через утилиту 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-Ревю»
Продолжаю писать про свой «бессонный хакатон».
В комментариях к первой заметке читатель Евгений Суреев посоветовал использовать внутренние средства оболочки электронного журнала, чтобы получать скриншоты. Этот способ существует, но мне он показался чересчур сложным и медленным. Но для очистки совести я сего всё же попробовал.
Получились следующие шаги:
- в эмулятор, кроме виртуального «диска» с журналом подключаем второй пустой диск;
- листаем на нужный экран внутри эмулятора;
- нажимаем в эмуляторе кнопки SS+S;
- переводим в появившемся окне курсор в поле выбора дисковода, пишем там B;
- в этом же окне переводим курсор в поле «Имя файла», пишем туда номер текущей страницы;
- повторяем шаги 2—5 до конца раздела;
- скидываем
куда-то на диск компьютера образ со скриншотами; - при помощи утилиты mctrd достаём из образа скриншоты;
- при помощи утилиты zx-image преобразовываем скриншоты в PNG;
- преобразовываем полученное в JPG, склеиваем с большой файл, закидываем в оболочку;
Весь этот интерактив с браузером (вызвать меню, ввести диск, ввести имя) — довольно долгий,
В общем, кажется слишком много проблем, которые надо решать, причём я ещё не знаю удастся ли их решить хорошо.
А теперь неспеша всё отрефакторим
Несколько дней назад я рассказывал о своём бессоном хакатоне, — ухаживая за дочкой во время её болезни, я особо не спал, а чтобы не умереть от безделья, придумал себе задачу — за ночь изобрести и реализовать способ вытащить из эмулятора спектрумовский электронный журнал «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 «ЭплСкрипта» выведет имя браузера в поток для ошибок, а конструкция $( … ) ждёт его в стандартном выводе.
ZX-Ревю 11 и 12 за 1997 год
В результате своего хакатона я сконвертировал в формат HTML совмещённый номер 11 и 12 за 1997 год электронного журнала «ZX-Ревю». Результат захостил на «Гитхабе», можно посмотреть что получилось.
Вся навигация — курсором мыши, на пункты меню можно щёлкать, текст прокручивается (полосы прокрутки не видно, так как в оригинале её тоже нет).
Совершенно без сил отправляюсь почивать.
Бессонный хакатон
Думаю
А тут дочка после ветрянки подхватила
В итоге, было проще не спать, чем спать вот так урывками, но просто так без сна листать ленты социальных сетей скучно, и я надумал устроить себе хакатон.
Условия — до рассвета решить
В момент этого решения я как раз долистал
Некоторые из них я тогда не смог посмотреть, так как они были в виде программ для «Спектрума» — издательство пыталось перейти на более прогрессивный формат электронного журнала.
Так я и определился с целью: выковырять содержимое этих журналов наружу, чтобы их можно было смотреть на моём ноутбуке не только в эмуляторе.
Для начала я поискал чем можно расковырять формат, в котором распространяется журнал. Утилита нашлась быстро и я даже поправил в ней небольшой баг — она не собиралась под «МакОС».
Изучение расковырянного содержимого принесло одно расстройство — внутри оказалось бинарное представление программы на Бейсике и несколько файлов неизвестного формата. Судя по тому как отображается журнал в эмуляторе, формат не текстовый. Вряд ли я бы справился с его обратным инжинирингом за ночь.
Поэтому я решил попробовать другую идею — сделать автоматическую скриншотилку, которая сдирала бы содержимое экрана эмулятора и собирала бы из него длинный графический файл. Так как у журнала двухуровневое меню, файлов должно образоваться несколько, а всю интерактивщину я решить сделать в обычном HTML и CSS.
В итоге получился набор инструментов из двух файлов на смеси bash и PHP.
Первый, make_screenshots скриншотит эмулятор, вырезает оттуда экран «Спектрума», жмёт на кнопку «вниз», заботится о том, чтобы экраны не дублировались и понимает когда больше не удаётся пролистнуть. Многие вещи там захардкожены — например, задержки, положение экрана эмулятора и кнопки на виртуальной клавиатуре.
Второй, convert_to_jpegs превращает полученный набор скриншотов в файлы JPEG. Во множественном числе, потому что оказалось, что максимальная высота файла JPEG — 65535 пикселей. В такую высоту у меня всё не влезало, поэтому пришлось разбивать получающуюся простыню два файла. Вообще, можно было бы использовать PNG, там такого ограничения нет, но он в таком размере очень сильно нагружает браузер.
Потом я эти скриншоты уже руками обверстал в HTML и планирую результат тоже положить на «Гитхаб», как только у меня закончится процесс оптимизации JPEG. Он очень долгий, но овчинка выделки стоит — около 20—25% экономии, а файлы там большие — по несколько мегабайт.
Как будет готово, напишу про это отдельный небольшой пост.