Разработка
приложений для PalmOS.
Вступление.
Петр Соболев попросил меня написать статью о программировании для 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) - небольшая программа (или кусочек кода), изменяющий или расширяющий функциональность ОС. Как правило, хак подменяет один или несколько системных вызовов, а также может выполнять некоторые не вполне стандартные с точки зрения системы действия, вроде прямого доступа к глобальным переменным системы или регистрам микропроцессора (какое кощунство!).
Чтобы несколько хаков, перехватывающих одни и те же вызовы (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, то после релиза я выдам исходные тексты на всеобщее растерзание и начну делать очередную версию в виде нормального приложения, а не хака. -- |
||||