//
// Framework for a Windows CE Application... This is being
// built for CE version 420 to run on an IPAQ 4700 (which is a VGA
// system). I am not going to guarantee or even worry too much (to start
// with at least) about support of other platform variants.
//
// A C Norman, Codemist Ltd, March 2005
//
/* Signature: 048865dc 05-Jan-2006 */
#include <windows.h>
#include "res.h"
#include <commctrl.h>
#include <aygshell.h>
#include <stdio.h>
HINSTANCE g_hInst; // The current instance
HWND g_hwndCB; // The command bar handle
HWND g_hWnd = NULL; // main window
HFONT hFont = 0;
// Private messages that I use within this application.
#define WM_REQUESTINPUT (WM_APP+0x01)
#define WM_PRINTCHAR (WM_APP+0x02)
#define WM_PRINTBUFFER (WM_APP+0x03)
#define WM_WORKERQUIT (WM_APP+0x04)
HANDLE workerThread;
HANDLE mutex1, mutex2;
int readRequestPending = 0;
char inputLine[200], workerLine[200];
int inputN = 0, workerN = 0, workerP = 0;
#define TYPEAHEADSIZE 256
char typeAhead[TYPEAHEADSIZE];
int aheadIn = 0, aheadOut = 0;
static RECT windowSize;
static SHACTIVATEINFO s_sai;
// Forward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);
HWND CreateRpCommandBar(HWND);
DWORD WINAPI WorkerProc(LPVOID parm);
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
HACCEL hAccelTable;
// Perform application initialization:
if (!InitInstance(hInstance, nCmdShow)) return FALSE;
hAccelTable = LoadAccelerators(hInstance, L"ACCEL");
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{ TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
// COMMENTS:
//
// It is important to call this function so that the application
// will get 'well formed' small icons associated with it.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, L"ICON");
wc.hCursor = 0;
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = L"PPC-CSL";
return RegisterClass(&wc);
}
#define TEXTSIZE 5000
#define TEXTLINES 33
TCHAR text[TEXTSIZE];
unsigned char textColour[(TEXTSIZE+3)/4]; // 2-bits per character.
int textIn = 0;
#define TEXT_BLACK 0
#define TEXT_RED 1
#define TEXT_BLUE 2
#define TEXT_GREEN 3
int currentColour = TEXT_BLACK;
int textLine[TEXTLINES];
int textX = 0, textY = 0;
// Another brief essay about how I do things. In particular the representation
// of text. I will allow for LIMITED scrollback (although that is not yet
// implemented) by having a circular buffer that actually contains the
// text. This is stored in Unicode as a sequence of lines each terminated
// with a newline. An array textLine[] identifies that start in this buffer
// of the visible lines of the screen:
//
// text: 0 1 2 3 4 5 6 7 8 9
// - - A B \ C D \ - - ("\" stands for newline here)
// ^ ^
// (textOut)---^ ^--- textIn
//
// textLine[0] 2
// textLine[1] 5
// textLine[2] -1 (indicates no such line)
// textLine[3] -1 (and -1 from now on)
//
// The data in the text buffer wraps round in a circular manner (and thus
// when it is displayed it will sometimes take two steps to write a split
// line. The textLine array is kept up to date as text is added, and so
// values in it are shuffled if a scroll is needed. If adding characters
// tries to overwrite the first line then the first stored line is totally
// purged. I will assume that even when the maximum number of visible lines
// are stored that the text buffer is not full so I do not have to panic
// about overflow.
// Individual output lines can only be displayed up to character 68, and so I
// can truncate after (about) that, which ensures space. This width comes
// from using a screen with 480 pixels width and characters that are 7
// pixels wide.
// Each character in the text buffer has 2-bits of associated attribute
// information which control the colour of display used.
// 0: black, 1:red, 2:blue, 3:green
//
inline int attribute(int n)
{
return (textColour[n>>2] >> (2*(n & 3))) & 3;
}
void setAttribute(int n, int v)
{
int k = n>>2;
int b = textColour[k];
int s = 2*(n & 3);
int mask = 3 << s;
int d = v << s;
b = (b & ~mask) | d;
textColour[k] = b;
}
//
// FUNCTION: InitInstance(HANDLE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
g_hInst = hInstance; // Store instance handle in our global variable
//If it is already running, then focus on the window
g_hWnd = FindWindow(L"PPC-CSL", L"CSL");
if (g_hWnd)
{ // set focus to foremost child window
// The "| 0x01" is used to bring any owned windows to the
// foreground and activate them.
SetForegroundWindow((HWND)((ULONG)g_hWnd | 0x01));
return 0;
}
MyRegisterClass(hInstance);
g_hWnd =
CreateWindow(L"PPC-CSL", L"CSL", WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
if (!g_hWnd) return FALSE;
// When the main window is created using CW_USEDEFAULT the height of the
// menubar (if one is created is not taken into account). So we resize
// the window after creating it if a menubar is present
GetWindowRect(g_hWnd, &windowSize);
if (g_hwndCB)
{ RECT rcMenuBar;
GetWindowRect(g_hwndCB, &rcMenuBar);
windowSize.bottom -= (rcMenuBar.bottom - rcMenuBar.top);
MoveWindow(g_hWnd, windowSize.left,
windowSize.top,
windowSize.right - windowSize.left,
windowSize.bottom - windowSize.top,
FALSE);
}
LOGFONT f;
memset(&f, 0, sizeof(f));
f.lfHeight = 16;
f.lfWidth = 8;
f.lfQuality = CLEARTYPE_COMPAT_QUALITY;
f.lfPitchAndFamily = FIXED_PITCH;
hFont = CreateFontIndirect(&f);
for (int i=1; i<TEXTLINES; i++)
textLine[i] = -1;
textLine[0] = 0;
text[0] = '\n';
textIn = 0; // text buffer now empty
textX = textY = 0; // writing to line 0, at column 0 at present
currentColour = TEXT_BLACK;
ShowWindow(g_hWnd, nCmdShow);
UpdateWindow(g_hWnd);
mutex1 = CreateMutex(NULL, 1, NULL);
mutex2 = CreateMutex(NULL, 0, NULL);
workerThread = CreateThread(NULL, 0, WorkerProc, NULL, 0, NULL);
return TRUE;
}
void deleteChar()
{
// Do nothing at present...
}
void insertChar(int ch)
{
int n = (textIn + 1) % TEXTSIZE;
text[textIn] = ch;
setAttribute(textIn, currentColour);
textIn = n;
text[n] = '\n'; // makes the re-painting code simpler
}
void showChar(int ch)
{
RECT r;
if (ch == '\n') // end a line
{ insertChar(ch);
if (textY == TEXTLINES-1)
{ for (int i=0; i<TEXTLINES-1; i++)
textLine[i] = textLine[i+1];
// If the screen scrolls I will erase the whole background so I can
// paint everything again. Maybe if I had a BitBlt to scroll data on the
// screen directly I could use that.
InvalidateRect(g_hWnd, NULL, TRUE);
}
else textY++;
textLine[textY] = textIn;
textX = 0; // now at column 0
return;
}
if (textX > 70) return; // silently truncate after 70 chars
insertChar(ch);
r.left = 7*textX;
r.right = 7*textX + 7;
r.top = 16*textY;
r.bottom = 16*textY + 16;
// I invalidate without erasing the background here, and when I paint the
// characters I do so in non-opaque mode.
InvalidateRect(g_hWnd, &r, FALSE);
textX++;
}
char promptString[32] = {'A', ':', ' ', 0};
void displayPrompt()
{
char *p = promptString;
currentColour = TEXT_BLUE;
while (*p != 0) showChar(*p++);
currentColour = TEXT_RED;
promptString[0]++; // make prompts change over time
}
int colourTable[4] = // These colours are subject to review!
{ // bbggrr
0x00000000, // black normal program output
0x004000c0, // redish maroon echo of keyboard input
0x00801000, // darkish blue prompt strings
0x00208000 // darkish green (unassigned at first)
};
//
// FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
HDC hdc;
int wmId, wmEvent;
PAINTSTRUCT ps;
int i, x, y;
switch (message)
{
case WM_CHAR:
// I need to write myself an essay about how I will handle input!
//
// At the start of a run there is no input request pending. The worker
// thread can make such a request by posting a message to the GUI thread
// and then going into a waiting state until a response arrives.
//
// If keyboard input arrives when there is no input request pending then
// printable characters are inserted into a type-ahead buffer. The only
// non-printable character that is handled is BACKSPACE, and that removes
// a character from the buffer unless the buffer is empty or the character
// that would be removed is an ENTER. At a later stage I will want to
// process at least ^\ (exit) and ^C (interrupt) even in this state.
//
// If an input request arrives I will first put a prompt string on the screen.
// Then if the type-ahead buffer is non-empty I will start moving characters
// from it into a current-line buffer, echoing each to the screen as I go. If
// I find an ENTER I return the line to the worker thread.
//
// If, even after processing some type-ahead, the current-line is incomplete
// I just await further keyboard input. Any that arrives is placed in the
// current-line and echoed on the screen. When an ENTER arrives I return to
// the worker (and in doing so I clear the input-request-pending status, as I
// did on returning a line from typed-ahead characters).
//
// The proper way to do a lot of this is to handle KEYDOWN and KEYUP
// events rather than CHAR. By so doing it becomes possibly to distinguish
// between (eg) backspace and ^H. But as best I can see at present I will
// need to detect shift and CTRL keys and maintain my own information about
// when they are pressed (and perhaps similarly for CAPS-LOCK) and do my own
// handling of character repetition. That all feels pretty heavy. So my
// strategy will be to start by handling CHAR messages and later (perhaps)
// I will detect the other cases.
//
if (!readRequestPending)
{ if (wParam == (0x1f & 'H'))
{ if (aheadIn == aheadOut) return 0; // no type-ahead
int p = aheadIn == 0 ? TYPEAHEADSIZE-1 : aheadIn-1;
// When the user presses ENTER the line they have just completed becomes
// committed. ENTER is represented as Ctl-M
if (typeAhead[p] == (0x1f & 'M')) return 0;
aheadIn = p;
}
else
{ int p = (aheadIn+1) % TYPEAHEADSIZE;
if (p == aheadOut) return 0; // no room in the buffer
typeAhead[aheadIn] = wParam;
aheadIn = p;
}
return 0;
}
switch (wParam)
{
case 0x1f & 'H':
// Backspace: if no chars in current line just ignore it.
if (inputN == 0) return 0;
inputN--;
deleteChar();
return 0;
default:
if (inputN < sizeof(inputLine)-1)
{ inputLine[inputN++] = wParam;
currentColour = TEXT_RED;
showChar(wParam);
}
return 0;
case 0x1f & 'M':
// ENTER: and there was a read request pending so I must pass data to the
// worker thread and release it.
inputLine[inputN++] = '\n'; // return as '\n' even if '\r'
inputLine[inputN] = 0; // terminate the input line
showChar('\n');
currentColour = TEXT_BLACK;
readRequestPending = 0;
HANDLE m2 = mutex2;
ReleaseMutex(mutex1); // permits worker to access the char
// The next line has a slight depth. It waits until the worker thread has
// accepted the data (and because it was waiting for it that should happen
// promptly). But while the worker has control it flips the two variables
// mutex1 and mutex2 so that after the WaitForSingleObject here the mutext
// actually claimed will be the one referred to be mutex1.
WaitForSingleObject(m2, INFINITE);
return 0;
}
// void ce_getline()
// {
// PostMessage(hWnd, WM_REQUESTINPUT, 0, 0);
// WaitForSingleObject(mutex1, INFINITE);
// memcpy(workerLine, inputLine, inputN);
// workerN = inputN;
// HANDLE w = mutex1;
// mutext1 = mutex2;
// mutex2 = w;
// ReleaseMutex(mutex1);
// // input data is now in workerLine.
// }
// The next few messages are ones that my worker thread can post to me.
case WM_REQUESTINPUT:
inputN = 0;
displayPrompt();
while (aheadOut != aheadIn)
{ int ch = typeAhead[aheadOut];
aheadOut = (aheadOut+1) % TYPEAHEADSIZE;
if (ch == (0x1f & 'M'))
{ inputLine[inputN++] = '\n';
inputLine[inputN] = 0;
showChar('\n');
currentColour = TEXT_BLACK;
HANDLE m2 = mutex2;
ReleaseMutex(mutex1);
WaitForSingleObject(m2, INFINITE);
return 0;
}
inputLine[inputN++] = ch;
currentColour = TEXT_RED;
showChar(ch);
}
readRequestPending = 1;
return 0;
case WM_PRINTCHAR:
showChar(wParam);
return 0;
case WM_PRINTBUFFER:
// This is pending!
{ char *s = "<printbuffer>";
while (*s != 0) showChar(*s++);
}
return 0;
case WM_WORKERQUIT: // the worker can send this to terminate the application
SendMessage(hWnd, WM_CLOSE, 0, 0);
return 0;
// Now handlers for some system messages that get treated in fairly
// standard manners.
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmId)
{
case IDM_HELP_ABOUT:
DialogBox(g_hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
return 0;
case IDM_ITEM_QUIT:
case IDOK:
SendMessage(hWnd, WM_CLOSE, 0, 0);
return 0;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
case WM_CREATE:
g_hwndCB = CreateRpCommandBar(hWnd);
// Initialize the shell activate info structure
memset(&s_sai, 0, sizeof(s_sai));
s_sai.cbSize = sizeof(s_sai);
return 0;
case WM_PAINT:
RECT rt;
hdc = BeginPaint(hWnd, &ps);
if (hFont != 0) SelectObject(hdc, hFont);
for (i=0; i<TEXTLINES; i++)
{ int start = textLine[i],
end = textLine[i+1];
if (start == end) continue;
// Now the text that I need to draw may have to be drawn in several
// segments. There are two reasons for making a break: (a) the line can
// be represented by a sequence of chars that wrap around in the circular
// text buffer and (b) I may need to deal with colour effects
x = 0;
y = 16*i;
int ch = text[start];
while (ch != '\n' && ch != 0)
{ int attstart = attribute(start);
int next = start;
int count = 0;
while (ch != '\n' && ch != 0)
{ next = (next + 1) % TEXTSIZE;
count++;
if (next == 0) break; // circular buffer wrap
int attnext = attribute(next);
if (attstart != attnext) break; // colour change
ch = text[next];
}
SetTextColor(hdc, colourTable[attstart]);
ExtTextOut(hdc, x, y, 0, NULL,
&text[start], count, NULL);
// Note that to cope with cleartype fonts I do not use opaque chars.
// Thus I need to clear the background manually at some stage. I think
// that two times SHOULD suffice for that: (a) at the start of the
// session and (b) clearing a row of stuff when I scroll the screen up.
x += 7*count; // I believe that my chars are 7 pixels wide
start = next;
ch = text[start];
}
}
EndPaint(hWnd, &ps);
return 0;
case WM_DESTROY:
CommandBar_Destroy(g_hwndCB);
if (hFont != 0) DeleteObject(hFont);
PostQuitMessage(0);
return 0;
case WM_ACTIVATE:
// Notify shell of our activate message
SHHandleWMActivate(hWnd, wParam, lParam, &s_sai, FALSE);
return 0;
case WM_SETTINGCHANGE:
SHHandleWMSettingChange(hWnd, wParam, lParam, &s_sai);
return 0;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
HWND CreateRpCommandBar(HWND hwnd)
{
SHMENUBARINFO mbi;
memset(&mbi, 0, sizeof(SHMENUBARINFO));
mbi.cbSize = sizeof(SHMENUBARINFO);
mbi.hwndParent = hwnd;
mbi.nToolBarId = IDM_MENU;
mbi.hInstRes = g_hInst;
mbi.nBmpId = 0;
mbi.cBmpImages = 0;
if (!SHCreateMenuBar(&mbi))
return NULL;
return mbi.hwndMB;
}
// Message handler for the About box.
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
SHINITDLGINFO shidi;
switch (message)
{
case WM_INITDIALOG:
// Create a Done button and size it.
shidi.dwMask = SHIDIM_FLAGS;
shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIPDOWN |
SHIDIF_SIZEDLGFULLSCREEN;
shidi.hDlg = hDlg;
SHInitDialog(&shidi);
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK)
{ EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
return FALSE;
}
void ce_getline()
{
PostMessage(g_hWnd, WM_REQUESTINPUT, 0, 0);
WaitForSingleObject(mutex1, INFINITE);
memcpy(workerLine, inputLine, inputN);
workerN = inputN;
workerP = 0;
HANDLE w = mutex1;
mutex1 = mutex2;
mutex2 = w;
ReleaseMutex(mutex1);
// input data is now in workerLine.
}
extern "C"
{
extern int ce_readch();
extern void ce_print(char *s);
}
int ce_readch()
{
if (workerN == workerP) ce_getline();
if (workerN == workerP) return EOF;
else return workerLine[workerP++];
}
void ce_print(char *s)
{
while (*s != 0) PostMessage(g_hWnd, WM_PRINTCHAR, *s++, 0);
}
char *argvec[] =
{
"csl",
"-v",
"-i",
"\\IPAQ File Store\\r38.img",
NULL
};
extern "C"
{
extern int fwin_main(int argc, char *argv[]);
}
DWORD WINAPI WorkerProc(LPVOID parm)
{
WaitForSingleObject(mutex2, INFINITE);
int i;
for (i=0; i<7; i++)
ce_print("....:....*");
ce_print("\n");
ce_print("This is a message from the worker\n");
// for (i=0; i<2; i++)
// { int ch = ce_readch();
// char b[30];
// sprintf(b, "Char %#.2x\n", ch);
// ce_print(b);
// }
// ce_print("Done...\n");
// After finishing I will wait 4 seconds or until an ENTER before
// killing the application.
// WaitForSingleObject(mutex1, 4000);
ce_print("will now try to start fwin_main\n");
fwin_main(4, argvec);
ce_print("fwin_main returned: press ENTER to quit\n");
ce_getline(); // wait for user to type ENTER
WaitForSingleObject(mutex1, 1000); // then wait 1 second more
PostMessage(g_hWnd, WM_WORKERQUIT, 0, 0);
return 1;
}
// end of ipaq.cpp