1051 lines
33 KiB
C
1051 lines
33 KiB
C
#define _WIN32_WINNT 0x0501
|
|
|
|
#include <windows.h>
|
|
#include <winhttp.h>
|
|
#include <commctrl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define ID_USERNAME 101
|
|
#define ID_PASSWORD 102
|
|
#define ID_LOGIN 103
|
|
#define ID_REFRESH 104
|
|
#define ID_OUTPUT 105
|
|
#define ID_STATUS 106
|
|
#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"
|
|
#define AUTH_PORT 5000
|
|
#define TRANSIT_PORT 5001
|
|
|
|
typedef struct {
|
|
HWND hwnd;
|
|
char token[512];
|
|
char lastText[8192];
|
|
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)
|
|
{
|
|
SetWindowTextA(GetDlgItem(hwnd, ID_STATUS), text);
|
|
}
|
|
|
|
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);
|
|
while (len > 0 && (text[len - 1] == '\n' || text[len - 1] == '\r' || text[len - 1] == ' ')) {
|
|
text[len - 1] = '\0';
|
|
len--;
|
|
}
|
|
}
|
|
|
|
static void ExtractJsonFieldString(const char *json,
|
|
const char *field,
|
|
char *out,
|
|
size_t outSize)
|
|
{
|
|
char pattern[128];
|
|
const char *key;
|
|
const char *colon;
|
|
const char *value;
|
|
const char *end;
|
|
|
|
out[0] = '\0';
|
|
if (!json || !field || outSize == 0) {
|
|
return;
|
|
}
|
|
|
|
snprintf(pattern, sizeof(pattern), "\"%s\"", field);
|
|
key = strstr(json, pattern);
|
|
if (!key) {
|
|
return;
|
|
}
|
|
|
|
colon = strchr(key + strlen(pattern), ':');
|
|
if (!colon) {
|
|
return;
|
|
}
|
|
|
|
value = colon + 1;
|
|
while (*value == ' ' || *value == '\t' || *value == '\n' || *value == '\r') {
|
|
value++;
|
|
}
|
|
|
|
if (*value == '"') {
|
|
const char *q1 = value + 1;
|
|
const char *q2 = strchr(q1, '"');
|
|
size_t len;
|
|
|
|
if (!q2) {
|
|
return;
|
|
}
|
|
|
|
len = (size_t)(q2 - q1);
|
|
if (len >= outSize) {
|
|
len = outSize - 1;
|
|
}
|
|
memcpy(out, q1, len);
|
|
out[len] = '\0';
|
|
return;
|
|
}
|
|
|
|
end = value;
|
|
while (*end && *end != ',' && *end != '}' && *end != '\n' && *end != '\r') {
|
|
end++;
|
|
}
|
|
|
|
if (end == value) {
|
|
return;
|
|
}
|
|
|
|
size_t len = (size_t)(end - value);
|
|
while (len > 0 && (value[len - 1] == ' ' || value[len - 1] == '\t' || value[len - 1] == '\r' || value[len - 1] == '\n')) {
|
|
len--;
|
|
}
|
|
|
|
if (len >= outSize) {
|
|
len = outSize - 1;
|
|
}
|
|
|
|
memcpy(out, value, len);
|
|
out[len] = '\0';
|
|
}
|
|
|
|
static char *FindObjectEnd(char *start)
|
|
{
|
|
int depth = 0;
|
|
char *p = start;
|
|
|
|
while (*p) {
|
|
if (*p == '{') {
|
|
depth++;
|
|
} else if (*p == '}') {
|
|
depth--;
|
|
if (depth == 0) {
|
|
return p;
|
|
}
|
|
}
|
|
p++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void ExtractScheduleSummary(const char *json, APPSTATE *state, char *out, size_t outSize)
|
|
{
|
|
const char *routesKey;
|
|
const char *p;
|
|
char routeSummary[2048];
|
|
char summary[8192];
|
|
int routeCount = 0;
|
|
|
|
out[0] = '\0';
|
|
summary[0] = '\0';
|
|
routeSummary[0] = '\0';
|
|
state->routeCount = 0;
|
|
SendMessage(GetDlgItem(state->hwnd, ID_ROUTES), LB_RESETCONTENT, 0, 0);
|
|
|
|
routesKey = strstr(json, "\"routes\"");
|
|
if (!routesKey) {
|
|
snprintf(out, outSize, "No routes found for this date.");
|
|
return;
|
|
}
|
|
|
|
p = strchr(routesKey, '[');
|
|
if (!p) {
|
|
snprintf(out, outSize, "No routes found for this date.");
|
|
return;
|
|
}
|
|
|
|
p++;
|
|
while (*p) {
|
|
char *objStart = strchr((char *)p, '{');
|
|
char *objEnd;
|
|
char routeText[2048];
|
|
char routeLine[128];
|
|
char routeStatus[128];
|
|
char routeStart[128];
|
|
char routeEnd[128];
|
|
char routeStartStop[128];
|
|
char routeEndStop[128];
|
|
char routeLineId[32];
|
|
char routeStartTime[128];
|
|
|
|
if (!objStart) {
|
|
break;
|
|
}
|
|
|
|
objEnd = FindObjectEnd(objStart);
|
|
if (!objEnd) {
|
|
break;
|
|
}
|
|
|
|
if ((size_t)(objEnd - objStart + 1) >= sizeof(routeText)) {
|
|
break;
|
|
}
|
|
|
|
memcpy(routeText, objStart, objEnd - objStart + 1);
|
|
routeText[objEnd - objStart + 1] = '\0';
|
|
|
|
routeLine[0] = '\0';
|
|
routeStatus[0] = '\0';
|
|
routeStart[0] = '\0';
|
|
routeEnd[0] = '\0';
|
|
routeStartStop[0] = '\0';
|
|
routeEndStop[0] = '\0';
|
|
routeLineId[0] = '\0';
|
|
routeStartTime[0] = '\0';
|
|
|
|
ExtractJsonFieldString(routeText, "lineNumber", routeLine, sizeof(routeLine));
|
|
ExtractJsonFieldString(routeText, "startTime", routeStartTime, sizeof(routeStartTime));
|
|
ExtractJsonFieldString(routeText, "id", routeLineId, sizeof(routeLineId));
|
|
if (routeLine[0] == '\0' || routeLineId[0] == '\0') {
|
|
char busLine[1024];
|
|
const char *busKey = strstr(routeText, "\"busLine\"");
|
|
if (busKey) {
|
|
const char *busStart = strchr(busKey, '{');
|
|
if (busStart) {
|
|
char *busEnd = FindObjectEnd((char *)busStart);
|
|
if (busEnd) {
|
|
size_t busLen = (size_t)(busEnd - busStart + 1);
|
|
if (busLen < sizeof(busLine)) {
|
|
memcpy(busLine, busStart, busLen);
|
|
busLine[busLen] = '\0';
|
|
ExtractJsonFieldString(busLine, "lineNumber", routeLine, sizeof(routeLine));
|
|
ExtractJsonFieldString(busLine, "id", routeLineId, sizeof(routeLineId));
|
|
ExtractJsonFieldString(busLine, "startStop", routeStartStop, sizeof(routeStartStop));
|
|
ExtractJsonFieldString(busLine, "endStop", routeEndStop, sizeof(routeEndStop));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ExtractJsonFieldString(routeText, "status", routeStatus, sizeof(routeStatus));
|
|
ExtractJsonFieldString(routeText, "startTime", routeStart, sizeof(routeStart));
|
|
ExtractJsonFieldString(routeText, "endTime", routeEnd, sizeof(routeEnd));
|
|
if (routeStartStop[0] == '\0') {
|
|
ExtractJsonFieldString(routeText, "startStop", routeStartStop, sizeof(routeStartStop));
|
|
}
|
|
if (routeEndStop[0] == '\0') {
|
|
ExtractJsonFieldString(routeText, "endStop", routeEndStop, sizeof(routeEndStop));
|
|
}
|
|
|
|
if (routeLine[0] == '\0') {
|
|
snprintf(routeLine, sizeof(routeLine), "R%d", routeCount + 1);
|
|
}
|
|
if (routeStatus[0] == '\0') {
|
|
snprintf(routeStatus, sizeof(routeStatus), "planned");
|
|
}
|
|
if (routeStartStop[0] == '\0') {
|
|
snprintf(routeStartStop, sizeof(routeStartStop), "N/A");
|
|
}
|
|
if (routeEndStop[0] == '\0') {
|
|
snprintf(routeEndStop, sizeof(routeEndStop), "N/A");
|
|
}
|
|
|
|
routeCount++;
|
|
if (state->routeCount < 32) {
|
|
state->routeLineIds[state->routeCount] = atoi(routeLineId);
|
|
snprintf(state->routeLabels[state->routeCount], sizeof(state->routeLabels[state->routeCount]),
|
|
"Line %s %s %s", routeLine, routeStart, routeStatus);
|
|
SendMessageA(GetDlgItem(state->hwnd, ID_ROUTES), LB_ADDSTRING, 0,
|
|
(LPARAM)state->routeLabels[state->routeCount]);
|
|
state->routeCount++;
|
|
}
|
|
snprintf(routeSummary + strlen(routeSummary), sizeof(routeSummary) - strlen(routeSummary),
|
|
"Route %s: %s -> %s | %s | %s\r\n",
|
|
routeLine,
|
|
routeStartStop,
|
|
routeEndStop,
|
|
routeStart,
|
|
routeStatus);
|
|
|
|
p = objEnd + 1;
|
|
while (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t' || *p == ',') {
|
|
p++;
|
|
}
|
|
|
|
if (*p == ']' || *p == '\0') {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (routeCount == 0) {
|
|
snprintf(out, outSize, "No routes found for this date.");
|
|
return;
|
|
}
|
|
|
|
snprintf(summary, sizeof(summary), "Daily schedule (%d route%s)\r\n\r\n%s",
|
|
routeCount, routeCount == 1 ? "" : "s",
|
|
routeSummary);
|
|
|
|
TrimNewlines(summary);
|
|
snprintf(out, outSize, "%s", summary);
|
|
}
|
|
|
|
static void ExtractStopsSummary(const char *json, APPSTATE *state, char *out, size_t outSize)
|
|
{
|
|
const char *stopsKey = strstr(json, "\"stops\"");
|
|
const char *p;
|
|
char summary[4096];
|
|
int count = 0;
|
|
|
|
summary[0] = '\0';
|
|
if (!stopsKey || !(p = strchr(stopsKey, '['))) {
|
|
snprintf(out, outSize, "No stop details were returned for this route.");
|
|
return;
|
|
}
|
|
|
|
p++;
|
|
while (*p && *p != ']') {
|
|
char *start = strchr((char *)p, '{');
|
|
char *end;
|
|
char item[1024];
|
|
char order[32];
|
|
char name[128];
|
|
char eta[32];
|
|
|
|
if (!start || !(end = FindObjectEnd(start))) {
|
|
break;
|
|
}
|
|
if ((size_t)(end - start + 1) >= sizeof(item)) {
|
|
break;
|
|
}
|
|
|
|
memcpy(item, start, end - start + 1);
|
|
item[end - start + 1] = '\0';
|
|
ExtractJsonFieldString(item, "order", order, sizeof(order));
|
|
ExtractJsonFieldString(item, "name", name, sizeof(name));
|
|
ExtractJsonFieldString(item, "estimatedArrivalMinutes", eta, sizeof(eta));
|
|
if (name[0] == '\0') {
|
|
snprintf(name, sizeof(name), "Unnamed stop");
|
|
}
|
|
if (eta[0] == '\0') {
|
|
snprintf(eta, sizeof(eta), "-");
|
|
}
|
|
|
|
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 == ',') {
|
|
p++;
|
|
}
|
|
}
|
|
|
|
if (count == 0) {
|
|
snprintf(out, outSize, "No stop details were returned for this route.");
|
|
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);
|
|
}
|
|
|
|
static void ExtractTokenFromJson(const char *json, char *outToken, size_t outSize)
|
|
{
|
|
const char *tokenKey = strstr(json, "\"token\"");
|
|
const char *q1;
|
|
const char *q2;
|
|
|
|
outToken[0] = '\0';
|
|
if (!tokenKey) {
|
|
return;
|
|
}
|
|
|
|
q1 = strchr(tokenKey, ':');
|
|
if (!q1) {
|
|
return;
|
|
}
|
|
q1 = strchr(q1 + 1, '"');
|
|
if (!q1) {
|
|
return;
|
|
}
|
|
q2 = strchr(q1 + 1, '"');
|
|
if (!q2) {
|
|
return;
|
|
}
|
|
|
|
q1++;
|
|
if ((size_t)(q2 - q1) >= outSize) {
|
|
strncpy(outToken, q1, outSize - 1);
|
|
outToken[outSize - 1] = '\0';
|
|
return;
|
|
}
|
|
|
|
memcpy(outToken, q1, q2 - q1);
|
|
outToken[q2 - q1] = '\0';
|
|
}
|
|
|
|
static void HttpErrorMessage(const char *context, DWORD error)
|
|
{
|
|
(void)context;
|
|
(void)error;
|
|
}
|
|
|
|
static int HttpRequestText(const char *method,
|
|
const char *url,
|
|
const char *body,
|
|
const char *authHeader,
|
|
char *response,
|
|
size_t responseSize)
|
|
{
|
|
HINTERNET hSession = NULL;
|
|
HINTERNET hConnect = NULL;
|
|
HINTERNET hRequest = NULL;
|
|
wchar_t hostW[256];
|
|
wchar_t pathW[512];
|
|
wchar_t methodW[32];
|
|
char host[256];
|
|
char path[512];
|
|
const char *src;
|
|
const char *slash;
|
|
const char *colon;
|
|
int port = 80;
|
|
DWORD flags = 0;
|
|
DWORD status = 0;
|
|
DWORD statusLen = sizeof(status);
|
|
DWORD bytesRead = 0;
|
|
DWORD totalRead = 0;
|
|
char buffer[MAX_BUF];
|
|
int ok = 0;
|
|
|
|
response[0] = '\0';
|
|
|
|
src = url;
|
|
if (strncmp(src, "http://", 7) == 0) {
|
|
src += 7;
|
|
} else if (strncmp(src, "https://", 8) == 0) {
|
|
src += 8;
|
|
}
|
|
|
|
host[0] = '\0';
|
|
path[0] = '/';
|
|
path[1] = '\0';
|
|
|
|
slash = strchr(src, '/');
|
|
colon = strchr(src, ':');
|
|
|
|
if (slash) {
|
|
size_t hostLen = (size_t)(slash - src);
|
|
if (hostLen >= sizeof(host)) {
|
|
hostLen = sizeof(host) - 1;
|
|
}
|
|
memcpy(host, src, hostLen);
|
|
host[hostLen] = '\0';
|
|
snprintf(path, sizeof(path), "%.*s", (int)(strlen(slash)), slash);
|
|
} else if (colon) {
|
|
size_t hostLen = (size_t)(colon - src);
|
|
if (hostLen >= sizeof(host)) {
|
|
hostLen = sizeof(host) - 1;
|
|
}
|
|
memcpy(host, src, hostLen);
|
|
host[hostLen] = '\0';
|
|
port = atoi(colon + 1);
|
|
snprintf(path, sizeof(path), "/");
|
|
} else {
|
|
snprintf(host, sizeof(host), "%s", src);
|
|
snprintf(path, sizeof(path), "/");
|
|
}
|
|
|
|
if (colon && slash) {
|
|
size_t hostLen = (size_t)(colon - src);
|
|
if (hostLen >= sizeof(host)) {
|
|
hostLen = sizeof(host) - 1;
|
|
}
|
|
memcpy(host, src, hostLen);
|
|
host[hostLen] = '\0';
|
|
port = atoi(colon + 1);
|
|
}
|
|
|
|
if (host[0] == '\0') {
|
|
snprintf(host, sizeof(host), "192.168.68.66");
|
|
}
|
|
|
|
if (port == 0) {
|
|
port = 80;
|
|
}
|
|
|
|
MultiByteToWideChar(CP_UTF8, 0, host, -1, hostW, 256);
|
|
MultiByteToWideChar(CP_UTF8, 0, path, -1, pathW, 512);
|
|
MultiByteToWideChar(CP_UTF8, 0, method, -1, methodW, 32);
|
|
|
|
hSession = WinHttpOpen(L"TransitDriverXP/1.0",
|
|
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
|
WINHTTP_NO_PROXY_NAME,
|
|
WINHTTP_NO_PROXY_BYPASS,
|
|
0);
|
|
if (!hSession) {
|
|
HttpErrorMessage("Unable to open WinHTTP session", GetLastError());
|
|
return 0;
|
|
}
|
|
|
|
hConnect = WinHttpConnect(hSession, hostW, port, 0);
|
|
if (!hConnect) {
|
|
char hostText[256];
|
|
char errorText[512];
|
|
WideCharToMultiByte(CP_UTF8, 0, hostW, -1, hostText, sizeof(hostText), NULL, NULL);
|
|
snprintf(errorText, sizeof(errorText), "Unable to connect to %s:%d", hostText, port);
|
|
HttpErrorMessage(errorText, GetLastError());
|
|
goto cleanup;
|
|
}
|
|
|
|
hRequest = WinHttpOpenRequest(hConnect,
|
|
methodW,
|
|
pathW,
|
|
NULL,
|
|
WINHTTP_NO_REFERER,
|
|
WINHTTP_DEFAULT_ACCEPT_TYPES,
|
|
0);
|
|
if (!hRequest) {
|
|
HttpErrorMessage("Unable to open request", GetLastError());
|
|
goto cleanup;
|
|
}
|
|
|
|
if (body != NULL && body[0] != '\0') {
|
|
if (!WinHttpAddRequestHeaders(hRequest,
|
|
L"Content-Type: application/json\r\n",
|
|
-1,
|
|
WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
|
|
HttpErrorMessage("Unable to add JSON content type", GetLastError());
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (authHeader && authHeader[0] != '\0') {
|
|
wchar_t wHeader[512];
|
|
MultiByteToWideChar(CP_UTF8, 0, authHeader, -1, wHeader, 512);
|
|
if (!WinHttpAddRequestHeaders(hRequest,
|
|
wHeader,
|
|
-1,
|
|
WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
|
|
HttpErrorMessage("Unable to add authorization header", GetLastError());
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (!WinHttpSendRequest(hRequest,
|
|
WINHTTP_NO_ADDITIONAL_HEADERS,
|
|
0,
|
|
(LPVOID)body,
|
|
body ? (DWORD)strlen(body) : 0,
|
|
body ? (DWORD)strlen(body) : 0,
|
|
0)) {
|
|
HttpErrorMessage("WinHTTP send request failed", GetLastError());
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!WinHttpReceiveResponse(hRequest, NULL)) {
|
|
HttpErrorMessage("WinHTTP receive response failed", GetLastError());
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!WinHttpQueryHeaders(hRequest,
|
|
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
|
|
WINHTTP_HEADER_NAME_BY_INDEX,
|
|
&status,
|
|
&statusLen,
|
|
WINHTTP_NO_HEADER_INDEX)) {
|
|
HttpErrorMessage("Unable to query HTTP status", GetLastError());
|
|
goto cleanup;
|
|
}
|
|
|
|
if (status >= 400) {
|
|
snprintf(response, responseSize, "HTTP error %lu", status);
|
|
ok = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
while (WinHttpQueryDataAvailable(hRequest, &bytesRead) && bytesRead > 0) {
|
|
DWORD actual = 0;
|
|
if (!WinHttpReadData(hRequest, buffer, sizeof(buffer) - 1, &actual)) {
|
|
HttpErrorMessage("Unable to read response data", GetLastError());
|
|
goto cleanup;
|
|
}
|
|
if (actual == 0) {
|
|
break;
|
|
}
|
|
buffer[actual] = '\0';
|
|
if (totalRead + actual + 1 < responseSize) {
|
|
memcpy(response + totalRead, buffer, actual);
|
|
totalRead += actual;
|
|
response[totalRead] = '\0';
|
|
}
|
|
}
|
|
|
|
ok = 1;
|
|
|
|
cleanup:
|
|
if (hRequest) {
|
|
WinHttpCloseHandle(hRequest);
|
|
}
|
|
if (hConnect) {
|
|
WinHttpCloseHandle(hConnect);
|
|
}
|
|
if (hSession) {
|
|
WinHttpCloseHandle(hSession);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
static void PerformLogin(HWND hwnd, APPSTATE *state)
|
|
{
|
|
char userCode[128];
|
|
char password[128];
|
|
char payload[512];
|
|
char response[MAX_BUF];
|
|
char token[256];
|
|
|
|
GetWindowTextA(GetDlgItem(hwnd, ID_USERNAME), userCode, sizeof(userCode));
|
|
GetWindowTextA(GetDlgItem(hwnd, ID_PASSWORD), password, sizeof(password));
|
|
|
|
if (userCode[0] == '\0' || password[0] == '\0') {
|
|
SetStatusText(hwnd, "Enter user code and password.");
|
|
return;
|
|
}
|
|
|
|
snprintf(payload, sizeof(payload),
|
|
"{\"userCode\":\"%s\",\"password\":\"%s\"}",
|
|
userCode, password);
|
|
|
|
SetStatusText(hwnd, "Logging in...");
|
|
if (!HttpRequestText("POST", "http://192.168.68.66:5000/login", payload, NULL, response, sizeof(response))) {
|
|
SetOutputText(hwnd, "Driver service unreachable. Please try again.");
|
|
SetStatusText(hwnd, "Login failed");
|
|
return;
|
|
}
|
|
|
|
ExtractTokenFromJson(response, token, sizeof(token));
|
|
if (token[0] == '\0') {
|
|
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';
|
|
ShowRouteScreen(hwnd);
|
|
SetStatusText(hwnd, "Loading today's assigned routes...");
|
|
PerformRefresh(hwnd, state);
|
|
}
|
|
|
|
static void PerformRefresh(HWND hwnd, APPSTATE *state)
|
|
{
|
|
char response[MAX_BUF];
|
|
char authHeader[512];
|
|
char date[32];
|
|
char scheduleUrl[256];
|
|
|
|
if (state->token[0] == '\0') {
|
|
SetStatusText(hwnd, "Login first.");
|
|
return;
|
|
}
|
|
|
|
snprintf(authHeader, sizeof(authHeader), "Authorization: Bearer %s\r\n", state->token);
|
|
GetWindowTextA(GetDlgItem(hwnd, ID_DATE), date, sizeof(date));
|
|
if (date[0] == '\0') {
|
|
snprintf(date, sizeof(date), "2026-09-17");
|
|
}
|
|
snprintf(scheduleUrl, sizeof(scheduleUrl), "http://192.168.68.66:5001/schedule/daily?date=%s", date);
|
|
SetStatusText(hwnd, "Loading schedule...");
|
|
|
|
if (!HttpRequestText("GET", scheduleUrl, NULL, authHeader, response, sizeof(response))) {
|
|
SetOutputText(hwnd, "Driver service unreachable. Please try again.");
|
|
SetStatusText(hwnd, "Schedule load failed");
|
|
return;
|
|
}
|
|
|
|
ExtractScheduleSummary(response, state, state->lastText, sizeof(state->lastText));
|
|
SetOutputText(hwnd, state->lastText);
|
|
SetStatusText(hwnd, "Schedule loaded");
|
|
}
|
|
|
|
static void PerformLoadStops(HWND hwnd, APPSTATE *state)
|
|
{
|
|
char response[MAX_BUF];
|
|
char authHeader[512];
|
|
char stopsUrl[256];
|
|
LRESULT selected;
|
|
int lineId;
|
|
|
|
if (state->token[0] == '\0') {
|
|
SetStatusText(hwnd, "Login first.");
|
|
return;
|
|
}
|
|
|
|
selected = SendMessage(GetDlgItem(hwnd, ID_ROUTES), LB_GETCURSEL, 0, 0);
|
|
if (selected == LB_ERR || selected >= state->routeCount) {
|
|
SetStatusText(hwnd, "Select a route first.");
|
|
return;
|
|
}
|
|
|
|
lineId = state->routeLineIds[selected];
|
|
if (lineId <= 0) {
|
|
SetOutputText(hwnd, "This schedule does not contain a valid bus line ID.");
|
|
SetStatusText(hwnd, "Stop details unavailable");
|
|
return;
|
|
}
|
|
|
|
snprintf(authHeader, sizeof(authHeader), "Authorization: Bearer %s\r\n", state->token);
|
|
snprintf(stopsUrl, sizeof(stopsUrl), "http://192.168.68.66:5001/bus-lines/%d/stops", lineId);
|
|
SetStatusText(hwnd, "Loading stop details...");
|
|
|
|
if (!HttpRequestText("GET", stopsUrl, NULL, authHeader, response, sizeof(response))) {
|
|
SetOutputText(hwnd, "Driver service unreachable. Please try again.");
|
|
SetStatusText(hwnd, "Stop details failed");
|
|
return;
|
|
}
|
|
|
|
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)
|
|
{
|
|
static APPSTATE *state = NULL;
|
|
HWND hUser, hPassword, hLogin, hRefresh, hOutput, hStatus;
|
|
|
|
switch (msg) {
|
|
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", "DRIVER LOGIN",
|
|
WS_CHILD | WS_VISIBLE,
|
|
20, 10, 330, 24,
|
|
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, (HMENU)ID_DATE_LABEL, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
|
|
|
|
CreateWindowExA(0, "EDIT", today,
|
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
|
|
175, 36, 130, 24,
|
|
hwnd, (HMENU)ID_DATE, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
|
|
|
|
CreateWindowExA(0, "STATIC", "User code",
|
|
WS_CHILD | WS_VISIBLE,
|
|
20, 72, 90, 20,
|
|
hwnd, (HMENU)ID_USER_LABEL, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
|
|
|
|
hUser = CreateWindowExA(0, "EDIT", "",
|
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
|
|
120, 70, 180, 24,
|
|
hwnd, (HMENU)ID_USERNAME, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
|
|
SetFocus(hUser);
|
|
|
|
CreateWindowExA(0, "STATIC", "Password",
|
|
WS_CHILD | WS_VISIBLE,
|
|
20, 104, 90, 20,
|
|
hwnd, (HMENU)ID_PASSWORD_LABEL, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
|
|
|
|
hPassword = CreateWindowExA(0, "EDIT", "",
|
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_PASSWORD | ES_AUTOHSCROLL,
|
|
120, 102, 180, 24,
|
|
hwnd, (HMENU)ID_PASSWORD, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
|
|
|
|
hLogin = CreateWindowExA(0, "BUTTON", "Login",
|
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP,
|
|
120, 138, 90, 30,
|
|
hwnd, (HMENU)ID_LOGIN, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
|
|
|
|
hRefresh = CreateWindowExA(0, "BUTTON", "Refresh schedule",
|
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP,
|
|
220, 138, 130, 30,
|
|
hwnd, (HMENU)ID_REFRESH, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
|
|
|
|
hStatus = CreateWindowExA(0, "STATIC", "Idle",
|
|
WS_CHILD | WS_VISIBLE,
|
|
20, 178, 500, 22,
|
|
hwnd, (HMENU)ID_STATUS, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
|
|
|
|
CreateWindowExA(0, "STATIC", "TODAY'S ROUTES",
|
|
WS_CHILD | WS_VISIBLE,
|
|
20, 208, 200, 20,
|
|
hwnd, (HMENU)ID_ROUTES_LABEL, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
|
|
|
|
CreateWindowExA(WS_EX_CLIENTEDGE, "LISTBOX", "",
|
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | LBS_NOTIFY | WS_VSCROLL,
|
|
20, 230, 500, 105,
|
|
hwnd, (HMENU)ID_ROUTES, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
|
|
|
|
hOutput = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "",
|
|
WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_READONLY,
|
|
20, 345, 500, 190,
|
|
hwnd, (HMENU)ID_OUTPUT, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
|
|
|
|
CreateWindowExA(0, "BUTTON", "Show selected route stops",
|
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP,
|
|
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;
|
|
}
|
|
|
|
case WM_COMMAND:
|
|
if (LOWORD(wParam) == ID_LOGIN && HIWORD(wParam) == BN_CLICKED) {
|
|
state = (APPSTATE *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
PerformLogin(hwnd, state);
|
|
return 0;
|
|
}
|
|
|
|
if (LOWORD(wParam) == ID_REFRESH && HIWORD(wParam) == BN_CLICKED) {
|
|
state = (APPSTATE *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
PerformRefresh(hwnd, state);
|
|
return 0;
|
|
}
|
|
|
|
if (LOWORD(wParam) == ID_STOPS && HIWORD(wParam) == BN_CLICKED) {
|
|
state = (APPSTATE *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
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:
|
|
PostQuitMessage(0);
|
|
return 0;
|
|
}
|
|
|
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
|
}
|
|
|
|
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
|
{
|
|
WNDCLASSA wc;
|
|
HWND hwnd;
|
|
MSG msg;
|
|
|
|
memset(&wc, 0, sizeof(wc));
|
|
wc.lpfnWndProc = WndProc;
|
|
wc.hInstance = hInstance;
|
|
wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
|
|
wc.lpszClassName = "TransitDriverXPClass";
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
|
|
wc.style = CS_HREDRAW | CS_VREDRAW;
|
|
|
|
if (!RegisterClassA(&wc)) {
|
|
MessageBoxA(NULL, "Failed to register window class.", "XP Driver App", MB_ICONERROR | MB_OK);
|
|
return 1;
|
|
}
|
|
|
|
hwnd = CreateWindowExA(0,
|
|
"TransitDriverXPClass",
|
|
"Transit Driver XP",
|
|
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
560,
|
|
640,
|
|
NULL,
|
|
NULL,
|
|
hInstance,
|
|
NULL);
|
|
|
|
if (!hwnd) {
|
|
MessageBoxA(NULL, "Failed to create window.", "XP Driver App", MB_ICONERROR | MB_OK);
|
|
return 1;
|
|
}
|
|
|
|
ShowWindow(hwnd, nCmdShow);
|
|
UpdateWindow(hwnd);
|
|
|
|
while (GetMessage(&msg, NULL, 0, 0)) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
|
|
return (int)msg.wParam;
|
|
}
|