updated ui to use multiple screens

This commit is contained in:
Sven laptop
2026-09-18 17:15:00 +02:00
parent c09b3f3f66
commit ebcc485f51
6 changed files with 224 additions and 26 deletions
+6 -1
View File
@@ -30,7 +30,7 @@ The app intentionally points at the live network host instead of localhost so it
## Installer
A custom Windows installer is included and generates a classic setup package for XP-era machines.
A custom Windows installer is the distribution package for XP-era machines. The executable is bundled inside the installer; distribute `TransitDriverXPSetup.exe` rather than the internal `driver_xp.exe`.
```bash
makensis installer.nsi
@@ -44,4 +44,9 @@ This creates `TransitDriverXPSetup.exe`, which installs:
- Desktop shortcut
- Uninstaller entry
When run on a machine with an existing Transit Driver XP installation, the installer
reuses the saved installation directory, stops the previous app if it is running,
and replaces the installed files with the new version. Existing shortcuts remain in
place and point to the updated app.
The installer is designed for test deployment on a real Windows XP or older Windows machine compatible with native Win32 executables.
Binary file not shown.
Binary file not shown.
+192 -23
View File
@@ -16,6 +16,13 @@
#define ID_DATE 107
#define ID_ROUTES 108
#define ID_STOPS 109
#define ID_BACK 110
#define ID_HALT 111
#define ID_PAGE_TITLE 112
#define ID_DATE_LABEL 113
#define ID_USER_LABEL 114
#define ID_PASSWORD_LABEL 115
#define ID_ROUTES_LABEL 116
#define MAX_BUF 65535
#define API_HOST "192.168.68.66"
@@ -29,6 +36,10 @@ typedef struct {
int routeCount;
int routeLineIds[32];
char routeLabels[32][256];
int stopCount;
int activeStop;
char stopNames[64][128];
char stopTimes[64][32];
} APPSTATE;
static void SetStatusText(HWND hwnd, const char *text)
@@ -41,6 +52,115 @@ static void SetOutputText(HWND hwnd, const char *text)
SetWindowTextA(GetDlgItem(hwnd, ID_OUTPUT), text);
}
static void ShowControl(HWND hwnd, int id, int visible)
{
ShowWindow(GetDlgItem(hwnd, id), visible ? SW_SHOW : SW_HIDE);
}
static void ShowRouteScreen(HWND hwnd)
{
SetWindowTextA(hwnd, "Transit Driver XP - Assigned Routes");
SetWindowTextA(GetDlgItem(hwnd, ID_PAGE_TITLE), "TODAY'S ASSIGNED ROUTES");
ShowControl(hwnd, ID_DATE, 1);
ShowControl(hwnd, ID_DATE_LABEL, 1);
ShowControl(hwnd, ID_ROUTES, 1);
ShowControl(hwnd, ID_ROUTES_LABEL, 1);
ShowControl(hwnd, ID_REFRESH, 1);
ShowControl(hwnd, ID_STOPS, 1);
ShowControl(hwnd, ID_OUTPUT, 1);
ShowControl(hwnd, ID_BACK, 0);
ShowControl(hwnd, ID_HALT, 0);
ShowControl(hwnd, ID_USERNAME, 0);
ShowControl(hwnd, ID_PASSWORD, 0);
ShowControl(hwnd, ID_USER_LABEL, 0);
ShowControl(hwnd, ID_PASSWORD_LABEL, 0);
ShowControl(hwnd, ID_LOGIN, 0);
}
static void ShowStartScreen(HWND hwnd, APPSTATE *state)
{
char title[256];
SetWindowTextA(hwnd, "Transit Driver XP - Route in Progress");
SetWindowTextA(GetDlgItem(hwnd, ID_PAGE_TITLE), "ROUTE IN PROGRESS");
ShowControl(hwnd, ID_DATE, 0);
ShowControl(hwnd, ID_DATE_LABEL, 0);
ShowControl(hwnd, ID_ROUTES, 0);
ShowControl(hwnd, ID_ROUTES_LABEL, 0);
ShowControl(hwnd, ID_REFRESH, 0);
ShowControl(hwnd, ID_STOPS, 0);
ShowControl(hwnd, ID_OUTPUT, 0);
ShowControl(hwnd, ID_BACK, 1);
ShowControl(hwnd, ID_HALT, 1);
ShowControl(hwnd, ID_USERNAME, 0);
ShowControl(hwnd, ID_PASSWORD, 0);
ShowControl(hwnd, ID_USER_LABEL, 0);
ShowControl(hwnd, ID_PASSWORD_LABEL, 0);
ShowControl(hwnd, ID_LOGIN, 0);
snprintf(title, sizeof(title), "Route started\r\n\r\nTap the halt button when you arrive at the displayed halt.");
SetWindowTextA(GetDlgItem(hwnd, ID_STATUS), "Route in progress");
SetWindowTextA(GetDlgItem(hwnd, ID_OUTPUT), title);
InvalidateRect(GetDlgItem(hwnd, ID_HALT), NULL, TRUE);
(void)state;
}
static void UpdateHaltButton(HWND hwnd, APPSTATE *state)
{
char label[256];
if (state->activeStop >= state->stopCount) {
SetWindowTextA(GetDlgItem(hwnd, ID_HALT), "ROUTE COMPLETE");
SetStatusText(hwnd, "Route complete");
return;
}
snprintf(label, sizeof(label), "%s\r\n+%s min", state->stopNames[state->activeStop], state->stopTimes[state->activeStop]);
SetWindowTextA(GetDlgItem(hwnd, ID_HALT), label);
SetStatusText(hwnd, "Route in progress");
}
static void ShowLoginScreen(HWND hwnd)
{
SetWindowTextA(hwnd, "Transit Driver XP - Driver Login");
SetWindowTextA(GetDlgItem(hwnd, ID_PAGE_TITLE), "DRIVER LOGIN");
ShowControl(hwnd, ID_DATE, 0);
ShowControl(hwnd, ID_DATE_LABEL, 0);
ShowControl(hwnd, ID_ROUTES, 0);
ShowControl(hwnd, ID_ROUTES_LABEL, 0);
ShowControl(hwnd, ID_REFRESH, 0);
ShowControl(hwnd, ID_STOPS, 0);
ShowControl(hwnd, ID_OUTPUT, 1);
ShowControl(hwnd, ID_BACK, 0);
ShowControl(hwnd, ID_HALT, 0);
ShowControl(hwnd, ID_USERNAME, 1);
ShowControl(hwnd, ID_PASSWORD, 1);
ShowControl(hwnd, ID_USER_LABEL, 1);
ShowControl(hwnd, ID_PASSWORD_LABEL, 1);
ShowControl(hwnd, ID_LOGIN, 1);
}
static void DrawHaltButton(const DRAWITEMSTRUCT *draw)
{
char label[256];
HBRUSH brush = CreateSolidBrush(RGB(210, 55, 45));
HPEN pen = CreatePen(PS_SOLID, 2, RGB(120, 25, 20));
HGDIOBJ oldBrush = SelectObject(draw->hDC, brush);
HGDIOBJ oldPen = SelectObject(draw->hDC, pen);
RECT rect = draw->rcItem;
Ellipse(draw->hDC, rect.left + 4, rect.top + 4, rect.right - 4, rect.bottom - 4);
SetBkMode(draw->hDC, TRANSPARENT);
SetTextColor(draw->hDC, RGB(255, 255, 255));
GetWindowTextA(draw->hwndItem, label, sizeof(label));
DrawTextA(draw->hDC, label, -1, &rect, DT_CENTER | DT_VCENTER | DT_WORDBREAK);
SelectObject(draw->hDC, oldBrush);
SelectObject(draw->hDC, oldPen);
DeleteObject(brush);
DeleteObject(pen);
}
static void PerformRefresh(HWND hwnd, APPSTATE *state);
static void TrimNewlines(char *text)
{
size_t len = strlen(text);
@@ -210,7 +330,7 @@ static void ExtractScheduleSummary(const char *json, APPSTATE *state, char *out,
ExtractJsonFieldString(routeText, "lineNumber", routeLine, sizeof(routeLine));
ExtractJsonFieldString(routeText, "startTime", routeStartTime, sizeof(routeStartTime));
ExtractJsonFieldString(routeText, "id", routeLineId, sizeof(routeLineId));
if (routeLine[0] == '\0') {
if (routeLine[0] == '\0' || routeLineId[0] == '\0') {
char busLine[1024];
const char *busKey = strstr(routeText, "\"busLine\"");
if (busKey) {
@@ -295,7 +415,7 @@ static void ExtractScheduleSummary(const char *json, APPSTATE *state, char *out,
snprintf(out, outSize, "%s", summary);
}
static void ExtractStopsSummary(const char *json, char *out, size_t outSize)
static void ExtractStopsSummary(const char *json, APPSTATE *state, char *out, size_t outSize)
{
const char *stopsKey = strstr(json, "\"stops\"");
const char *p;
@@ -338,6 +458,10 @@ static void ExtractStopsSummary(const char *json, char *out, size_t outSize)
snprintf(summary + strlen(summary), sizeof(summary) - strlen(summary),
"%s. %s (arrival +%s min)\r\n", order, name, eta);
if (count < 64) {
snprintf(state->stopNames[count], sizeof(state->stopNames[count]), "%s", name);
snprintf(state->stopTimes[count], sizeof(state->stopTimes[count]), "%s", eta);
}
count++;
p = end + 1;
while (*p == ' ' || *p == '\r' || *p == '\n' || *p == '\t' || *p == ',') {
@@ -350,6 +474,8 @@ static void ExtractStopsSummary(const char *json, char *out, size_t outSize)
return;
}
state->stopCount = count < 64 ? count : 64;
state->activeStop = 0;
snprintf(out, outSize, "Stop-by-stop timetable (%d stops)\r\n\r\n%s", count, summary);
}
@@ -390,9 +516,8 @@ static void ExtractTokenFromJson(const char *json, char *outToken, size_t outSiz
static void HttpErrorMessage(const char *context, DWORD error)
{
char text[256];
snprintf(text, sizeof(text), "%s (WinHTTP error %lu)", context, error);
MessageBoxA(NULL, text, "Driver app error", MB_ICONERROR | MB_OK);
(void)context;
(void)error;
}
static int HttpRequestText(const char *method,
@@ -623,23 +748,23 @@ static void PerformLogin(HWND hwnd, APPSTATE *state)
SetStatusText(hwnd, "Logging in...");
if (!HttpRequestText("POST", "http://192.168.68.66:5000/login", payload, NULL, response, sizeof(response))) {
SetOutputText(hwnd, "Login failed. Check that auth_api is running on 192.168.68.66:5000.");
SetOutputText(hwnd, "Driver service unreachable. Please try again.");
SetStatusText(hwnd, "Login failed");
return;
}
ExtractTokenFromJson(response, token, sizeof(token));
if (token[0] == '\0') {
SetOutputText(hwnd, response);
SetStatusText(hwnd, "No token returned");
SetOutputText(hwnd, "Driver service unreachable. Please try again.");
SetStatusText(hwnd, "Service unavailable");
return;
}
strncpy(state->token, token, sizeof(state->token) - 1);
state->token[sizeof(state->token) - 1] = '\0';
snprintf(state->lastText, sizeof(state->lastText), "Login succeeded. Driver session ready.\r\nUse Refresh schedule to load the route list.");
SetOutputText(hwnd, state->lastText);
SetStatusText(hwnd, "Connected");
ShowRouteScreen(hwnd);
SetStatusText(hwnd, "Loading today's assigned routes...");
PerformRefresh(hwnd, state);
}
static void PerformRefresh(HWND hwnd, APPSTATE *state)
@@ -663,7 +788,7 @@ static void PerformRefresh(HWND hwnd, APPSTATE *state)
SetStatusText(hwnd, "Loading schedule...");
if (!HttpRequestText("GET", scheduleUrl, NULL, authHeader, response, sizeof(response))) {
SetOutputText(hwnd, "Unable to load daily schedule. Check that transit_api is running on 192.168.68.66:5001.");
SetOutputText(hwnd, "Driver service unreachable. Please try again.");
SetStatusText(hwnd, "Schedule load failed");
return;
}
@@ -704,14 +829,14 @@ static void PerformLoadStops(HWND hwnd, APPSTATE *state)
SetStatusText(hwnd, "Loading stop details...");
if (!HttpRequestText("GET", stopsUrl, NULL, authHeader, response, sizeof(response))) {
SetOutputText(hwnd, "Unable to load stops for this route. Check the transit API connection.");
SetOutputText(hwnd, "Driver service unreachable. Please try again.");
SetStatusText(hwnd, "Stop details failed");
return;
}
ExtractStopsSummary(response, state->lastText, sizeof(state->lastText));
SetOutputText(hwnd, state->lastText);
SetStatusText(hwnd, "Stop details loaded");
ExtractStopsSummary(response, state, state->lastText, sizeof(state->lastText));
ShowStartScreen(hwnd, state);
UpdateHaltButton(hwnd, state);
}
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
@@ -723,22 +848,26 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
case WM_CREATE:
{
LPCREATESTRUCT cs = (LPCREATESTRUCT)lParam;
SYSTEMTIME now;
char today[16];
state = (APPSTATE *)calloc(1, sizeof(APPSTATE));
state->hwnd = hwnd;
cs->lpCreateParams = state;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)state);
GetLocalTime(&now);
snprintf(today, sizeof(today), "%04d-%02d-%02d", now.wYear, now.wMonth, now.wDay);
CreateWindowExA(0, "STATIC", "TRANSIT DRIVER CONSOLE",
CreateWindowExA(0, "STATIC", "DRIVER LOGIN",
WS_CHILD | WS_VISIBLE,
20, 10, 330, 24,
hwnd, NULL, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
hwnd, (HMENU)ID_PAGE_TITLE, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
CreateWindowExA(0, "STATIC", "Schedule date (YYYY-MM-DD)",
WS_CHILD | WS_VISIBLE,
20, 38, 150, 20,
hwnd, NULL, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
hwnd, (HMENU)ID_DATE_LABEL, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
CreateWindowExA(0, "EDIT", "2026-09-17",
CreateWindowExA(0, "EDIT", today,
WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
175, 36, 130, 24,
hwnd, (HMENU)ID_DATE, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
@@ -746,7 +875,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
CreateWindowExA(0, "STATIC", "User code",
WS_CHILD | WS_VISIBLE,
20, 72, 90, 20,
hwnd, NULL, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
hwnd, (HMENU)ID_USER_LABEL, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
hUser = CreateWindowExA(0, "EDIT", "",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
@@ -757,7 +886,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
CreateWindowExA(0, "STATIC", "Password",
WS_CHILD | WS_VISIBLE,
20, 104, 90, 20,
hwnd, NULL, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
hwnd, (HMENU)ID_PASSWORD_LABEL, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
hPassword = CreateWindowExA(0, "EDIT", "",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_PASSWORD | ES_AUTOHSCROLL,
@@ -782,7 +911,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
CreateWindowExA(0, "STATIC", "TODAY'S ROUTES",
WS_CHILD | WS_VISIBLE,
20, 208, 200, 20,
hwnd, NULL, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
hwnd, (HMENU)ID_ROUTES_LABEL, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
CreateWindowExA(WS_EX_CLIENTEDGE, "LISTBOX", "",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | LBS_NOTIFY | WS_VSCROLL,
@@ -799,7 +928,18 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
20, 545, 190, 30,
hwnd, (HMENU)ID_STOPS, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
CreateWindowExA(0, "BUTTON", "HALT",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_OWNERDRAW,
145, 120, 250, 250,
hwnd, (HMENU)ID_HALT, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
CreateWindowExA(0, "BUTTON", "Back to today's routes",
WS_CHILD | WS_VISIBLE | WS_TABSTOP,
20, 545, 190, 30,
hwnd, (HMENU)ID_BACK, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
SetWindowTextA(hOutput, "Driver console ready.\r\nLog in, choose a date, and refresh the schedule.");
ShowLoginScreen(hwnd);
return 0;
}
@@ -821,6 +961,35 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
PerformLoadStops(hwnd, state);
return 0;
}
if (LOWORD(wParam) == ID_BACK && HIWORD(wParam) == BN_CLICKED) {
state = (APPSTATE *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
ShowRouteScreen(hwnd);
SetStatusText(hwnd, "Today's routes");
return 0;
}
if (LOWORD(wParam) == ID_HALT && HIWORD(wParam) == BN_CLICKED) {
state = (APPSTATE *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
if (state->activeStop < state->stopCount) {
state->activeStop++;
UpdateHaltButton(hwnd, state);
}
return 0;
}
if (LOWORD(wParam) == ID_ROUTES && HIWORD(wParam) == LBN_SELCHANGE) {
state = (APPSTATE *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
PerformLoadStops(hwnd, state);
return 0;
}
break;
case WM_DRAWITEM:
if (wParam == ID_HALT) {
DrawHaltButton((DRAWITEMSTRUCT *)lParam);
return TRUE;
}
break;
case WM_DESTROY:
Binary file not shown.
+25 -1
View File
@@ -1,11 +1,12 @@
; Transit Driver XP custom installer
!include "MUI2.nsh"
!include "WinVer.nsh"
!define APPNAME "Transit Driver XP"
!define COMPANYNAME "Transit Project"
!define DESCRIPTION "Native Windows XP-compatible driver app"
!define VERSIONMAJOR 1
!define VERSIONMINOR 0
!define VERSIONMINOR 1
!define VERSIONBUILD 0
Name "${APPNAME}"
@@ -14,12 +15,35 @@ InstallDir "$PROGRAMFILES\TransitDriverXP"
InstallDirRegKey HKCU "Software\TransitDriverXP" "InstallDir"
RequestExecutionLevel admin
VIProductVersion "${VERSIONMAJOR}.${VERSIONMINOR}.${VERSIONBUILD}.0"
VIAddVersionKey "ProductName" "${APPNAME}"
VIAddVersionKey "CompanyName" "${COMPANYNAME}"
VIAddVersionKey "LegalCopyright" "Transit Project"
VIAddVersionKey "FileDescription" "${DESCRIPTION}"
VIAddVersionKey "FileVersion" "${VERSIONMAJOR}.${VERSIONMINOR}.${VERSIONBUILD}"
VIAddVersionKey "ProductVersion" "${VERSIONMAJOR}.${VERSIONMINOR}.${VERSIONBUILD}"
Function .onInit
ReadRegStr $0 HKCU "Software\TransitDriverXP" "InstallDir"
StrCmp $0 "" done
IfFileExists "$0\driver_xp.exe" 0 done
StrCpy $INSTDIR $0
MessageBox MB_OK|MB_ICONINFORMATION "An existing Transit Driver XP installation was found. It will be updated in $INSTDIR."
; Release the old executable before NSIS replaces it.
ExecWait '"$SYSDIR\taskkill.exe" /F /IM driver_xp.exe'
done:
FunctionEnd
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
Section "Install"
SetOutPath "$INSTDIR"
SetOverwrite on
File "driver_xp.exe"
File "README.md"