diff --git a/windows_xp_driver_ui/README.md b/windows_xp_driver_ui/README.md index 7c6373b..e65c114 100644 --- a/windows_xp_driver_ui/README.md +++ b/windows_xp_driver_ui/README.md @@ -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. diff --git a/windows_xp_driver_ui/TransitDriverXPInstaller.exe b/windows_xp_driver_ui/TransitDriverXPInstaller.exe deleted file mode 100644 index 9087967..0000000 Binary files a/windows_xp_driver_ui/TransitDriverXPInstaller.exe and /dev/null differ diff --git a/windows_xp_driver_ui/TransitDriverXPSetup.exe b/windows_xp_driver_ui/TransitDriverXPSetup.exe index efcaf00..cefe9f9 100644 Binary files a/windows_xp_driver_ui/TransitDriverXPSetup.exe and b/windows_xp_driver_ui/TransitDriverXPSetup.exe differ diff --git a/windows_xp_driver_ui/driver_xp.c b/windows_xp_driver_ui/driver_xp.c index ff2b592..866c6ac 100644 --- a/windows_xp_driver_ui/driver_xp.c +++ b/windows_xp_driver_ui/driver_xp.c @@ -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); + 20, 38, 150, 20, + 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: diff --git a/windows_xp_driver_ui/driver_xp.exe b/windows_xp_driver_ui/driver_xp.exe index c5a6add..5a7bf00 100755 Binary files a/windows_xp_driver_ui/driver_xp.exe and b/windows_xp_driver_ui/driver_xp.exe differ diff --git a/windows_xp_driver_ui/installer.nsi b/windows_xp_driver_ui/installer.nsi index a53ac73..e791e02 100644 --- a/windows_xp_driver_ui/installer.nsi +++ b/windows_xp_driver_ui/installer.nsi @@ -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"