Программирование для Palm OS: ставим CodeWarrior и оживляем ТСД

Приветствую всех!
Немало ушедших в историю платформ успели мы повидать. И столько же ещё будет у нас впереди. Но сейчас поговорим не о чём-то реликтовом и экзотическом, а о КПК, что наверняка успели застать многие пользователи данного ресурса. Эти девайсы имели массу поклонников, не удивлюсь, если кто-то до сих пор пользуется таким. Тем интереснее будет поговорить о разработке софта для данной ОС.

20yzzilxlvz5mci3o_0-ncfhyvu.jpeg

Итак, в сегодняшней статье рассмотрим разработку софта для КПК под управлением легендарной Palm OS. Узнаем, где взять весь нужный софт, как собрать свою программу. Поговорим о некоторых аспектах программирования для этих железок. Традиционно будет много интересного.

Суть такова


Давным-давно, ещё пять лет назад, известный в узких кругах товарищ dlinyj выкатил пост «Программирование для Palm в 2017 году». Там описывалось, где взять необходимое ПО, как его установить, как запустить свою программу в эмуляторе. И так вышло, что эта статья спустя два года после того, как я её прочитал, вдохновила меня на то, чтобы с головой окунуться в мир разработки под старое железо и всего того, что с этим связано. Но вот именно под Palm я так ничего и не написал, и это не давало мне покоя. А раз так — самое время попробовать. А заодно и выяснить, что примечательного нас будет ждать на этом пути.

Сложно ли писать для Palm OS?


На просторах я встречал немало комментариев о Palm OS с точки зрения разработчика. Кому-то писать под неё было одним удовольствием, кто-то называл её худшей платформой, с которой доводилось сталкиваться.

И, признаться, оба этих мнения небезосновательны. С одной стороны, после других тогдашних органайзеров, имевших свой SDK (Casio PV, Franklin eBookMan), где надо было заниматься ерундой типа ручного опроса сенсора, хранения элементов интерфейса в виде битмапов, непривычной даже для тогдашнего разработчика работой с данными, Palm OS давал куда больше возможностей. Да и сами средства разработки тоже были на высоте, один лишь эмулятор, полностью воспроизводивший работу реального КПК и даже использовавший оригинальную прошивку, не мог не радовать. У того же Pocket PC вполне можно было добиться ситуации, когда программа идеально работает в эмуляторе, но при этом отправляет «железный» КПК в глубокую задумчивость или просто вылетает сразу после запуска.

С другой же стороны, тот же Psion на базе EPOC16, обладающий в разы более скромными характеристиками, предоставлял такие возможности, которых в пальме не было до конца её существования (а ещё каждый Psion оснащался средой разработки на языке OPL, что давало возможность написать полноценное приложение даже вдали от компьютера), например, в нём уже тогда была нормальная файловая система, модули памяти, очень многое для работы со всякой периферией, да и порог вхождения в разработку под него был куда ниже: написать консольное приложение под EPOC16 было немногим сложнее, чем под DOS. Пальма всегда имела свои отличительные особенности вроде бестолкового управления ресурсами, отсутствия файловой системы (вместо которой была своеобразная база данных) и многого другого. К тому же тем временем КПК на базе Windows CE стали стоить уже не таких конских денег, отчего многие начали предпочитать при выборе КПК жертвовать автономностью в угоду производительности. После всего этого по сложности разработки с Palm OS смог конкурировать разве что Symbian, вообще не оставлявший шансов на быстрый старт для разработчика «с улицы».

Впрочем, что бы то ни было, для многих из нас пальмы стали настоящей легендой КПК-строения. Их любили за компактность, красивый и эргономичный дизайн, обилие софта, удобный интерфейс и по нынешним меркам фантастическое время автономной работы. Эти девайсы использовались как органайзеры, электронные книги, инженерные калькуляторы, терминалы и много как ещё. А раз так — погнали!

Что у нас есть?


Ну и перед началом экспериментов разберёмся с тем, какими возможностями обладают КПК на базе Palm OS.

Типичная пальма работала на процессоре MC68000 (точнее, Motorola DragonBall, встраиваемой системе на базе этого процессора) с частотой в районе шестнадцати мегагерц, имела ОЗУ объёмом от четырёх до шестнадцати мегабайт (совсем старые модели вроде PalmPilot имели ещё меньше), поделённое на динамическую память и RAM-диск, монохромный ЖКИ разрешением 160×160. Из интерфейсов — RS-232 и инфракрасный порт.

Поздние версии (PalmOne) работали уже на ARM и обладали куда более жирными характеристиками, но там тоже были свои особенности вроде эмуляции архитектуры MC68000 для запуска приложений, что обеспечивало хорошую совместимость, но сильно теряло в производительности. В данной статье поговорим преимущественно о «традиционных» пальмах.

Обзор оборудования


Разумеется, КПК на базе данной ОС в моих руках оказалось немало.

pzmkcfpxgpi736h76cygj6fle2e.jpeg

Palm III. Модель достаточно древняя, ещё под маркой 3Com. Первый девайс из линейки Palm (не Pilot и не PalmPilot). На борту четыре мегабайта памяти и Palm OS третьей версии.

wgat33dur6ipmx8xbrvykkeo8yq.jpeg

Palm VX. Как по мне, настоящий шедевр дизайна, его габаритам позавидуют даже некоторые нынешние телефоны. Памяти стало в два раза больше, версия ОС теперь 3.5.

pbo8vtd3bbu8vnhdsrqmbjjpbe0.jpeg

Palm M105. На тот момент позиционировался как «молодёжный» девайс. Впрочем, из-за его надёжности, простоты и не слишком высокой цены нравился он многим.

kz0e4pi0fwankvpclegympnctea.jpeg

Palm Tungsten E2. Этот агрегат сильно отличается от предыдущих — работает он на процессоре ARM с частотой двести мегагерц, имеет тридцать два мегабайта памяти, которая наконец-то стала энергонезависимой. Хотя девайс считался бюджетным, обладал он весьма неплохими характеристиками.

-jgot1xx4-1dqkmvtgoccznfjye.jpeg

Sony Clié PEG-NR70V/U. Та самая знаменитая раскладушка на базе Palm OS. Про аналогичную модель NX70V товарищем f15 даже был сделан отдельный обзор. КПК имеет крайне впечатляющий дизайн и до сих пор смотрится очень стильно.

Не КПК едиными


Помимо «пользовательских» девайсов, на базе Palm OS выпускались и околопромышленные.

wcmaby1lwcmfl2d8viabqhn1ak0.png

Наибольший вклад в производство промышленных терминалов на базе Palm OS стала небезызвестная Symbol (позже Motorola, позже Zebra). Вот, например, SPT-1500. Аппарат представляет собой практически полную копию Palm III и полностью совместим с ним по софту и аксессуарам. Из нововведений — сканер штрих-кодов, а также возможность зарядки аккумуляторов прямо внутри устройства (для этого необходимо вставить фирменную батарею и 
поменять крышку на таковую с двумя отверстиями для контактов, после чего установить аппарат на зарядную подставку).

j2jp05qphwufkygrwqdimxe5e4m.jpeg

Symbol SPT-1550. По виду он очень похож на предыдущий, но всё же сильно отличается от него. Процессор на этот раз имеет частоту 33 МГц, памяти тоже больше — восемь мегабайт. Более новой стала и версия Palm OS — 4.0. Этот экземпляр также имеет один серьёзный недостаток, о котором будет сказано чуть позже.

24tumbevvaltwg9bjgs4xpr2vuu.jpeg

Symbol SPT-1800. Именно этот аппарат изображён на КДПВ в той статье товарища dlinyj. По сути это защищённая версия предыдущей модели. Есть также и некоторые другие навороты, например, в некоторых моделях поддерживается работа с проприетарной беспроводной сетью Symbol Spectrum24. Увы, мой девайс не имеет подставки, что сильно ограничивает возможности его использования.

Возвращаем к жизни SPT-1550


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

kw45vdkn21ws2nm0qczc0bzlq-k.jpeg

Для начала разбираем аппарат и снимаем экран.

g0wksajybxo3gdo4209fhrvxi5q.jpeg

А вот и он.

dwxtkdm_8ll6hhm6eamhfwxipkg.jpeg

Обратная сторона. Прозваниваем мультиметром контакты тачскрина. Даже на самом верхнем пределе он показывал обрыв. Это значит, что сенсор умер и даже пропайка его контактов не поможет.

kx6ir4mfkhka_firyuukpe1ku_c.jpeg

Для проверки решил подкинуть экран от Palm III. КПК внутри очень идентичны, конечно, не так, что аж боишься перепутать, какая запчасть от какого, но общие черты явно прослеживаются.

rre5qmygzvwmdrpoe88uo-mn2i8.jpeg

Экраны вместе: злополучный от SPT-1550 и от Palm III.

ziavpqqjrsntrijz8cth2w4jklq.jpeg

Экран от Palm III без фольги. Трассировка платы здесь несколько иная, тем не менее, электрически они полностью совместимы.

vksonl0xajnojsi96g4xbsq6f9e.jpeg

Меняем экраны местами и собираем. Работает. Удивительно, но ионистор у Palm III необъяснимым образом умудрился сохранить свой заряд, и КПК даже показал рабочий стол, а не стандартное приветствие после перезагрузки.

В таком виде КПК прожил у меня примерно месяц. Но Palm III при этом был дохляком, и это не давало мне покоя. Поэтому было решено прибегнуть к крайним мерам. Итак, разбираем аппараты и вновь достаём померший дисплей. На место стыка шлейфа и проводящего стекла тачскрина кладём кусок термобумаги (или аналогичную тонкую бумажку) и с нажимом проводим горячим паяльником. Герметик, которым залито это место, достаточно термостойкий, так что, в отличие от шлейфов ЖК-дисплеев, если делать эту процедуру без фанатизма, то можно не бояться перегреть. Подкидываем экран, вставляем батарейки, замыкаем контакты кнопки питания. Уже лучше: экран реагирует, но только по вертикали: все нажатия смещены к левой стороне. Снова отсоединяем дисплейный модуль и повторяем прожарку. Проверяем — работает, однако! Не очень точно, но всё же. Собираем всё в кучу, ждём, пока ионистор не выдохнется, и пробуем включать. Запуск, калибровка экрана, и… аппарат полностью исправен! Не бит, не разбирался, пользовалась девушка! У меня были опасения насчёт того, насколько долговечным окажется такой ремонт, но даже спустя год аппарат всё так же запускается и продолжает радовать меня своей успешной работой.

Именно на нём мы и будем сегодня тестировать наши проги.

На чём пишут на Palm OS?


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

  • BASIC. Есть такой софт как NSBASIC IDE for Palm. Автор её заверяет, что это самый простой способ что-то написать для пальмы. Для работы приложений также необходимо установить Runtime, который поставляется вместе с программой.
  • C. Ну, это ясно, что такое. Тут есть несколько вариантов (в зависимости от ОС и ваших предпочтений).

Ставим софт


Статья про то, как программировать для пальмы под Linux, уже была. И даже ссылки в ней за пять лет умудрились не протухнуть, что не может не радовать. Так что тут рассмотрим, как обстоят дела под Windows. Путей тут несколько:

  • Palm OS Developer Suite. Это IDE на базе Eclipse, официально поставлявшаяся разработчиками Palm OS. Также надо будет скачать соответствующий SDK. К слову говоря, эта IDE использует cygwin для запуска компилятора, то есть внутри у неё много общего со средствами разработки под Linux.
  • Виртуальная машина с Linux. Как нетрудно догадаться, дальше надо будет действовать как в той легендарной статье.
  • CodeWarrior. Это весьма навороченная IDE от Metrowerks, адаптированная для разработки под эти КПК. Вообще, данная компания хорошо известна в узких кругах именно как производитель средств разработки для различных архитектур.


Самым удобным из них видится последний, именно его мы и рассмотрим.
Компания Metrowerks давно ушла в историю, будучи выкупленной Motorola, позже её остатки отошли Freescale, а затем и NXP, которая до сих пор продаёт некоторые версии данного ПО. Поддержка CodeWarrior for Palm была прекращена многие годы назад, так что теперь этот софт можно найти в открытом доступе. Всё необходимое лежит тут, на сайте PalmDB. В отличие от PocketViewer или Psion SIBO, где материалы остались лишь в архивах, сообщество таки смогло создать хороший сайт с софтом для пальм. Из всех файлов, что там есть, нас интересует версия 9.3. Для запуска также понадобится компьютер с Windows NT/2000/XP или виртуальная машина с ней же. Для тех, кому не хочется разбираться со скачиванием установочного ISO, развёртыванием системы, накатыванием софта, там также выложен образ для VMWare с уже установленной CodeWarrior IDE. Есть даже версия для Mac OS, так что обладатели старенького Power Mac тоже могут приобщиться к разработке под данную ОС.

Итак, скачиваем ISOшник, распаковываем его и приступаем к установке.

wolvbfnz7ukpg4y4ma-eitvvwdi.jpeg

В ходе установки выбираем «Custom installation». Тут надо отметить нужные компоненты. Все специфические SDK вроде Handspring или Symbol уже включены в дистрибутив CodeWarrior, достаточно их просто установить.

emyxbcnmnhsskiwjzf0juep3i84.jpeg

После установки (процесс которой не вызывает никаких проблем) отказываемся от обновлений и регистрации, запускаем софтину и создаём новый проект.

y8olgneonylqnhlcgykaod1ceqi.jpeg

Вот тут видно наследие той эпохи — Creator ID, который представляет собой своеобразный идентификатор приложения. Если вы хотели разрабатывать софт на коммерческой основе, вам надо было зарегистрировать его на сайте PalmOS.

c4hcep13yszfwy-mna_jxrd08sc.jpeg

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

v0n6qjryuotsxb4utwqx0rhbjq0.jpeg

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

ojxv-_nfx9uufvjjethgicybcxa.jpeg

Вот и наш проект.

Эмулятор


Как я уже упоминал ранее, здесь используется полноценный эмулятор, который даже использует ROM-ы с настоящих КПК. Для начала работы нужно скачать их всё с того же PalmDB. Далее запускаем эмулятор (на панели меню жмякаем «Palm OS» и там выбираем нужный ROM).

qt3w6ops2lg-sfdufmpssqyths4.jpeg

Всё, можно играться. Жмякнув правой кнопкой мыши, можно выбрать *.prc-Файлы для загрузки.

Пишем первую программу


Итак, попробуем что-то написать. Итак, удаляем из исходника весь текст и пишем там:

#include 

UInt32 PilotMain(UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags)
{
	EventType event;
	char* toLCD = "Hello, Habr!";

	switch (cmd)
	{
		case sysAppLaunchCmdNormalLaunch:
			WinDrawChars(toLCD, StrLen(toLCD), 55, 74);
			do {
			  EvtGetEvent(&event, evtWaitForever);
			  SysHandleEvent(&event);
			} while(event.eType != appStopEvent);
			break;
	}

	return errNone;
}


Далее собираем и запускаем. Если всё было сделано правильно, то у нас откроется эмулятор, в котором будет примерно следующее:

04xvvip-bkfuyew74-x4gw2rhli.jpeg

Работает. Едем дальше.

Ресурсы


Разумеется, на одном тексте далеко не уедешь, поэтому необходимы ресурсы — элементы управления, картинки, формы и прочие части интерфейса. А это значит, что самое время разобраться, как их создавать.

2vltwhbl4czzxgo9e5tz5tdhsd8.jpeg

Соберём и запустим стандартный пример, что предлагается нам сразу при создании проекта. На экране появляется текстовое поле с кнопкой.

tgezfnhawti2fiquvnoconc9isq.jpeg

При нажатии на верхнюю панель вылезает полноценное меню.

Как это работает? Заглянем в соседнюю папку, где обитает файл с расширением *.rcp примерно следующего содержания:

// test_Rsc.rcp
//
// PilRC-format resources for test
//
// Generated by the CodeWarrior for Palm OS V9 application wizard

GENERATEHEADER "test_Rsc.h"
RESETAUTOID 1000

MENU ID MainMenuBar
BEGIN
    PULLDOWN "Edit"
    BEGIN
		MENUITEM "Undo" ID 10000 "U"
		MENUITEM "Cut" ID 10001 "X"
		MENUITEM "Copy"ID 10002 "C"
		MENUITEM "Paste" ID 10003 "P"
		MENUITEM "Select All" ID 10004 "S"
		MENUITEM SEPARATOR ID 10005
		MENUITEM "Keyboard" ID 10006 "K"
		MENUITEM "Graffiti Help" ID 10007 "G"
    END


END	

MENU ID EditOnlyMenuBar
BEGIN
    PULLDOWN "Edit"
    BEGIN
		MENUITEM "Undo" ID 10000 "U"
		MENUITEM "Cut" ID 10001 "X"
		MENUITEM "Copy"ID 10002 "C"
		MENUITEM "Paste" ID 10003 "P"
		MENUITEM "Select All" ID 10004 "S"
		MENUITEM SEPARATOR ID 10005
		MENUITEM "Keyboard" ID 10006 "K"
		MENUITEM "Graffiti Help" ID 10007 "G"
    END
END

ALERT ID RomIncompatibleAlert
    DEFAULTBUTTON 0
    ERROR
BEGIN
    TITLE "System Incompatible"
    MESSAGE "System Version 3.0 or greater is required to run this application."
    BUTTONS "OK"
END

FORM ID MainForm AT (0 0 160 160)
	NOSAVEBEHIND NOFRAME
	MENUID MainMenuBar
BEGIN
	TITLE "test"
    GRAFFITISTATEINDICATOR AT (149 148)
	FIELD ID MainDescriptionField AT (0 16 160 126)
		MULTIPLELINES
		EDITABLE
		UNDERLINED
		MAXCHARS 1024
	BUTTON "Clear Text" ID MainClearTextButton AT (1 147 AUTO 12)
END

ICONFAMILYEX
BEGIN
    BITMAP "icon-lg-1.bmp" BPP 1 
    BITMAP "icon-lg-2.bmp" BPP 2 
    BITMAP "icon-lg-8.bmp" BPP 8 TRANSPARENTINDEX 210 COMPRESS
    BITMAP "icon-lg-1-d144.bmp" BPP 1 DENSITY 2
    BITMAP "icon-lg-2-d144.bmp" BPP 2 DENSITY 2
    BITMAP "icon-lg-8-d144.bmp" BPP 8 TRANSPARENTINDEX 210 COMPRESS DENSITY 2
END

SMALLICONFAMILYEX
BEGIN
    BITMAP "icon-sm-1.bmp"  BPP 1
    BITMAP "icon-sm-2.bmp"  BPP 2
    BITMAP "icon-sm-8.bmp"  BPP 8 TRANSPARENTINDEX 210 COMPRESS
    BITMAP "icon-sm-1-d144.bmp" BPP 1 DENSITY 2
    BITMAP "icon-sm-2-d144.bmp" BPP 2 DENSITY 2
    BITMAP "icon-sm-8-d144.bmp" BPP 8 TRANSPARENTINDEX 210 COMPRESS DENSITY 2 
END


Этот файл и есть описание ресурсов. Кроме него в папке также находится *.h-файл:

/* pilrc generated file.  Do not edit!*/
#define MainClearTextButton 1008
#define MainDescriptionField 1007
#define MainForm 1006
#define RomIncompatibleAlert 1005
#define EditOnlyMenuBar 1003
#define MainMenuBar 1001


В нём лежат ID этих ресурсов.

Рассмотрим теперь саму логику этой программы:

/*
 * test.c
 *
 * main file for test
 *
 * This wizard-generated code is based on code adapted from the
 * stationery files distributed as part of the Palm OS SDK 4.0.
 *
 * Copyright (c) 1999-2000 Palm, Inc. or its subsidiaries.
 * All rights reserved.
 */
 
#include 
#include 

#include "test.h"
#include "test_Rsc.h"

/*********************************************************************
 * Entry Points
 *********************************************************************/

/*********************************************************************
 * Global variables
 *********************************************************************/



/*********************************************************************
 * Internal Constants
 *********************************************************************/

/* Define the minimum OS version we support */
#define ourMinVersion    sysMakeROMVersion(3,0,0,sysROMStageDevelopment,0)
#define kPalmOS20Version sysMakeROMVersion(2,0,0,sysROMStageDevelopment,0)

/*********************************************************************
 * Internal Functions
 *********************************************************************/

/*
 * FUNCTION: GetObjectPtr
 *
 * DESCRIPTION:
 *
 * This routine returns a pointer to an object in the current form.
 *
 * PARAMETERS:
 *
 * formId
 *     id of the form to display
 *
 * RETURNED:
 *     address of object as a void pointer
 */

static void * GetObjectPtr(UInt16 objectID)
{
	FormType * frmP;

	frmP = FrmGetActiveForm();
	return FrmGetObjectPtr(frmP, FrmGetObjectIndex(frmP, objectID));
}

/*
 * FUNCTION: MainFormInit
 *
 * DESCRIPTION: This routine initializes the MainForm form.
 *
 * PARAMETERS:
 *
 * frm
 *     pointer to the MainForm form.
 */

static void MainFormInit(FormType *frmP)
{
	FieldType *field;
	const char *wizardDescription;
	UInt16 fieldIndex;

	fieldIndex = FrmGetObjectIndex(frmP, MainDescriptionField);
	field = (FieldType *)FrmGetObjectPtr(frmP, fieldIndex);
	FrmSetFocus(frmP, fieldIndex);

	wizardDescription =
		"C application\n"
		"Creator Code: STRT\n"
		"\n"
		"Other SDKs:\n"
		;
				
	/* dont stack FldInsert calls, since each one generates a
	 * fldChangedEvent, and multiple uses can overflow the event queue */
	FldInsert(field, wizardDescription, StrLen(wizardDescription));
}

/*
 * FUNCTION: MainFormDoCommand
 *
 * DESCRIPTION: This routine performs the menu command specified.
 *
 * PARAMETERS:
 *
 * command
 *     menu item id
 */

static Boolean MainFormDoCommand(UInt16 command)
{
	Boolean handled = false;

	switch (command)
	{

	}

	return handled;
}

/*
 * FUNCTION: MainFormHandleEvent
 *
 * DESCRIPTION:
 *
 * This routine is the event handler for the "MainForm" of this 
 * application.
 *
 * PARAMETERS:
 *
 * eventP
 *     a pointer to an EventType structure
 *
 * RETURNED:
 *     true if the event was handled and should not be passed to
 *     FrmHandleEvent
 */

static Boolean MainFormHandleEvent(EventType * eventP)
{
	Boolean handled = false;
	FormType * frmP;

	switch (eventP->eType) 
	{
		case menuEvent:
			return MainFormDoCommand(eventP->data.menu.itemID);

		case frmOpenEvent:
			frmP = FrmGetActiveForm();
			FrmDrawForm(frmP);
			MainFormInit(frmP);
			handled = true;
			break;
            
        case frmUpdateEvent:
			/* 
			 * To do any custom drawing here, first call
			 * FrmDrawForm(), then do your drawing, and
			 * then set handled to true. 
			 */
			break;
			
		case ctlSelectEvent:
		{
			if (eventP->data.ctlSelect.controlID == MainClearTextButton)
			{
				/* The "Clear" button was hit. Clear the contents of the field. */
				FieldType * field = (FieldType*)GetObjectPtr(MainDescriptionField);
				if (field)
				{
					FldDelete(field, 0, 0xFFFF);					
					FldDrawField(field);
				}
				break;
			}

			break;
		}
	}
    
	return handled;
}

/*
 * FUNCTION: AppHandleEvent
 *
 * DESCRIPTION: 
 *
 * This routine loads form resources and set the event handler for
 * the form loaded.
 *
 * PARAMETERS:
 *
 * event
 *     a pointer to an EventType structure
 *
 * RETURNED:
 *     true if the event was handled and should not be passed
 *     to a higher level handler.
 */

static Boolean AppHandleEvent(EventType * eventP)
{
	UInt16 formId;
	FormType * frmP;

	if (eventP->eType == frmLoadEvent)
	{
		/* Load the form resource. */
		formId = eventP->data.frmLoad.formID;
		frmP = FrmInitForm(formId);
		FrmSetActiveForm(frmP);

		/* 
		 * Set the event handler for the form.  The handler of the
		 * currently active form is called by FrmHandleEvent each
		 * time is receives an event. 
		 */
		switch (formId)
		{
			case MainForm:
				FrmSetEventHandler(frmP, MainFormHandleEvent);
				break;

		}
		return true;
	}

	return false;
}

/*
 * FUNCTION: AppEventLoop
 *
 * DESCRIPTION: This routine is the event loop for the application.
 */

static void AppEventLoop(void)
{
	UInt16 error;
	EventType event;

	do 
	{
		/* change timeout if you need periodic nilEvents */
		EvtGetEvent(&event, evtWaitForever);

		if (! SysHandleEvent(&event))
		{
			if (! MenuHandleEvent(0, &event, &error))
			{
				if (! AppHandleEvent(&event))
				{
					FrmDispatchEvent(&event);
				}
			}
		}
	} while (event.eType != appStopEvent);
}

/*
 * FUNCTION: AppStart
 *
 * DESCRIPTION:  Get the current application's preferences.
 *
 * RETURNED:
 *     errNone - if nothing went wrong
 */

static Err AppStart(void)
{

	return errNone;
}

/*
 * FUNCTION: AppStop
 *
 * DESCRIPTION: Save the current state of the application.
 */

static void AppStop(void)
{
        
	/* Close all the open forms. */
	FrmCloseAllForms();

}

/*
 * FUNCTION: RomVersionCompatible
 *
 * DESCRIPTION: 
 *
 * This routine checks that a ROM version is meet your minimum 
 * requirement.
 *
 * PARAMETERS:
 *
 * requiredVersion
 *     minimum rom version required
 *     (see sysFtrNumROMVersion in SystemMgr.h for format)
 *
 * launchFlags
 *     flags that indicate if the application UI is initialized
 *     These flags are one of the parameters to your app's PilotMain
 *
 * RETURNED:
 *     error code or zero if ROM version is compatible
 */

static Err RomVersionCompatible(UInt32 requiredVersion, UInt16 launchFlags)
{
	UInt32 romVersion;

	/* See if we're on in minimum required version of the ROM or later. */
	FtrGet(sysFtrCreator, sysFtrNumROMVersion, &romVersion);
	if (romVersion < requiredVersion)
	{
		if ((launchFlags & 
			(sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp)) ==
			(sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp))
		{
			FrmAlert (RomIncompatibleAlert);

			/* Palm OS versions before 2.0 will continuously relaunch this
			 * app unless we switch to another safe one. */
			if (romVersion < kPalmOS20Version)
			{
				AppLaunchWithCommand(
					sysFileCDefaultApp, 
					sysAppLaunchCmdNormalLaunch, NULL);
			}
		}

		return sysErrRomIncompatible;
	}

	return errNone;
}

/*
 * FUNCTION: PilotMain
 *
 * DESCRIPTION: This is the main entry point for the application.
 * 
 * PARAMETERS:
 *
 * cmd
 *     word value specifying the launch code. 
 *
 * cmdPB
 *     pointer to a structure that is associated with the launch code
 *
 * launchFlags
 *     word value providing extra information about the launch.
 *
 * RETURNED:
 *     Result of launch, errNone if all went OK
 */

UInt32 PilotMain(UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags)
{
	Err error;

	error = RomVersionCompatible (ourMinVersion, launchFlags);
	if (error) return (error);

	switch (cmd)
	{
		case sysAppLaunchCmdNormalLaunch:
			error = AppStart();
			if (error) 
				return error;

			/* 
			 * start application by opening the main form
			 * and then entering the main event loop 
			 */
			FrmGotoForm(MainForm);
			AppEventLoop();

			AppStop();
			break;
	}

	return errNone;
}


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

PilRC Designer


Теперь рассмотрим, как вообще создавать ресурсы. Для этого в комплекте с CodeWarrior имеется аж два приложения — Constructor и PilRC Designer. Для примера рассмотрим второй вариант.

j5r8nvom4gbohpf8ynenzy-nwpe.jpeg

Запускаем прогу. Тут необходимо выбрать модель КПК и создать новый проект.

jvb7p22-njf392nx1lchjpl2jwc.jpeg

Создаём новую форму и присваиваем ей какой-нибудь ID. В случае с Constructor можно сгенерировать автоматически готовый заголовочный файл (именно там и был создан файл из примера).

rh5j8sqfxv0lsqmk5m81ch-bx44.jpeg

Вот для примера добавление пункта выпадающего меню. Один из них — всегда Exit, другие можно добавить по своему усмотрению.

mpglpkhtzxdkcpxigzkamdxymlm.jpeg

Попробуем сделать какую-нибудь тестовую прогу. Для чего возьмём и посередине формы шлёпнем кнопку.

oqlnsqa77dn3c-k2l_sfnynxjfo.jpeg

В соседней вкладке можно сразу увидеть содержание файла описания ресурсов.

Итак, сохраняем это всё и полученный файл кидаем в папку проекта. Прописываем ID всех элементов, создаём *.c-файл, где пишем примерно следующее:

#include 
#include 

#include "test.h"
#include "test_Rsc.h"

static void * GetObjectPtr(UInt16 objectID)
{
	FormType * frmP;

	frmP = FrmGetActiveForm();
	return FrmGetObjectPtr(frmP, FrmGetObjectIndex(frmP, objectID));
}


static Boolean MainFormDoCommand(UInt16 command)
{
	Boolean handled = false;

	switch (command)
	{

	}

	return handled;
}

static Boolean MainFormHandleEvent(EventType * eventP)
{
	Boolean handled = false;
	FormType * frmP;

	switch (eventP->eType) 
	{
		case menuEvent:
			return MainFormDoCommand(eventP->data.menu.itemID);

		case frmOpenEvent:
			frmP = FrmGetActiveForm();
			FrmDrawForm(frmP);
			handled = true;
			break;
            
        case frmUpdateEvent:
			
			break;
			
		case ctlSelectEvent:
		break;
		
	}
    
	return handled;
}

static Boolean AppHandleEvent(EventType * eventP)
{
	UInt16 formId;
	FormType * frmP;

	if (eventP->eType == frmLoadEvent)
	{
		/* Load the form resource. */
		formId = eventP->data.frmLoad.formID;
		frmP = FrmInitForm(formId);
		FrmSetActiveForm(frmP);

		switch (formId)
		{
			case MainForm:
				FrmSetEventHandler(frmP, MainFormHandleEvent);
				break;

		}
		return true;
	}

	return false;
}

static void AppEventLoop(void)
{
	UInt16 error;
	EventType event;

	do 
	{
		EvtGetEvent(&event, evtWaitForever);

		if (! SysHandleEvent(&event))
		{
			if (! MenuHandleEvent(0, &event, &error))
			{
				if (! AppHandleEvent(&event))
				{
					FrmDispatchEvent(&event);
				}
			}
		}
	} while (event.eType != appStopEvent);
}


static Err AppStart(void)
{

	return errNone;
}

static void AppStop(void)
{
        
	FrmCloseAllForms();

}

UInt32 PilotMain(UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags)
{
	Err error;

	switch (cmd)
	{
		case sysAppLaunchCmdNormalLaunch:
			error = AppStart();
			if (error) 
				return error;

			FrmGotoForm(MainForm);
			AppEventLoop();

			AppStop();
			break;
	}

	return errNone;
}


f696ynkr_dlnk2cukc2brbz63hc.jpeg

Запускаем. Как можем видеть, форма выводится. Но кнопка, разумеется, не работает, так как никакого действия на неё не назначено. Исправим это недоразумение.
Создадим какой-нибудь диалог, также пропишем его ID, а в функции после case ctlSelectEvent: пропишем следующее:

	if (eventP->data.ctlSelect.controlID == testButton)
	{
		FrmAlert(testAlert);
		break;
		}


Здесь мы проверяем, что именно было выбрано и, если это то, что нам надо, выводим сообщение. Аналогичным образом выполняются действия при открытии формы, при обновлении, а также при выборе какого-либо из пунктов меню.

our-sihnh4s8sxdj4gro1wsopv8.jpeg

Итак, собираем всё это (не забудьте прописать нужные ID, иначе работать не будет). Нажимаем кнопку — получаем alert.

xh_w0w2j3xp-syp6ul0jccplzmg.jpeg

Разумеется, ничто так не радует как тесты на железе. Поэтому берём наш *.prc-файл и добавляем его в список для установки.

ukyo4lhtgd_6aatyxnx4v_xogvw.jpeg

rpntrd5tuhlss8qmgqnxamo_juo.jpeg

hssc6wje2a1zmpq-aopbug90njc.jpeg

Насаживаем КПК на подставку, жмякаем кнопочку на ней, и наша прога заливается в память устройства. Запускаем, пробуем — работает.

Картинки


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

w3vgqgezv2wld8l0g_hmkir6_ik.jpeg

Берём нашу картинку и добавляем новый ресурс — BITMAP. Также можно добавить сразу BITMAP FAMILY — для семейства картинок разной битности, чтобы пользователь с цветным КПК при загрузке нашего приложения не увидел монохромную кашу из пикселей.

opkmn4rlimfzebr3bld8v2fxb2s.jpeg

Далее возвращаемся к нашей форме и добавляем FORMBITMAP, где указываем ID нашей картинки.

ly1zdgz8mbyfihz5itgn3penvnc.jpeg

Всё, можно собирать и пробовать!

RS-232


Одна из самых крутых возможностей пальмы — наличие в ней последовательного порта с уровнями полноценного RS-232. То есть если установить какую-нибудь программу терминала (аналогично Comms на устройствах Psion), то можно управлять какими-то железками по COM-порту.
Итак, разберёмся, как он работает. Для начала порт надо открыть, что мы делаем в основной функции:

UInt16 port;

UInt32 PilotMain(UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags)
{
	Err error;
	error = SrmOpen(serPortCradlePort,115200,&port);
	if (error) 	return error;
	
	switch (cmd)
	{
		case sysAppLaunchCmdNormalLaunch:
			error = AppStart();
			if (error) 
				return error;

			/* 
			 * start application by opening the main form
			 * and then entering the main event loop 
			 */
			FrmGotoForm(MainForm);
			AppEventLoop();
			SrmClose(port);
			AppStop();
			break;
	}

	return errNone;
}


После выхода из обработчика событий порт обязательно надо закрыть. Если этим пренебречь, то при следующем HotSync вы рискуете увидеть примерно следующее:

n5j7ddw10xwsvgsjr-9myvowgkc.jpeg

lnbrw8zd22a1ssz-iy-5vt1y9cs.jpeg

Итак, порт открыли. Для примера я бахнул две кнопки, одна из которых отправляет в порт »1», другая — проверяет, есть ли во входящем буфере данные и выводит соответствующее сообщение. Выполняется всё это, как нетрудно догадаться, в обработчике главной формы:

if (eventP->data.ctlSelect.controlID == sendButton)
	{
		Err error;
		char data = '1';
		SrmSend(port,&data,sizeof(data),&error);
		FrmAlert (sendAlert);
		break;
	}
if (eventP->data.ctlSelect.controlID == receiveButton)
	{
		UInt8 * receivedData;
		UInt32 bytesRead = 0;
		Err error = SrmReceiveWindowOpen(port,&receivedData,&bytesRead);
		if(error == errNone) {
		 SrmReceiveWindowClose(port,bytesRead);
		 }
		if(bytesRead != 0) {
		 FrmAlert (receiveAlert);
		 } 
		else FrmAlert (noDataAlert);
		break;
	}

IMMA CHARGIN MAH LAZER!


Ну и напоследок рассмотрим, как работает API сканера в ТСД Symbol. Благо в комплекте с Symbol SDK есть тестовый проект.

А вот и код этого проекта
/************************************************************************
* COPYRIGHT:   Copyright  ©  1998, 2000 Symbol Technologies, Inc. 
*
* FILE:        Sscan.c
*
* SYSTEM:      Symbol barcode scanner for Symbol Palm Terminals
* 
* MODULE:      A Simple Scan Demo Application for both 1D and 2D Barcodes
*              
* DESCRIPTION: Contains application support functions, including main form event handlers
*              and event loop for the sample application.
*					
* FUNCTIONS:   StartApplication
* 					StopApplication
*					PilotMain
*					MainFormHandleEvent
*					MainFormOnInit
*					MainFormHandleMenu
*					EventLoop
*					ApplicationHandleEvent
*					OnDecoderData
* 					OnAbout
*					AboutHandleEvent
* 					AboutOnInit
*
* HISTORY:     5/25/98    KEF   Created
*			   3/17/99	  CFS   Set bHandled to true so that the default error processing will 
*								not be used.  The app processing is used (line 175).
*			   3/29/00	  MW	Modified for 2D Barcodes.
*              ...
*************************************************************************/
#ifdef __cplusplus
   extern "C" {
#endif

#include "PalmOS.h"				// all the system toolbox headers
#include 

#include "ScanMgrDef.h"				// Scan Manager constant definitions
#include "ScanMgrStruct.h"			// Scan Manager structure definitions 
#include "ScanMgr.h"	 			// Scan Manager API function definitions

#include "SscandemoRsc.h"			// application resource defines
#include "Utils.h"					// miscellaneous utility functions for this app

#define  SCANDATA_WIDTH	145

Boolean extend;
Int16 extendedDataLength;

/***********************************************************************
 * Prototypes for internal functions
 **********************************************************************/
static void StartApplication(void);
static void StopApplication(void);
static Boolean MainFormHandleEvent(EventPtr event);
static void MainFormHandleMenu( UInt16 menuSel );
static void EventLoop(void);
static MenuBarPtr	CurrentMenu;
static Boolean OnDecoderData(); 
static void MainFormOnInit();
static Boolean ApplicationHandleEvent (EventPtr event);
static void OnAbout();
static Boolean AboutHandleEvent( EventPtr ev );
static void AboutOnInit( void );
static void UpdateScrollbar(void);
static void	ScrollLines(Int16 numLinesToScroll, Boolean redraw);
static void PageScroll(WinDirectionType direction);

#ifdef __cplusplus
    }
#endif
/***********************************************************************
 *
 * FUNCTION: 		StartApplication
 *
 * DESCRIPTION: 	This routine sets up the initial state of the application.
 * 					Set the Main Form as the initial form to display.
 * 					Checks to make sure we're running on Symbol hardware, then 
 * 					calls ScanOpenDecoder to initialize the Scan Manager.  
 * 					If successful, then we proceed with setting decoder 
 * 					parameters that we care about for this application.
 * 					ScanCmdSendParams is called to send our params to the decoder.
 *
 * PARAMETERS: 	None.
 *
 * RETURNED: 		Nothing.
 *
 ***********************************************************************/
static void StartApplication(void)
{
	Err error;			
				
	// Call up the main form.
	FrmGotoForm( MainForm );
	
	// Make sure we're running on Symbol hardware before attempting to 
	// open the decoder or call any other Scan Manager functions.
	if (ScanIsPalmSymbolUnit())
	{
		// Now, open the scan manager library	
		error = ScanOpenDecoder();

		if (!error)
		{
			// Set decoder parameters we care about...
			ScanCmdScanEnable(); 				// enable scanning
			ScanSetTriggeringModes( HOST ); 	// allow software-triggered scans (from our Scan button)
			ScanSetBarcodeEnabled( barUPCA, true ); 	// Enable any barcodes to be scanned
			ScanSetBarcodeEnabled( barUPCE, true );
			ScanSetBarcodeEnabled( barUPCE1, true );
			ScanSetBarcodeEnabled( barEAN13, true );
			ScanSetBarcodeEnabled( barEAN8, true );
			ScanSetBarcodeEnabled( barBOOKLAND_EAN, true);
			ScanSetBarcodeEnabled( barCOUPON, true);
			ScanSetBarcodeEnabled( barPDF417, true);

			// We've set our parameters...
			// Now call "ScanCmdSendParams" to send them to the decoder
			ScanCmdSendParams( No_Beep); 
		}
	}
}

/***********************************************************************
 *
 * FUNCTION:     StopApplication
 *
 * DESCRIPTION:  This routine does any cleanup required, including shutting down
 * 				  the decoder and Scan Manager shared library.
 *
 * PARAMETERS:   None.
 *
 * RETURNED:     Nothing.
 *
 ***********************************************************************/
static void StopApplication(void)
{
	if (ScanIsPalmSymbolUnit())
	{
		// Disable the scanner and Close Scan Manager shared library
		ScanCmdScanDisable(); 			
		ScanCloseDecoder(); 	
	}
}

/***********************************************************************
 *
 * FUNCTION:		MainFormHandleEvent
 *
 * DESCRIPTION:	Handles processing of events for the Main Form.
 * 					The following events are handled:
 * 						frmOpenEvent and menuEvent - standard handling
 * 						scanDecodeEvent - indicates that a scan was completed
 * 						scanBatteryErrorEvent - indicates batteries too low to scan
 * 						ctlSelectEvent - for Scan button on the main form
 *
 * PARAMETERS:		event	- the most recent event.
 *
 * RETURNED:		True if the event is handled, false otherwise.
 *
 ***********************************************************************/
static Boolean MainFormHandleEvent(EventPtr event)
{
	Boolean		bHandled = false;
	UInt16		extendedDataFlag;

	switch( event->eType )
	{
		case frmOpenEvent:
			MainFormOnInit();
			bHandled = true;
			break;
			
		case menuEvent:
			MainFormHandleMenu(event->data.menu.itemID);
			bHandled = true;
			break;

		case scanDecodeEvent:
			// A decode has been performed.  
			// Use the decoder API to get the decoder data into our memory

			// Get barcode parameters from the registers
			extendedDataFlag = ((ScanEventPtr)event)->scanData.scanGen.data1;
			extendedDataLength  = (Int16)(((ScanEventPtr)event)->scanData.scanGen.data2);

			extend = extendedDataFlag & EXTENDED_DATA_FLAG;

			OnDecoderData();
			
			bHandled = true;
			break;
								
		case scanBatteryErrorEvent:
		{
			Char szTemp[10];
			StrIToA( szTemp, ((ScanEventPtr)event)->scanData.batteryError.batteryLevel );
			FrmCustomAlert( BatteryErrorAlert, szTemp, NULL, NULL );

			bHandled = true;
			break;
		}
		
		case ctlSelectEvent:
		{
			if (ScanIsPalmSymbolUnit())
			{
				// Scan Button
				if (event->data.ctlEnter.controlID == MainSCANButton)
				{
					ScanCmdStartDecode();
					bHandled = true;
				}
			}
		   	break;
		}
		
		case fldChangedEvent:
			UpdateScrollbar();
			bHandled = true;
			break;
		
		case sclRepeatEvent:
			ScrollLines(event->data.sclRepeat.newValue - event->data.sclRepeat.value, false);
			break;
		
		case keyDownEvent:
		{
			if (event->data.keyDown.chr == pageUpChr) {
				PageScroll(winUp);
				bHandled = true;
			} else if (event->data.keyDown.chr == pageDownChr) {
				PageScroll(winDown);
				bHandled = true;
			}
		
			break;
		}
		
	} //end switch

	return(bHandled);
}



/***********************************************************************
 *
 * FUNCTION:     MainFormOnInit
 *
 * DESCRIPTION:  This routine sets up the initial state of the main form
 *
 * PARAMETERS:   None.
 *
 * RETURNED:     Nothing.
 *
 ***********************************************************************/
static void MainFormOnInit()
{
	FormPtr pForm = FrmGetActiveForm();
	if( pForm )
	{
		// initialize the barcode type and barcode data fields
		SetFieldText( MainBarTypeField, "No Data", 20, false );
		SetFieldText( MainScandataField, "No Data", 80, false );
		FrmDrawForm( pForm );
	}
}

/***********************************************************************
 *
 * FUNCTION:     MainFormHandleMenu
 *
 * DESCRIPTION:  This routine handles menu selections off of the main form
 *
 * PARAMETERS:   None.
 *
 * RETURNED:     Nothing.
 *
 ***********************************************************************/
void MainFormHandleMenu( UInt16 menuSel )
{
	switch( menuSel )
	{
		// Options menu
		case OptionsResetDefaults:
			if (ScanIsPalmSymbolUnit()) {
				ScanCmdScanDisable(); 			

				if(ScanCmdParamDefaults() == STATUS_OK)
					ScanCmdScanEnable(); 				// enable scanning
			}
			break;
			
		case OptionsAbout:
			OnAbout();
			break;
	}
}

/***********************************************************************
 *
 * FUNCTION:		EventLoop
 *
 * DESCRIPTION:	A simple loop that obtains events from the Event
 *						Manager and passes them on to various applications and
 *						system event handlers before passing them on to
 *						FrmHandleEvent for default processing.
 *
 * PARAMETERS:		None.
 *
 * RETURNED:		Nothing.
 *
 ***********************************************************************/
static void EventLoop(void)
{
	static EventType	event;
	UInt16 error;
		
	do
	{
		// Get the next available event.
		EvtGetEvent(&event, /*5*/ evtWaitForever);

		// Give the system a chance to handle the event.
	  	if( !SysHandleEvent (&event))
			if( !MenuHandleEvent (CurrentMenu, &event, &error))
				// Give the application a chance to handle the event.
				if( !ApplicationHandleEvent(&event) )
					// Let the form object provide default handling of the event.
					FrmDispatchEvent(&event);
	} 
	while (event.eType != appStopEvent);
}


/***********************************************************************
 *
 * FUNCTION:		ApplicationHandleEvent
 *
 * DESCRIPTION:	An event handler for this application.  Gives control 
 * 					to the appropriate form (Main or About) by setting the
 * 					newly-loaded form's event handler.
 *
 * PARAMETERS:		None.
 *
 * RETURNED:		Nothing.
 *
 ***********************************************************************/
static Boolean ApplicationHandleEvent (EventPtr event)
{
	UInt16 formID;
	FormPtr frm;

	if (event->eType == frmLoadEvent)
	{
		// Load the form resource.
		formID = event->data.frmLoad.formID;
		frm = FrmInitForm (formID);
		FrmSetActiveForm (frm);		
		
		// Set the event handler for the form.  The handler of the currently
		// active form is called by FrmDispatchEvent each time is receives an
		// event.
		switch (formID)
		{
			case MainForm:
				FrmSetEventHandler (frm, MainFormHandleEvent);
				break;
		
			case AboutForm:
				FrmSetEventHandler (frm, AboutHandleEvent );
				break;
		}
		return (true);
	}
	return (false);
}


/***********************************************************************
 *
 * FUNCTION:		PilotMain
 *
 * DESCRIPTION:	This function is the equivalent of a main() function
 *						in standard C.  It is called by the Emulator to begin
 *						execution of this application.
 *
 * PARAMETERS:		cmd - command specifying how to launch the application.
 *						cmdPBP - parameter block for the command.
 *						launchFlags - flags used to configure the launch.			
 *
 * RETURNED:		Any applicable error code.
 *
 ***********************************************************************/
UInt32 PilotMain(UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags)
{
	// Check for a normal launch.
	if (cmd == sysAppLaunchCmdNormalLaunch)
	{
		Err error = STATUS_OK;
		
		// Set up Scan Manager and the initial (Main) form.
		StartApplica
    
            

© Habrahabr.ru