Приветствую всех!

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



Итак, в сегодняшней статье рассмотрим разработку софта для КПК под управлением легендарной 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 для запуска приложений, что обеспечивало хорошую совместимость, но сильно теряло в производительности. В данной статье поговорим преимущественно о «традиционных» пальмах.

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


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



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



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



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



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



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

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


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



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



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



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

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


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



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



А вот и он.



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



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



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



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



Меняем экраны местами и собираем. Работает. Удивительно, но ионистор у 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шник, распаковываем его и приступаем к установке.



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



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



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



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



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



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

Эмулятор


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



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

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


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

#include <PalmOS.h>

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;
}

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



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

Ресурсы


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



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



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

Как это работает? Заглянем в соседнюю папку, где обитает файл с расширением *.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 <PalmOS.h>
#include <PalmOSGlue.h>

#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. Для примера рассмотрим второй вариант.



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



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



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



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



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

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

#include <PalmOS.h>
#include <PalmOSGlue.h>

#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;
}



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

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

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



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



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







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

Картинки


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



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



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



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

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 вы рискуете увидеть примерно следующее:





Итак, порт открыли. Для примера я бахнул две кнопки, одна из которых отправляет в порт «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 <Menu.h>

#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.
		StartApplication();
		
		// Start up the event loop.
		EventLoop();

		// Close down Scan Manager, decoder
		StopApplication();
	}
	
	return(0);
}

/***********************************************************************
 *
 * FUNCTION: 		OnDecoderData
 *
 * DESCRIPTION:	Called when the app receives a scanDecodeEvent, which
 * 					signals that a decode operation has been completed.
 * 					Calls the Scan Manager function "ScanGetDecodedData" 
 * 					to get the scan data and barcode type from the last 
 * 					scan.  Fills in the controls on the main form that 
 * 					display this information.
 *
 * RETURNED:		True if the event is handled, false otherwise.
 *
 ***********************************************************************/
Boolean OnDecoderData()
{

	static Char 	BarTypeStr[80]=" ";
	MESSAGE 		decodeDataMsg;
	Int16			status;
	MemHandle		hExtendedData;
	UInt8			*pExtendedData;
	Int16			extendedDataType;
	UInt16			numlines;

	
	if (extend)	{
		hExtendedData = MemHandleNew( extendedDataLength );
		pExtendedData = (UInt8 *)MemHandleLock( hExtendedData );
		status = ScanGetExtendedDecodedData(extendedDataLength, &extendedDataType, pExtendedData);
	}
	else	{
		status = ScanGetDecodedData( &decodeDataMsg );
		extendedDataType = decodeDataMsg.type;
		extendedDataLength = decodeDataMsg.length;
		hExtendedData = MemHandleNew(extendedDataLength+1);
		pExtendedData = (UInt8 *)MemHandleLock( hExtendedData );
		pExtendedData[extendedDataLength] = '\0';
		MemMove( &pExtendedData[0], &decodeDataMsg.data[0], extendedDataLength+1 );		
	}
	
	if( status == STATUS_OK ) // if we successfully got the decode data from the API...
	{
		FieldPtr 		pField;

		// call a function to translate barcoce type into a string, and display it
		ScanGetBarTypeStr( extendedDataType, BarTypeStr, 30 );	// in Utils.c
		SetFieldText( MainBarTypeField, BarTypeStr, 30, true );


		// Check to see if this scan was a "No Data Read" (indicated by type of zero).
		if( extendedDataType == 0)
		{
			SetFieldText( MainScandataField, "No Scan", 30, true );
		}
		else
		{
			FieldAttrType attr;
			// Place the barcode data into the field and display

		  	/* Set up data display field to display the memory */
			pField = (FieldPtr)GetObjectPtr(MainScandataField);

			FldGetAttributes(pField, &attr);
			attr.editable = true;
			FldSetAttributes(pField, &attr);

			FldDelete(pField, 0, FldGetTextLength(pField));		// clear out old data
  						
			if (extendedDataLength > FldGetMaxChars(pField))	
				FldSetMaxChars (pField, extendedDataLength);

			FldEraseField(pField);								// hide field so we don't
																// see the data scroll in

			FldInsert(pField, (Char *)&pExtendedData[0], extendedDataLength);

			// move to top of scroll bar
			numlines = FldCalcFieldHeight((Char *)&pExtendedData[0], SCANDATA_WIDTH);
			ScrollLines(-numlines, false);				// scroll to top of data
	
			FldDrawField(pField);								// show field

			FldGetAttributes(pField, &attr);
			attr.editable = false;
			FldSetAttributes(pField, &attr);

		}
	}

	UpdateScrollbar();

	MemHandleUnlock( hExtendedData );
	MemHandleFree( hExtendedData );

	return(0);	    			
}

/***********************************************************************
 *
 * FUNCTION:	UpdateScrollbar
 *
 * DESCRIPTION:	Updates the scroll bar according to the current condition
 *				of MainScandataField. Code taken from Palm Programming.
 *
 * PARAMETERS:		none
 *
 * RETURNED:		none
 *
 ***********************************************************************/

static void UpdateScrollbar(void)
{
	FormPtr			frm = FrmGetActiveForm();
	ScrollBarPtr	scroll;
	FieldPtr		field;
	UInt16			currentPosition;
	UInt16			textHeight;
	UInt16			fieldHeight;
	UInt16			maxValue;
	
	field = (FieldPtr)FrmGetObjectPtr(frm, FrmGetObjectIndex(frm, MainScandataField));
	FldGetScrollValues(field, &currentPosition, &textHeight, &fieldHeight);
	
	if (textHeight > fieldHeight)
		maxValue = textHeight - fieldHeight;
	else if (currentPosition)
		maxValue = currentPosition;
	else
		maxValue = 0;
		
	scroll = (ScrollBarPtr)FrmGetObjectPtr(frm, FrmGetObjectIndex(frm, MainScanScrollBar));
	SclSetScrollBar(scroll, currentPosition, 0, maxValue, fieldHeight - 1);
	
}

/***********************************************************************
 *
 * FUNCTION:	ScrollLines
 *
 * DESCRIPTION:	Scrolls the field according to the Scroll bar
 *
 * PARAMETERS:		numLinesToScroll - number of lines to scroll
 *					redraw - call update scrollbar
 *
 * RETURNED:		none
 *
 ***********************************************************************/

static void ScrollLines (Int16 numLinesToScroll, Boolean redraw)
{
	FormPtr		frm = FrmGetActiveForm();
	FieldPtr	field;

	field = (FieldPtr)FrmGetObjectPtr(frm, FrmGetObjectIndex(frm, MainScandataField));

	if (numLinesToScroll < 0)
		FldScrollField(field, -numLinesToScroll, winUp);
	else
		FldScrollField(field, numLinesToScroll, winDown);
		
	if (FldGetNumberOfBlankLines(field) && numLinesToScroll < 0 || redraw)
		UpdateScrollbar();
}
	

/***********************************************************************
 *
 * FUNCTION:	PageScroll
 *
 * DESCRIPTION:	Scrolls the field up or down one field page length
 *
 * PARAMETERS:		direction - up or down
 *
 * RETURNED:		none
 *
 ***********************************************************************/

static void PageScroll (WinDirectionType direction)
{
	FormPtr		frm = FrmGetActiveForm();
	FieldPtr	field;
	
	field = (FieldPtr)FrmGetObjectPtr(frm, FrmGetObjectIndex(frm, MainScandataField));
	if (FldScrollable(field, direction)){
		Int16 linesToScroll = FldGetVisibleLines(field) -1;
		
		if(direction == winUp)
			linesToScroll = -linesToScroll;
			
		ScrollLines(linesToScroll, true);
	}
}

/***********************************************************************
   About Form
************************************************************************/

/***********************************************************************
 *
 * FUNCTION:		OnAbout
 *
 * DESCRIPTION:	Changes the active form to the About Form
 *
 * PARAMETERS:		None.
 *
 * RETURNED:		Nothing
 *
 ***********************************************************************/
void OnAbout()
{
	FrmGotoForm( AboutForm );
}

/***********************************************************************
 *
 * FUNCTION:		AboutOnInit
 *
 * DESCRIPTION:	Initialize controls on the about form, after getting 
 * 					the version strings from the Scan Manager API.
 *
 * PARAMETERS:		None.
 *
 * RETURNED:		Nothing
 *
 ***********************************************************************/
static void AboutOnInit( void )
{
	Char chrDecoderVersion[MAX_PACKET_LENGTH];
	Char chrPortDriverVersion[20];
	Char chrScanMgrVersion[20];
	FormPtr pForm = FrmGetActiveForm();

	if (ScanIsPalmSymbolUnit())
	{
		// initialize text in all the version string fields
		ScanGetDecoderVersion( chrDecoderVersion, MAX_PACKET_LENGTH);
		SetFieldText(AboutDecoderField, chrDecoderVersion, MAX_PACKET_LENGTH, false);

		ScanGetScanPortDriverVersion(chrPortDriverVersion, 20 );
		SetFieldText(AboutPortDriverField, chrPortDriverVersion, 20, false);
		
		ScanGetScanManagerVersion(chrScanMgrVersion, 20 );
		SetFieldText(AboutScanManagerField, chrScanMgrVersion, 20, false);
	}
	
	FrmDrawForm( pForm );	
}

/***********************************************************************
 *
 * FUNCTION:		AboutHandleEvent
 *
 * DESCRIPTION:	Event handler for the About form.
 * 					Handles the frmOpenEvent, frmCloseEvent, and the 
 * 					ctlSelectEvent (for the OK button).
 *
 * PARAMETERS:		ev - pointer to the event information structure.
 *
 * RETURNED:		true - the event was handled by us.  false otherwise
 *
 ***********************************************************************/
Boolean AboutHandleEvent( EventPtr ev )
{
	Boolean bHandled = false;
	
	switch( ev->eType )
	{
		case ctlSelectEvent:
			if( ev->data.ctlSelect.controlID == AboutOKButton )
			{
				FrmGotoForm(MainForm);
				bHandled = true;
			}
			break;

		case frmOpenEvent:
			AboutOnInit();
			bHandled = true;
			break;
			
		case frmCloseEvent:
			FreeFieldHandle(AboutDecoderField);
			break;
			
		default:
			break;
	}
	
	return bHandled;
}


Для начала вызывается функция ScanIsPalmSymbolUnit(), позволяющая удостовериться, что программа действительно работает на ТСД. Также она позволяет сделать работу приложения более гибкой, например, на обычном КПК можно сделать доступным только ручной ввод, а на ТСД — ещё и сканирование кодов. Если всё верно, сканер активируется функцией ScanCmdScanEnable(). В обработчике событий добавляются два новых пункта: scanDecodeEvent и scanBatteryErrorEvent. Первое из них происходит, когда сканер что-то прочитал, второе — при низком заряде батарей.

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

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



Итак, собираем, загружаем, запускаем.



Работает! Кстати, как видно из названия приложения, оно унифицировано для работы со всеми типами сканеров, что могли устанавливаться в данные КПК, то есть QR-коды или PDF417 считывались бы точно так же.

Что дальше?


Разумеется, все аспекты программирования под Palm OS в одной статье не представить. Более того, их не представить и в паре-тройке статей, поскольку нюансов весьма много. Тем не менее, с документацией по программированию всё весьма неплохо, ту же Palm OS Programming от O'Reilly и сейчас вполне реально найти на просторах. Также целая куча хороших примеров идёт в комплекте с CodeWarrior.

Пара слов про rePalm и другие проекты


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



Чего только стоит такой проект как rePalm, позволяющий запустить Palm OS на куда более новом железе (как, например, эта сборка для платы STM32F429I-DISCO, что как раз обнаружилась в моих щедрых закромах Родины).



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



Совсем недавно, летом прошлого года, на Web Archive появился целый раздел с софтом для пальм. Причём каждое приложение можно запустить в эмуляторе прямо в браузере.

Вот как-то так




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

Ссылки




Комментарии (23)


  1. dlinyj
    21.05.2023 09:14
    +6

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


    1. MaFrance351 Автор
      21.05.2023 09:14
      +2

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


      1. saboteur_kiev
        21.05.2023 09:14

        Но ведь современные читалки гораздо более экономные с жидкими чернилами? При этом там уже линукс. Чем Палм лучше?


        1. MaFrance351 Автор
          21.05.2023 09:14
          +1

          Компактный размер, ну очень долгое время работы от батареек и (в случае Sony) колёсико прокрутки? Не?
          А, ну и никаких проблем с проприетарщиной и DRM вроде SD-карты в PocketBook, о которой писал даже dlinyj.


          1. saboteur_kiev
            21.05.2023 09:14

            Не знаю.. мне очень нравится киндл. Я в последнее время его и не ломаю, ибо конвертнуть или сразу скачать в mobi формате не проблема.
            Зато подсветка, работа неделями, удобство с подключением по нормальному usb-c и в принципе поддержка wifi даже есть, хотя ею почти не польбзовался


            1. dlinyj
              21.05.2023 09:14
              +1

              Это просто разные устройства. Хотя, без сомнения, читалки — это идеологический наследник Palm.


              1. saboteur_kiev
                21.05.2023 09:14
                +2

                Я так не считаю. Читалки - идеологический наследник открытия технологии жидких чернил. А идеологический наследник палма - больше смартфон.

                И да, 20 лет назад мы юзали в конторе палмы, так что я с них в свое время читал =)


                1. MaFrance351 Автор
                  21.05.2023 09:14

                  Тогда уж WM (Pocket PC) в качестве предшественника смартфонов надо взять. Пальмы были в сравнении с ними лишены очень многого в угоду энергосбережения и не самой высокой цены. И представляли собой навороченные органайзеры, лишь некое дополнение к ПК.
                  Ну и, кстати, читалки существовали задолго до массового появления e-ink. Те же Franklin BOOKMAN (у меня даже где-то лежит BOOKMAN Sidekick, гибрид читалки и органайзер) или eBookMan (про который у меня даже есть пост).


                1. dlinyj
                  21.05.2023 09:14
                  +1

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


                  Тут спор бессмысленен, в том смысле, что появилось курица или яйцо.


                  1. saboteur_kiev
                    21.05.2023 09:14

                    Но он поддерживал возможность соединения с устройствами. У нас на палмах была организован удаленный складской учет. Человек с палмом приезжал к "клиенту", подключался через мобилку к инету, скачивал файлик с остатками на складе, набирал заказ, опять подключался и отправлял. Через минуту заказ уже в набранном виде попадал на склад на подборку. За день можно было проехаться региону, взять несколько заказов, до конца дня они отгружались и на следующий день уже были у заказчика. Для 2000-2005 годов это была охрененная киллер фича.


    1. Astroscope
      21.05.2023 09:14
      +2

      работает от батарей

      Спасало то, что вместо батарей работали и аккумуляторы. Иначе, по моему скромному мнению, это было бы минусом.


      1. MaFrance351 Автор
        21.05.2023 09:14
        +2

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


        1. Astroscope
          21.05.2023 09:14

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

          Мои пять копеек.


          1. dlinyj
            21.05.2023 09:14
            +2

            Тогда, когда были Palm — мобильных зарядок не было в природе.


            1. Astroscope
              21.05.2023 09:14
              +2

              Возможно да, автомобильные зарядки появились позже.


          1. Lirix_vladimir
            21.05.2023 09:14
            +1

            в те времена в поезде было мало розеток. в основном в туалетах и тамбуре перед туалетом.
            в самолете их вообще не бывало


            1. MaFrance351 Автор
              21.05.2023 09:14
              +1

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


  1. WorkND
    21.05.2023 09:14
    +3

    Спасибо вам за приятные воспоминания.

    В свое время писал программы под Treo680.


    1. MaFrance351 Автор
      21.05.2023 09:14
      +1

      Если не секрет, что за софт был?


  1. Sagittarius67
    21.05.2023 09:14
    +1

    Нужно будет раскопать свой TRGPro.


  1. zzcop
    21.05.2023 09:14
    +1

    Спасибо, статья напомнила когда-то популярный раздел "Программирование" в радио и научно-технических журналах тех самых лет. Теперь напрашивается создать текстовый квест с картинками, а что еще ?


    1. MaFrance351 Автор
      21.05.2023 09:14

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


  1. dizatorr
    21.05.2023 09:14
    +1

    У меня в кладовке лежат 4 штуки палмов. 2 м105, тунгстен и сонька. Разной степени убитости. Рука не поднимается их выкинуть, уж больно удобными они были. Дольше всего прослужили м105. Единственный недостаток - на морозе экран не показывал, совсем.

    А вот по поводу отсутствия файловой системы ни кто не заморачивался, по сути - как только приложение заливалось на пальму, оно считалось запущенным. И реальный старт приложений, не смотря на слабый процессор, происходил на удивление быстро. Ведь все приложения и все ресурсы уже находились в, условно, оперативной памяти. Какая экономия ресурсов.

    При этом на ВМ, одно и то же могло присутствовать в 3 копиях. На эмуляции жесткого диска, в оперативной памяти и в свопе.