kpmpgsmkii/client/src/linux/linux.c
2021-11-29 20:28:32 -06:00

604 lines
13 KiB
C

/*
* Kangaroo Punch MultiPlayer Game Server Mark II
* Copyright (C) 2020-2021 Scott Duensing
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include <SDL2/SDL.h>
#define ENET_IMPLEMENTATION
#include "thirdparty/enet/include/enet.h"
#include "os.h"
#include "mouse.h"
#include "vesa.h"
#include "surface.h"
#include "comport.h"
#define SECONDS_IN_DAY 86400
#define TICKS_PER_SECOND 18.2
#define TICKS_PER_DAY (SECONDS_IN_DAY * TICKS_PER_SECOND)
#define TIMER_INTERVAL (1000 / TICKS_PER_SECOND)
#define COM_BUFFER_SIZE 2048
#define MODEM_RESULT_OK 1
#define MODEM_RESULT_ERROR 2
static MouseT _mouse;
static SDL_Window *_window = NULL;
static SDL_Renderer *_renderer = NULL;
static SDL_Surface *_surface = NULL;
static SDL_Texture *_texture = NULL;
static uint16_t _width = 0;
static uint16_t _height = 0;
static uint8_t _windowScale = 1;
static uint8_t _alt = 0;
static uint8_t _control = 0;
static uint8_t _shift = 0;
static uint8_t _ASCII = 0;
static uint8_t _scanCode = 0;
static uint8_t _extended = 0;
static uint8_t _ASCIIKeep = 0;
static uint8_t _scanCodeKeep = 0;
static uint8_t _keyPressed = 0;
static uint8_t _debounce = 0;
static long _timerTicks = 0;
static SDL_TimerID _timerID;
static ENetHost *_host = NULL;
static ENetPeer *_peer = NULL;
static ENetAddress _address;
static uint8_t _comPortOpen = 0;
static uint8_t _modemCommandMode = 1;
static char _buffer[COM_BUFFER_SIZE];
static uint16_t _bufferHead = 0;
static uint16_t _bufferTail = 0;
static uint8_t _connected = 0;
static void comAddToBuffer(char *data, uint16_t len);
static void comModem(uint8_t c);
static void processEvent(void);
static void processNetworkEvent(void);
long biostime(int cmd, long newtime) {
(void)cmd;
(void)newtime;
return _timerTicks;
}
static void comAddToBuffer(char *data, uint16_t len) {
uint16_t x;
for (x=0; x<len; x++) {
_buffer[_bufferHead++] = data[x];
if (_bufferHead >= COM_BUFFER_SIZE) _bufferHead = 0;
}
}
int comClose(int com) {
// We only pretend to support one COM port.
if (com != 0) return SER_ERR_UNKNOWN;
if (!_comPortOpen) return SER_ERR_NOT_OPEN;
if (_peer) {
enet_peer_reset(_peer);
_peer = NULL;
}
if (_host) {
enet_host_destroy(_host);
_host = NULL;
}
_comPortOpen = 0;
return SER_SUCCESS;
}
static void comModem(uint8_t c) {
static char command[COM_BUFFER_SIZE] = { 0 };
uint16_t x;
uint8_t result = 0;
uint8_t parsing = 1;
char *p = NULL;
if (c != 13) {
x = strlen(command);
// Room in buffer?
if (x > COM_BUFFER_SIZE - 1) {
// No. Error. Clear buffer.
snprintf(command, COM_BUFFER_SIZE, "%cERROR%c", 13, 13);
comAddToBuffer(command, strlen(command));
command[0] = 0;
} else {
// Add to command buffer.
command[x] = toupper(c);
command[x+1] = 0;
}
} else {
// AT?
if (command[0] != 'A' || command[1] != 'T') result = MODEM_RESULT_ERROR;
// Process command string.
x = 2;
while (parsing && !result && command[x]) {
switch (command[x]) {
// Vendor Commands
case '+':
x++;
// Select Socket Mode
if (strncmp(&command[x], "SOCK", 4) == 0) {
result = MODEM_RESULT_OK;
} else {
result = MODEM_RESULT_ERROR;
}
break;
// Dial
case 'D':
x++;
if (command[x] == 'T' || command[x] == 'P') x++;
// Find port, if any.
_address.port = 23;
if ((p = strstr(&command[x], ":"))) {
*p = 0;
p++;
_address.port = atoi(p);
}
if (enet_address_set_host(&_address, &command[x]) < 0) {
result = MODEM_RESULT_ERROR;
} else {
_host = enet_host_create(NULL, 1, 1, 0, 0);
if (!_host) {
result = MODEM_RESULT_ERROR;
} else {
_peer = enet_host_connect(_host, &_address, 1, 0);
if (!_peer) {
enet_host_destroy(_host);
_host = NULL;
result = MODEM_RESULT_ERROR;
} else {
result = MODEM_RESULT_OK;
}
}
}
_modemCommandMode = 0;
break;
// Dunno.
default:
result = MODEM_RESULT_ERROR;
break;
}
}
// Send result.
switch (result) {
case MODEM_RESULT_OK:
snprintf(command, COM_BUFFER_SIZE, "OK%c", 13);
comAddToBuffer(command, strlen(command));
break;
case MODEM_RESULT_ERROR:
snprintf(command, COM_BUFFER_SIZE, "ERROR%c", 13);
comAddToBuffer(command, strlen(command));
break;
}
// Clear buffer.
command[0] = 0;
}
}
int comOpen(int com, long bps, int dataBits, char parity, int stopBits, int handshaking) {
(void)bps;
(void)dataBits;
(void)parity;
(void)stopBits;
(void)handshaking;
// We only pretend to support one COM port.
if (com != 0) return SER_ERR_UNKNOWN;
if (_comPortOpen) return SER_ERR_ALREADY_OPEN;
_comPortOpen = 1;
_bufferHead = 0;
_bufferTail = 0;
// We really shouldn't do this, but enet is being stupid about not sending a DISCONNECT_TIMEOUT when failing to connect.
_modemCommandMode = 1;
return SER_SUCCESS;
}
int comRead(int com, char *data, int len) {
uint16_t x = 0;
// We only pretend to support one COM port.
if (com != 0) return SER_ERR_UNKNOWN;
if (!_comPortOpen) return SER_ERR_NOT_OPEN;
// Is the buffer empty?
if (_bufferHead == _bufferTail) {
return 0;
}
// Fill buffer.
while (x < len && _bufferHead != _bufferTail) {
data[x++] = _buffer[_bufferTail++];
if (_bufferTail >= COM_BUFFER_SIZE) _bufferTail = 0;
}
return x;
}
int comWrite(int com, const char *data, int len) {
uint16_t x = 0;
ENetPacket *packet = NULL;
// We only pretend to support one COM port.
if (com != 0) return SER_ERR_UNKNOWN;
if (!_comPortOpen) return SER_ERR_NOT_OPEN;
while (_modemCommandMode && x < len) {
comAddToBuffer((char *)&data[x], 1);
comModem(data[x++]);
}
if (!_modemCommandMode && _connected && len - x > 0) {
packet = enet_packet_create(&data[x], len - x, ENET_PACKET_FLAG_RELIABLE);
if (!packet) return SER_ERR_UNKNOWN;
enet_peer_send(_peer, 0, packet);
}
return SER_SUCCESS;
}
uint8_t keyAltGet(void) {
return _alt;
}
uint8_t keyASCIIGet(void) {
return _ASCIIKeep;
}
uint8_t keyControlGet(void) {
return _control;
}
uint8_t keyExtendedGet(void) {
return _extended;
}
uint8_t keyHit(void) {
uint8_t result = 0;
processEvent();
result = _keyPressed;
if (result) {
// Default mapping.
_extended = 0;
_ASCIIKeep = _ASCII;
_scanCodeKeep = _scanCode;
_keyPressed = 0;
// Fix mappings to match DOS bioskey() call.
if (_scanCodeKeep == SDL_SCANCODE_ESCAPE) _ASCIIKeep = 27;
if (_scanCodeKeep == SDL_SCANCODE_BACKSPACE) _ASCIIKeep = 8;
if (_scanCodeKeep == SDL_SCANCODE_HOME) { _extended = 1; _ASCIIKeep = 71; }
if (_scanCodeKeep == SDL_SCANCODE_UP) { _extended = 1; _ASCIIKeep = 72; }
if (_scanCodeKeep == SDL_SCANCODE_PAGEUP) { _extended = 1; _ASCIIKeep = 73; }
if (_scanCodeKeep == SDL_SCANCODE_LEFT) { _extended = 1; _ASCIIKeep = 75; }
if (_scanCodeKeep == SDL_SCANCODE_RIGHT) { _extended = 1; _ASCIIKeep = 77; }
if (_scanCodeKeep == SDL_SCANCODE_END) { _extended = 1; _ASCIIKeep = 79; }
if (_scanCodeKeep == SDL_SCANCODE_DOWN) { _extended = 1; _ASCIIKeep = 80; }
if (_scanCodeKeep == SDL_SCANCODE_PAGEDOWN) { _extended = 1; _ASCIIKeep = 81; }
if (_scanCodeKeep == SDL_SCANCODE_DELETE) { _extended = 1; _ASCIIKeep = 83; }
}
return result;
}
uint8_t keyScanCodeGet(void) {
return _scanCode;
}
uint8_t keyShiftGet(void) {
return _shift;
}
void keyShutdown(void) {
SDL_StopTextInput();
}
void keyStartup(void) {
SDL_StartTextInput();
}
void mouseShutdown(void) {
// Eh, don't care.
}
int16_t mouseStartup(void) {
_mouse.active = 1;
_mouse.buttonCount = 3;
_mouse.buttonLeft = 0;
_mouse.buttonRight = 0;
_mouse.buttonMiddle = 0;
_mouse.buttonLeftWasDown = 0;
_mouse.buttonRightWasDown = 0;
_mouse.buttonMiddleWasDown = 0;
_mouse.x = 0;
_mouse.y = 0;
_mouse.w = vbeDisplayWidthGet();
_mouse.h = vbeDisplayHeightGet();
return _mouse.active;
}
MouseT *mouseRead(void) {
_mouse.buttonLeftWasDown = _mouse.buttonLeft;
_mouse.buttonRightWasDown = _mouse.buttonRight;
_mouse.buttonMiddleWasDown = _mouse.buttonMiddle;
processEvent();
return &_mouse;
}
static void processEvent(void) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_MOUSEMOTION:
_mouse.x = e.motion.x;
_mouse.y = e.motion.y;
break;
case SDL_MOUSEBUTTONUP:
if (e.button.button == SDL_BUTTON_LEFT) _mouse.buttonLeft = 0;
if (e.button.button == SDL_BUTTON_RIGHT) _mouse.buttonRight = 0;
if (e.button.button == SDL_BUTTON_MIDDLE) _mouse.buttonMiddle = 0;
break;
case SDL_MOUSEBUTTONDOWN:
if (e.button.button == SDL_BUTTON_LEFT) _mouse.buttonLeft = 1;
if (e.button.button == SDL_BUTTON_RIGHT) _mouse.buttonRight = 1;
if (e.button.button == SDL_BUTTON_MIDDLE) _mouse.buttonMiddle = 1;
break;
case SDL_TEXTINPUT:
_ASCII = e.text.text[0]; //***TODO*** This is horrifically wrong.
break;
case SDL_KEYUP:
_scanCode = 0;
_ASCII = 0;
_keyPressed = 0;
_debounce = 0;
break;
case SDL_KEYDOWN:
if (_debounce == 0) {
_scanCode = e.key.keysym.scancode;
_shift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
_alt = ((SDL_GetModState() & KMOD_ALT) != 0);
_control = ((SDL_GetModState() & KMOD_CTRL) != 0);
if (e.key.keysym.scancode != 0) {
// Not a meta key
_keyPressed = 1;
_debounce = 1;
}
}
break;
}
}
processNetworkEvent();
}
static void processNetworkEvent(void) {
ENetEvent event = { 0 };
if (_host) {
while (enet_host_service(_host, &event, 1) > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_CONNECT:
_connected = 1;
break;
case ENET_EVENT_TYPE_RECEIVE:
comAddToBuffer((char *)event.packet->data, event.packet->dataLength);
break;
case ENET_EVENT_TYPE_DISCONNECT:
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
_connected = 0;
_modemCommandMode = 1;
comAddToBuffer("\13NO CARRIER\13", 12);
break;
default:
break;
}
}
}
}
uint32_t timerCallback(uint32_t interval, void *param) {
(void)param;
_timerTicks++;
if (_timerTicks > TICKS_PER_DAY) {
_timerTicks = 0;
}
return interval;
}
uint8_t vbeDisplayDepthBitsGet(void) {
return 32;
}
uint8_t vbeDisplayDepthBytesGet(void) {
return 4;
}
uint16_t vbeDisplayHeightGet(void) {
return _height;
}
uint16_t vbeDisplayWidthGet(void) {
return _width;
}
ColorT vbeColorMake(uint8_t red, uint8_t green, uint8_t blue) {
// Pixels are 8:8:8:8 RGBA - SDL_PIXELFORMAT_RGBA8888
return
(red << 24) |
(green << 16) |
(blue << 8) |
255;
}
void vbePresent(void) {
void *pixels;
int temp;
SurfaceT *s = surfaceOffScreenGet();
SDL_LockTexture(_texture, NULL, &pixels, &temp);
memcpy(pixels, s->buffer.bits8, s->bytes);
SDL_UnlockTexture(_texture);
SDL_RenderCopy(_renderer, _texture, NULL, NULL);
SDL_RenderPresent(_renderer);
if (_host) {
// Throttle this to some sane frame rate by checking for network events.
for (temp=0; temp<32; temp++) {
processNetworkEvent();
}
} else {
// No network activity. Just sleep.
SDL_Delay(32);
}
}
int16_t vbeInfoShow(void) {
// Eh, don't care.
return 0;
}
int16_t vbeShutdown(void) {
if (_texture) {
SDL_DestroyTexture(_texture);
_texture = NULL;
}
if (_renderer) {
SDL_DestroyRenderer(_renderer);
_renderer = NULL;
}
if (_window) {
SDL_DestroyWindow(_window);
_window = NULL;
}
// Stop network.
enet_deinitialize();
// Stop timer.
SDL_RemoveTimer(_timerID);
SDL_Quit();
return 0;
}
uint8_t vbeStartup(uint16_t xRes, uint16_t yRes, uint8_t bpp) {
(void)bpp;
_windowScale = 3;
SDL_Init(SDL_INIT_EVERYTHING);
_window = SDL_CreateWindow("GUI Test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, xRes, yRes, SDL_WINDOW_ALLOW_HIGHDPI);
_surface = SDL_GetWindowSurface(_window);
_renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
_texture = SDL_CreateTexture(_renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, xRes, yRes);
SDL_RenderSetLogicalSize(_renderer, xRes, yRes);
SDL_SetWindowSize(_window, xRes * _windowScale, yRes * _windowScale);
_width = xRes;
_height = yRes;
// We do timer startup here, too.
_timerID = SDL_AddTimer(TIMER_INTERVAL, timerCallback, NULL);
// And networking/serial startup.
enet_initialize();
return 0;
}
void vbeVBlankWait(void) {
// Eh, don't care.
}