бессонный хакатон

Бессонный хакатон: где экран?

Продолжаю писать о рефакторинге кода, написанного в свой бессонный хакатон. На этот раз хочется избавиться от подобранных вручную координат экрана эмулируемого компьютера — это неаккуратно и не универсально, — в другом браузере эти координаты скорее всего будут другими.

У меня какое-то время не было хороших идей как бы это сделать, всё что мне приходило в голову — это перебрать координаты и найти несколько примыкающих к друг другу палок определённого размера и цвета, которые окаймляют искомый экран.

Такое решение наводило на меня тоску, но потом вспомнилось, что в графической библиотеке ПХП есть фильтр, который выделяет края и я решил глянуть не будет ли от него пользы.

Эмулятор ZX Spectrum в браузере Opera: меню «ZX-РЕВЮ 11-12 1997» с пунктами «Описание оболочки», «Путеводитель», «Авторская разработка» и др.; внизу — виртуальная клавиатура.
Окно браузера, в котором запущен эмулятор; наша задача — понять где тут экран эмулируемого компьютера

Код для наложения фильтров выглядит следующим образом:

$im = imagecreatefrompng($screenshot_file);

imagefilter($im, IMG_FILTER_GRAYSCALE);
imagefilter($im, IMG_FILTER_EDGEDETECT);

Мне казалось, что на получившемся изображении можно искать четыре точки экрана на известном расстоянии, но оказалось, что у такого метода есть ложные срабатывания. Поэтому я стал перебирать на экране все горизонтальные белые линии, пока не встречаю две параллельные нужной мне длины.

Эмулятор ZX Spectrum в браузере Opera: меню «ZX-РЕВЮ 11-12 1997» с пунктами «Описание оболочки», «Путеводитель» и др., внизу — виртуальная клавиатура.
Окно браузера после применения фильтров: переведено в ч/б, выделены края

Таким образом очень надёжно находится граница экрана эмулятора, от которой на фиксированном расстоянии находится искомый экран. В итоге получается вот такой код:

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-Ревю»

Продолжаю писать про свой «бессонный хакатон».

В комментариях к первой заметке читатель Евгений Суреев посоветовал использовать внутренние средства оболочки электронного журнала, чтобы получать скриншоты. Этот способ существует, но мне он показался чересчур сложным и медленным. Но для очистки совести я сего всё же попробовал.

Скриншот меню программы для ZX Spectrum: пункты «Полоса прокрутки», «Изменить цвет текста», подсказки F6–F12, заголовок «21 ПРИ ПРОСМОТРЕ ВНЕШНЕГО РАЗДЕЛА...
Получившийся, в результате проделанных ниже шагов, скриншот

Получились следующие шаги:

  1. в эмулятор, кроме виртуального «диска» с журналом подключаем второй пустой диск;
  2. листаем на нужный экран внутри эмулятора;
  3. нажимаем в эмуляторе кнопки SS+S;
  4. переводим в появившемся окне курсор в поле выбора дисковода, пишем там B;
  5. в этом же окне переводим курсор в поле «Имя файла», пишем туда номер текущей страницы;
  6. повторяем шаги 2—5 до конца раздела;
Скриншот интерфейса с диалоговым окном «Запись экрана на диск», где указан дисковод B и имя файла Screen02; на фоне — меню с настройками и инструкциями по клавишам.
Меню сохранения скриншота в файл внутри эмулятора, нужно выбрать дисковод и имя файла
  1. скидываем куда-то на диск компьютера образ со скриншотами;
  2. при помощи утилиты mctrd достаём из образа скриншоты;
  3. при помощи утилиты zx-image преобразовываем скриншоты в PNG;
  4. преобразовываем полученное в JPG, склеиваем с большой файл, закидываем в оболочку;

Весь этот интерактив с браузером (вызвать меню, ввести диск, ввести имя) — довольно долгий, всё-таки эмулятор работает примерно со скоростью «Спектрума», а он был не особо шустрым. Перевод курсора в координаты это пока тоже проблема — мышки нет, надо выяснить где курсор сейчас и поставить его в нужное поле. Есть и проблема с сохранением дампа — надо как-то из браузера найти нужную папку.

В общем, кажется слишком много проблем, которые надо решать, причём я ещё не знаю удастся ли их решить хорошо.

6 комментариев

А теперь неспеша всё отрефакторим

Несколько дней назад я рассказывал о своём бессоном хакатоне, — ухаживая за дочкой во время её болезни, я особо не спал, а чтобы не умереть от безделья, придумал себе задачу — за ночь изобрести и реализовать способ вытащить из эмулятора спектрумовский электронный журнал «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-Ревю». Результат захостил на «Гитхабе», можно посмотреть что получилось.

Вся навигация — курсором мыши, на пункты меню можно щёлкать, текст прокручивается (полосы прокрутки не видно, так как в оригинале её тоже нет).

Совершенно без сил отправляюсь почивать.

Оставить комментарий

Бессонный хакатон

Думаю для кого-то это будет неожиданностью, но я ни разу в жизни не участвовал в хакатонах. Меня приглашали посудить парочку, но я сейчас про участие в качестве программиста. Странно, но несмотря на недавнюю бешеную популярность хакатонов, как-то не довелось.

А тут дочка после ветрянки подхватила какое-то ОРЗ с высокой температурой и у меня выдались две бессонные ночи — жена на конференции, мы с дочкой вдвоём. Из-за температуры она плохо спала, а я просыпался, потому что она меня часто будила.

В итоге, было проще не спать, чем спать вот так урывками, но просто так без сна листать ленты социальных сетей скучно, и я надумал устроить себе хакатон.

Условия — до рассвета решить какую-нибудь сложную задачу и чем бесполезнее, тем лучше. Я очень люблю бесполезные задачи на досуге, — полезных мне на работе хватает.

В момент этого решения я как раз долистал в какой-то из лент до ностальгических воспоминаний о «Спектруме» и вспомнил как набрёл когда-то на архив журналов «ZX-Ревю» и «ZX-Форум». Когда-то в детстве, когда у меня у самого был «Спектрум», я их выписывал.

Некоторые из них я тогда не смог посмотреть, так как они были в виде программ для «Спектрума» — издательство пыталось перейти на более прогрессивный формат электронного журнала.

Так я и определился с целью: выковырять содержимое этих журналов наружу, чтобы их можно было смотреть на моём ноутбуке не только в эмуляторе.

Обложка журнала ZX-РЕВЮ №11-12 за 1997 год с перечнем статей: описание оболочки, путеводитель, новые программы, форум, советы экспертов и каталог.
Обложка электронного журнала «ZX-Ревю» №№ 11, 12 за 1997-й год

Для начала я поискал чем можно расковырять формат, в котором распространяется журнал. Утилита нашлась быстро и я даже поправил в ней небольшой баг — она не собиралась под «МакОС».

Изучение расковырянного содержимого принесло одно расстройство — внутри оказалось бинарное представление программы на Бейсике и несколько файлов неизвестного формата. Судя по тому как отображается журнал в эмуляторе, формат не текстовый. Вряд ли я бы справился с его обратным инжинирингом за ночь.

Поэтому я решил попробовать другую идею — сделать автоматическую скриншотилку, которая сдирала бы содержимое экрана эмулятора и собирала бы из него длинный графический файл. Так как у журнала двухуровневое меню, файлов должно образоваться несколько, а всю интерактивщину я решить сделать в обычном HTML и CSS.

В итоге получился набор инструментов из двух файлов на смеси bash и PHP.

Первый, make_screenshots скриншотит эмулятор, вырезает оттуда экран «Спектрума», жмёт на кнопку «вниз», заботится о том, чтобы экраны не дублировались и понимает когда больше не удаётся пролистнуть. Многие вещи там захардкожены — например, задержки, положение экрана эмулятора и кнопки на виртуальной клавиатуре.

Второй, convert_to_jpegs превращает полученный набор скриншотов в файлы JPEG. Во множественном числе, потому что оказалось, что максимальная высота файла JPEG — 65535 пикселей. В такую высоту у меня всё не влезало, поэтому пришлось разбивать получающуюся простыню два файла. Вообще, можно было бы использовать PNG, там такого ограничения нет, но он в таком размере очень сильно нагружает браузер.

Потом я эти скриншоты уже руками обверстал в HTML и планирую результат тоже положить на «Гитхаб», как только у меня закончится процесс оптимизации JPEG. Он очень долгий, но овчинка выделки стоит — около 20—25% экономии, а файлы там большие — по несколько мегабайт.

Как будет готово, напишу про это отдельный небольшой пост.

6 комментариев