Palm (Pilot)
Клуб пользователей, Санкт-Петеpбуpг
 ENLiGHT Project. Новости об информационных технологиях, науке, авиации и космонавтике

ENLiGHT Project

оглавление | новости от ib/news | другие проекты | www.palmgear.com | письмо | win koi lat

Разработка приложений для PalmOS.
Русификатор CyrHack
(Тимур Ташпулатов, 23 января 1999)

 

Вступление.

TymHack ;-)
Тимур Ташпулатов

Петр Соболев попросил меня написать статью о программировании для

PalmPilot на примере CyrHack. Мне и самому было бы интересно рассказать об этом - глядишь, полку приложений для пилота прибудет :)

Почему именно CyrHack? Хотелось, с одной стороны, попробовать написать что-нибудь для PalmPilot; с другой - плохо представлялось, что именно. На тот момент (август-сентябрь 98) в конференции пользователей КПК шло активное обсуждение единственного программного продукта, выполнявшего локализацию пилота - PiLoc фирмы Парагон. При всех достоинствах PiLoc были у него и недостатки - например, в распространявшейся бесплатно версии PiLoc 2.0 присутствовала ошибка, благодаря которой активная работа аппаратными кнопками приводила к зависанию устройства с потерей всего содержимого ОЗУ (превращала Pilot в "калькулятор за $400"). Кроме того, раздражала позиция Парагона по отношению к поддержке своего продукта - несмотря на многочисленные жалобы пользователей PiLoc, фирма хранила молчание относительно ошибки и своих планов по ее устранению. Мол, мы монополисты - куда ж вы от нас денетесь.

Мне известны еще несколько проектов по локализации PalmOS - это CyRus (shareware; автор Сергей Меньшиков) и Russian III (sourceware; автор Константин Кляцкин). Именно Константину CyrHack обязан некоторыми важными решениями. Недавно появился еще один - PaPiRus от фирмы ФизТехСофт. Больше русификаторов, хороших и разных!

 

С точки зpения пpогpаммиста..

..PalmPilot мало чем отличается от обычного компьютера. Процессор, совместимый по системе команд с M68000, память (от 128К до 2М, с возможностью расширения до 16М), устройства ввода/вывода (ЖК-дисплей разрешением 160х160 с сенсорной панелью, несколько кнопок и последовательный порт) - что еще нужно, чтобы достойно встретить старость?

Средств для создания программ под PalmOS более чем достаточно - от бесплатных GNU SDK и ASDK до крутых коммерческих вроде CodeWarrior от Metrowerks. Существуют даже средства разработки прямо на пилоте (например, Pocket C, пара бейсиков, Forth и даже LISP). Писать можно на чем угодно - от ассемблера до C и Java. На http://www.palm.com можно бесплатно разжиться SDK 3.0, содержащим документацию по PalmOS 3.0, неплохой эмулятор PalmPilot для Windows 95 POSE (сделан на основе известного эмулятора CoPilot) и образ ROM для него. Существуют также эмуляторы для MacOS и X-Windows (xcopilot).

Мне оказалось удобней писать на ассемблере. Во-первых, код получается достаточно компактный; во-вторых, компилятор PILA входит в поставку бесплатного ASDK (альтернативный SDK), доступен в исходных текстах и легко собирается практически под любую платформу.

Более подробно о средствах разработки можно узнать на www.massena.com

 

Хаки

Хак (hack, patch) - небольшая программа (или кусочек кода), изменяющий или расширяющий функциональность ОС. Как правило, хак подменяет один или несколько системных вызовов, а также может выполнять некоторые не вполне стандартные с точки зрения системы действия, вроде прямого доступа к глобальным переменным системы или регистрам микропроцессора (какое кощунство!).

CyrHack
CyrHack 'about' window

Чтобы несколько хаков, перехватывающих одни и те же вызовы (systraps), не подрались между собой (и не привели систему в состояние перманентного изумления), в 1996 году Edward Keyes предложил элегантный способ их организации - путем введения специального API по установке/удалению хаков, и разработал замечательную программу HackMaster. Благодаря HackMaster разработчику хака больше не нужно отвлекаться на обвязку своего кода, зачастую состоящего из двух-трех десятков команд, процедурами установки и удаления хака, отработки системных событий и тому подобной шелухой - все это берет на себя HackMaster.

На страничке, посвященной HackMaster, можно также найти статью о том, как самостоятельно написать хак. Статья настолько живая и интересная, что решение о том, что альтернативный русификатор для пилота должен быть выполнен в виде хака, окрепло окончательно.

Разумеется, реализация программы в виде хака имеет и свои недостатки. Первый - хаку нужен HackMaster; хотя он и является условно-бесплатным (и без ограничений), все же есть люди, которых необходимость ставить на пилот HackMaster несколько напрягает. Впрочем, пилотер, у которого нет в системе ни одного хака, на мой взгляд, не настоящий пилотер :)

Второй недостаток, весьма чувствительный - хаку недоступны глобальные переменные. Есть и третий - если хак подменяет некоторые системные значения своими, то в случае деактивации хака отыграть их обратно нет возможности (В HackMaster версии 0.9 не была реализована так называемая Custom Install Procedure; Ed Keyes обещал реализовать ее в версии 2.0, которая сейчас находится в стадии бета-тестирования, но, насколько мне известно, так и не выполнил обещания). Наконец, хаку недоступна возможность выполнить какие-либо действия сразу после сброса системы. Наверняка есть и еще недостатки.

 

О гpаффити

В CyrHack не была реализована собственная граффити, поскольку в PalmOS, предоставляющей программисту [относительно] удобный и [достаточно] хорошо сгруппированный набор вызовов API по работе с памятью, вводом-выводом, пользовательским интерфейсом и т.п., не были предусмотрены средства по модификации, расширению или полной замене Graffiti Manager. В принципе, ничего сложного в перехвате всех вызовов Graffiti Manager и подмене их своим кодом нет, но вот реализация собственного движка (engine) по распознаванию рукописного ввода - задача не из простых (и недешевых ;)). Поэтому представляется разумным подход "малой кровью", использованный в PiLoc, Russian III и CyrHack - использование сходных по начертанию штрихов для ввода кириллицы.

 

Как это работает

Условно функции, выполняемые CyrHackом, можно разделить на три группы
    - вывод кириллицы (шрифты)
    - ввод кириллицы (Граффити, экранная клавиатура)
    - сервисные (сортировка, установка значений по включению питания и проч.)

ВЫВОД

Замена встроенных шрифтов PalmOS на русифицированные осуществляется модификацией таблицы шрифтов. Делается это не от хорошей жизни - хотя в API имеется вызов FntGetFontPtr, который, казалось бы, позволяет обойтись без хирургического вмешательства в содержимое означенной таблицы, но практически ни один из вызовов Font Manager им не пользуется - все лезут напрямую в память.

(Механизм работы со шрифтами несколько изменился в PalmOS версии 3.0, в частности, появилась возможность регистрировать в системе свои шрифты (Custom fonts) с возможностью их последующего использования; в CyrHack не используется)

Собственно модификация таблицы шрифтов производится в перехватываемом вызове FntSetFont в рамках инициализации структур хака (начальные значения переключателей, указатели на таблицы перекодировок и тому подобное):

pSys    equ     'psys'
        systrap FtrGet(#pSys.l, #1.w, &osVersion(a6).l)
        move.l  osVersion(a6), Features.osversion(a3)
        cmp.b   #03, osVersion(a6)
        beq.s   PalmOS3
PalmOS12
        lea     Fonts(pc), a0
        move.l  a0, $01d2.l
        adda.l  #2694.l, a0
        move.l  a0, $01d6.l
        adda.l  #3002.l, a0
        move.l  a0, $01da.l
        adda.l  #3598+3782.l, a0
        move.l  a0, $01de.l
        bra.s   PalmOS99
PalmOS3
        systrap MemPtrNew(#32.l)
        move.l  a0,Features.fontptr(a3)
        move.l  a0,a2
        systrap MemPtrSetOwner(a2.l, #0.l)
        movea.l $01da.l, a0
        systrap MemMove(a2.l, a0.l, #32.l)
        lea     Fonts(pc), a0
        move.l  a0, (a2)
        adda.l  #2694.l, a0
        move.l  a0, 4(a2)
        adda.l  #3002.l, a0
        move.l  a0, 8(a2)
        adda.l  #3598.l, a0
        move.l  a0, 28(a2)
        adda.l  #3782.l, a0
        move.l  a0,12(a2)
        move.l  a2, $01da.l
PalmOS99

Для поддержки в CyrHack различных кодировок кириллицы был выбран довольно панковский подход. Самое простое решение - добавление в него шрифтов в соответствующих кодировках - сильно увеличило бы размер хака (в среднем на 10К для каждой кодировки), что не есть хорошо в условиях вечного дефицита памяти, особенно на устройствах с 512К и менее. Было решено перехватить вызов WinDrawChars и перекодировать выводимые на экран символы в соответствии с таблицами (128 байт на кодировку). Разумеется, при этом вывод на экран существенно замедляется, но, во-первых, он и так притормаживается вызовами Feature Manager, а во-вторых, мы же панки!

; 0 - 1251, 1 - koi8r, 2 - 866, 3 - Mac, etc
        tst.l           Features.encoding(a4)
        beq.s   Go
        movem.l d0-d2/a0-a3, -(a7)

; T r a n s l a t i o n
        move.l  Features.chrtable(a4), a1
        move.l  Features.encoding(a4), d0
        subi.l  #1, d0
        asl.l           #7, d0
        adda.l  d0, a1
        moveq   #0, d0
; счетчик цикла
        move.w  wLen(a6), d0
        move.l  Features.buffer(a4),a2
        move.l  pChar(a6),a3
; адрес процедуры трансляции
        move.l  Features.translation(a4),a0
        jsr     (a0)
; e n d

        movem.l (a7)+,d0-d2/a0-a3
; подмена исходной строки оттранслированной
        move.l  Features.buffer(a4),pbuf(a6)
Go
        move.w  bY(a6), -(a7)
        move.w  bX(a6), -(a7)
        move.w  wLen(a6), -(a7)
        move.l  pbuf(a6), -(a7)

        move.l  pfnOldProc(a6), a0
        jsr (a0)

Но шрифты в PalmOS пропорциональные - отсюда вытекает необходимость перехватывать еще и несколько вызовов Font Manager, в частности, FntCharsWidth, FntLineWidth и FntCharWidth. Судя по тому, что результат довольно коряв, стопроцентного успеха пока достичь не удается :-)

ВВОД

Что касается экранной клавиатуры, то здесь все довольно просто. Перехватывается DmGetResource и его параметры модифицируются в зависимости от текущей кодировки таким образом, что при отрисовке клавиатуры вызовом SysKeyboardDialog используются ресурсы в соответствующей кодировке:

        cmpi.l  #tkbd, type(a6)
        bne.s   No
        cmpi.w  #$2710, id(a6)
        bne.s   No

; start fiddling
        move.l  Features.encoding(a0), d0
        add.w   d0, id(a6)
No
        movem.l (a7)+, d0-d2/a0-a2
        move.w  id(a6), -(a7)
        move.l  type(a6), -(a7)

        move.l  pfnOldProc(a6), a0
        jsr (a0)

С Граффити дело обстоит немного сложнее. Перехват EvtEnqueueKey - очевидный способ для модификации кодов, помещаемых в Key Queue - несовместим с "тяжелыми" вызовами, вроде вызовов Feature Manager. Поэтому было решено перехватывать SysHandleEvent (весьма удобное место для полета фантазии, например, для переключения языка щелчком по индикатору RU/EN):

        movea.l event(a6), a0
        move.w  EventType.eType(a0), d0

; check if it is key event
        cmp.w   #keyDownEvent, d0
        bne     PenDown
        move.w  EventType.data + keyDown.chr(a3), d3

Toggle
        cmp.w   #161, d3         ; Ext.shift + |
        bne.s   Switch
        move.l  Features.ruslat(a2), d0
        eori.l  #1, d0
        move.l  d0, Features.ruslat(a2)
        lea.l   DrawIndicator(pc), a0
        jsr     (a0)
        moveq   #0,d0
        bra     Change

; cycle encodings
Switch
        cmp.w   #165, d3         ; Ext.shift + Y
        bne     Go
        addi.l  #1, Features.encoding(a2)
        cmpi.l  #4, Features.encoding(a2)
        blt.s   Switch1
        move.l  #0,Features.encoding(a2)
Switch1
        systrap FrmGetActiveFormID()
        systrap FrmUpdateForm(d0.w, #frmRedrawUpdateCode.w)

Русское Граффити сделано аналогично PiLoc - так привычней и проще. Введенные с помощью Граффити коды перекодируются с помощью простой таблицы (64 байт на кодировку).

; remap stroke if RU is active
        cmp.l   #1, Features.ruslat(a2)          ; 0 - EN, 1 - RU
        bne.s   Done0

Remap
        move.l  Features.grftable(a2), a0
        move.w  d3, d0
        subi.b  #'A', d0
        add.l   d0, a0
        move.b  (a0), d0
Change
        move.w  d0, EventType.data + keyDown.chr(a3)

GSI (Graffiti Shift Indicator) сделан с помощью двух шрифтов, различающихся только значками RU/Ru/ru и EN/En/en. Не совсем здорово в смысле объема (лишних ~600 байт), но зато прикольно. Шрифты просто переключаются в зависимости от активной раскладки (снова патчится многострадальная таблица шрифтов).

proc DrawIndicator()
beginproc
        move.l  d0,-(a7)
; patch GSI font entry
        cmp.b   #03, Features.osversion(a2)
        beq.s   PalmOS03
PalmOS12
        move.l  Features.gsien(a2), $01de.l
        tst.l   Features.ruslat(a2)
        beq.s   DrawGSI
        add.l   #334,$01de.l
        bra.s   DrawGSI
PalmOS03
        move.l  Features.fontptr(a2), a0
        move.l  Features.gsien(a2), 12(a0)
        tst.l   Features.ruslat(a2)
        beq.s   DrawGSI
        add.l   #334,12(a0)
DrawGSI
        systrap GsiEnabled()
        systrap GsiEnable(d0.b)
        move.l  (a7)+, d0
endproc

Переключение раскладки щелчком по индикатору EN/RU (красивая идея, правда?) сделано довольно просто. Так как ранее мы уже начали заигрывать с вызовом SysHandleEvent, то не составит труда получить (и использовать раньше других) координаты пера - достаточно обработать penDownEvent. Дальше остается только определить, не произошло ли опускание пера на область, занятую индикатором (ради этого пришлось перехватить вызов GsiSetLocation, потому что в PalmOS (сюрприз!) нет законного способа узнать координаты индикатора на экране. Попутно выяснилось, что в документации display- и window-relative координаты перепутаны. Поубивал бы, ей-богу).

PenDown
        move.l  #0, handled(a6)
; check if it is penDown event
        cmp.w   #penDownEvent, d0
        bne     Done
; get window coordinates of pen
        moveq   #0, d0
        move.w  EventType.screenX(a0), d0        ; x
        cmp.w   Features.gsix(a2), d0
        bmi     PenDown0
        sub.w   Features.gsix(a2), d0
        cmpi.w  #10, d0
        bgt     PenDown0

        move.w  EventType.screenY(a0), d0        ; y
        cmp.w   Features.gsiy(a2), d0
        bmi     PenDown0
        sub.w   Features.gsiy(a2), d0
        cmpi.w  #8, d0
        bgt     PenDown0
; if GSI is not enabled then we just skip along
        systrap GsiEnabled()
        tst.b   d0
        beq.s   PenDown0
        move.l  #1, handled(a6)
; reverse ruslat
        move.l  Features.ruslat(a2), d0
        eori.l  #1, d0
        move.l  d0, Features.ruslat(a2)

        lea.l   DrawIndicator(pc), a0
        jsr     (a0)

; когда-нибудь дойдут руки приделать звуковую индикацию :)
;       systrap SndPlaySystemSound(#1.b)                

PenDown0

Вот с чем пришлось повозиться, так это с Commands и Shortcuts. Похоже, в PalmOS опять-таки не существует нормального способа определить, находится ли система в настоящий момент в состоянии приема Command (во всяком случае, эксперименты с атрибутами меню, перехватом MenuEraseStatus и шаманским бубном результата не дали), и способ борьбы был позаимствован из исходных текстов RussianIII (использование значений TimGetSeconds). С шорткатами тоже пришлось прогнуться - помимо перехвата GrfGetAndExpandMacro - чтобы английское "Dinner", введенное с помощью шортката при активной русской раскладке, не превращалось в загадочное "Диннея".

СЕРВИС

Нормальная сортировка кириллицы достигается перехватом GetCharSortValue и GetCharCaselessValue и подменой "родных" таблиц сортировки "правильными":

proc    MyGetCharCaselessValue()
beginproc
        lea.l   SortTable(pc), a0
endproc

Панель управления хака сделана больше из любопытства, чем из необходимости :-) все, что требуется от хакописателя - это обеспечить наличие в хаке формы, представляющей панель на экране, и обработчик событий к ней; об остальном позаботится HackMaster.

Установка Power On потребовала перехвата вызова HwrLCDWake - в качестве образца использовались исходные тексты LogoHack:

; check Power mode selector
        cmp.l   #2, Features.powermode(a2)
        beq.s   Go
Lat
        move.l  Features.powermode(a2), Features.ruslat(a2)
Go

 

Заключение.

Как видно, программировать под PalmOS нетрудно. Обширный инструментарий, относительно подробная документация и большой выбор ресурсов Интернет, посвященных разработке приложений для Pilot, делают это занятие почти что развлечением.

Разумеется, PalmOS не лишена недостатков (а порой и просто маразмов). Например, в API отсутствует возможность определить, засвечена ли точка на экране; многие моменты неясно или просто неверно отражены в документации (вспомним путаницу с экранными и оконными координатами). Зато имеются зачатки здравого смысла, вроде FntGetFontPtr:

link      a6, #0
movea.l   $01d6.w, a0
unlk      a6
rts

Всего-то ничего, но по каким-то соображениям разработчики постеснялись вставить вызовы FntGetFontPtr в остальные функции Font Manager и вместо этого старательно лезут напрямую в память, - остается только развести руками.

К чести 3Com надо отметить, что обращение в их службу поддержки не остается без ответа. Правда, ответ этот может воспоследовать через несколько недель (сразу выдается только специальный квиток-подтверждение о том, что-де "ваш номер в очереди 78675645"). Большим подспорьем является лист рассылки Palm Developers (palm-dev-forum@3com.com), где тусуются зубры из Palm Computing, 3Com и массы мелких компаний-разработчиков софта под PalmOS.

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

--

Если вы хотите дополнить FAQ - пожалуйста пишите. Ваши вопросы и ответы по e-mail могут быть помещены в форум или опубликованы в FAQ.

design/collection/some content by Frog,
PALM FAQ (C) Frog 2000-2003,
"PALM FAQ, http://www.enlight.ru/pilot/".