DJGPP backend working.

This commit is contained in:
Scott Duensing 2022-06-02 19:15:53 -05:00
parent ef63b14c2f
commit 4d0b209adc
16 changed files with 1451 additions and 840 deletions

View file

@ -23,6 +23,8 @@ HEADERS += \
$$SHARED/stddclmr.h \
$$SHARED/thirdparty/memwatch/memwatch.h \
$$SHARED/thirdparty/stb_ds.h \
$$SHARED/log.h \
$$SHARED/util.h \
src/gui/font.h \
src/gui/gui.h \
src/gui/image.h \
@ -38,6 +40,8 @@ SOURCES += \
$$SHARED/array.c \
$$SHARED/memory.c \
$$SHARED/thirdparty/memwatch/memwatch.c \
$$SHARED/log.c \
$$SHARED/util.c \
src/gui/font.c \
src/gui/gui.c \
src/gui/image.c \

View file

@ -25,7 +25,7 @@ static ColorT _mouseTransparency;
void guiRun(void) {
EventT event;
EventT event = { 0 };
while (_guiRunning) {
// Read mouse & keyboard.
@ -75,14 +75,15 @@ void guiShutdown(void) {
}
void guiStartup(int16_t width, int16_t height, int16_t depth) {
uint8_t guiStartup(int16_t width, int16_t height, int16_t depth) {
platformStartup(width, height, depth);
if (platformStartup(width, height, depth) == FAIL) return FAIL;
__guiScreenBuffer = videoSurfaceScreenGet();
__guiBackBuffer = videoSurfaceCreate(videoDisplayWidthGet(), videoDisplayHeightGet());
videoSurfaceSet(__guiBackBuffer);
_mousePointer = imageLoad("mouse.png");
_mousePointer = imageLoad("mouse.png");
_mouseTransparency = videoSurfacePixelGet(_mousePointer, videoSurfaceWidthGet(_mousePointer) - 2, 0); // Find our transparency color.
__guiFontVGA8x14 = fontLoad("vga8x14.dat");
@ -90,6 +91,8 @@ void guiStartup(int16_t width, int16_t height, int16_t depth) {
wmStartup();
guiRegister(windowRegister);
return SUCCESS;
}

View file

@ -70,13 +70,13 @@ extern FontT *__guiFontVGA8x14;
#define GUI_WHITE __guiBaseColors[15]
void guiModesShow(void);
void guiRegister(WidgetRegisterT widgetRegister);
void guiRun(void);
void guiShutdown(void);
void guiStartup(int16_t width, int16_t height, int16_t depth);
void guiStop(void);
void guiWidgetBaseSet(WidgetT *widget, uint8_t magic, uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void guiModesShow(void);
void guiRegister(WidgetRegisterT widgetRegister);
void guiRun(void);
void guiShutdown(void);
uint8_t guiStartup(int16_t width, int16_t height, int16_t depth);
void guiStop(void);
void guiWidgetBaseSet(WidgetT *widget, uint8_t magic, uint16_t x, uint16_t y, uint16_t w, uint16_t h);
#endif // GUI_H

View file

@ -7,19 +7,22 @@ int main(int argc, char *argv[]) {
uint16_t i;
(void)argc;
(void)argv;
guiStartup(800, 600, 24);
memoryStartup(argv[0]);
logOpenByHandle(memoryLogHandleGet());
i = 1;
//for (i=1; i<4; i++) {
sprintf(title, "Testing %d", i);
windowCreate(i * 50, i * 50, 300, 200, title, WIN_CLOSE | WIN_MAXIMIZE | WIN_MINIMIZE | WIN_RESIZE);
//}
if (guiStartup(800, 600, 32) == SUCCESS) {
i = 1;
//for (i=1; i<4; i++) {
sprintf(title, "Testing %d", i);
windowCreate(i * 50, i * 50, 300, 200, title, WIN_CLOSE | WIN_MAXIMIZE | WIN_MINIMIZE | WIN_RESIZE);
//}
guiRun();
guiShutdown();
}
guiRun();
guiShutdown();
logClose();
memoryShutdown();
return 0;
}

View file

@ -7,9 +7,14 @@
#include <stdint.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "macros.h"
#include "util.h"
#include "log.h"
#include "memory.h"
#include "stddclmr.h"

View file

@ -1,6 +1,8 @@
#ifdef BACKEND_DJGPP
#include "os.h"
#include <dos.h>
#include <dpmi.h>
#include <go32.h>
@ -8,19 +10,52 @@
#include <conio.h>
#include <sys/farptr.h>
#include <sys/nearptr.h>
#include <string.h>
#include "djgpp.h"
#define DIVISIBLE_BY_EIGHT(x) ((((x) >> 3) << 3) == (x))
// These are all we support
#define VBE_MM_PACKED 4
#define VBE_MM_DCOLOR 6
// Based on http://www.brackeen.com/vga/source/djgpp20/mouse.c.html
#define MOUSE_INT 0x33
#define MOUSE_RESET 0x00
#define MOUSE_STATUS 0x03
#define MOUSE_GETMOTION 0x0B
#define MOUSE_LEFT_BUTTON 0x01
#define MOUSE_RIGHT_BUTTON 0x02
#define MOUSE_MIDDLE_BUTTON 0x04
#define KEYBOARD_READ_EXTENDED 0x10
#define KEYBOARD_CHECK_EXTENDED 0x11
#define KEYBOARD_META_EXTENDED 0x12
enum MetaBitsE {
KEY_META_SHIFT_RIGHT = 0,
KEY_META_SHIFT_LEFT,
KEY_META_CONTROL,
KEY_META_ALT,
KEY_META_SCROLL_LOCKED,
KEY_META_NUM_LOCKED,
KEY_META_CAPS_LOCKED,
KEY_META_INSERT_LOCKED,
KEY_META_CONTROL_LEFT,
KEY_META_ALT_LEFT,
KEY_META_CONTROL_RIGHT,
KEY_META_ALT_RIGHT,
KEY_META_SCROLL_LOCK,
KEY_META_NUM_LOCK,
KEY_META_CAPS_LOCK,
KEY_META_SYSREQ
};
typedef struct VBEInfoS {
char vbeSignature[4]; // 'VESA' 4 byte signature
int16_t vbeVersion; // VBE version number
@ -121,6 +156,9 @@ typedef struct VBESurfaceS {
} VBESurfaceT;
extern ColorT *__guiBaseColors;
static VBESurfaceT _vbeSurface;
static VBEInfoT _vbeInfo;
static VBEModeInfoT _vbeModeInfo;
@ -148,7 +186,98 @@ void (*videoSurfacePixelSet)(uint16_t x, uint16_t y, ColorT color);
void platformEventGet(EventT *event) {
int32_t x;
int32_t y;
int16_t dx;
int16_t dy;
int16_t h = videoDisplayHeightGet();
int16_t w = videoDisplayWidthGet();
int16_t ext = bioskey(KEYBOARD_CHECK_EXTENDED);
int16_t meta = bioskey(KEYBOARD_META_EXTENDED);
int16_t key = 0;
union REGS regs;
// Read mouse motion.
regs.x.ax = MOUSE_GETMOTION;
int86(MOUSE_INT, &regs, &regs);
dx = regs.x.cx; // Temporary assignment changes values to signed.
dy = regs.x.dx; // Don't skip this step. :-)
x = event->x + dx;
y = event->y + dy;
if (x < 0) x = 0;
if (x > w - 1) x = w - 1;
if (y < 0) y = 0;
if (y > h - 1) y = h - 1;
event->x = x;
event->y = y;
// Read mouse buttons.
regs.x.ax = MOUSE_STATUS;
int86(MOUSE_INT, &regs, &regs);
// Was the left button down?
if (event->buttons & BUTTON_LEFT) {
// Yes. Is it still down?
if ((regs.x.bx & MOUSE_LEFT_BUTTON) > 0) {
// Yes. Do nothing.
} else {
// No! Clear it and set UP event.
event->buttons &= ~BUTTON_LEFT;
event->flags |= EVENT_FLAG_LEFT_UP;
}
} else {
// No. Is it down now?
if ((regs.x.bx & MOUSE_LEFT_BUTTON) > 0) {
// Yes! Set bit and DOWN event.
event->buttons |= BUTTON_LEFT;
event->flags |= EVENT_FLAG_LEFT_DOWN;
} else {
// No. Do nothing.
}
}
// Was the right button down?
if (event->buttons & BUTTON_RIGHT) {
// Yes. Is it still down?
if ((regs.x.bx & MOUSE_RIGHT_BUTTON) > 0) {
// Yes. Do nothing.
} else {
// No! Clear it and set UP event.
event->buttons &= ~BUTTON_RIGHT;
event->flags |= EVENT_FLAG_RIGHT_UP;
}
} else {
// No. Is it down now?
if ((regs.x.bx & MOUSE_RIGHT_BUTTON) > 0) {
// Yes! Set bit and DOWN event.
event->buttons |= BUTTON_RIGHT;
event->flags |= EVENT_FLAG_RIGHT_DOWN;
} else {
// No. Do nothing.
}
}
// Read keyboard.
event->key = 0;
event->kbstat = 0;
if (ext > 0) {
key = bioskey(KEYBOARD_READ_EXTENDED);
// The value returned is a combination of the key's scan code in the high 8 bits
// and its ASCII code in the low 8 bits. For non-alphanumeric keys, such as the
// arrow keys, the low 8 bits are zeroed.
// Extended keys have the E0h prefix in the low 8 bits.
event->flags |= EVENT_FLAG_KEYPRESS;
if (LOW_BYTE(key) == 0xE0) {
//_extended = 1;
event->key = HIGH_BYTE(key);
} else {
//_extended = 0;
event->key = LOW_BYTE(key);
}
if ((meta & (1 << KEY_META_ALT)) + (meta & (1 << KEY_META_ALT_LEFT)) + (meta & (1 << KEY_META_ALT_RIGHT))) event->kbstat |= META_ALT;
if ((meta & (1 << KEY_META_CONTROL)) + (meta & (1 << KEY_META_CONTROL_LEFT)) + (meta & (1 << KEY_META_CONTROL_RIGHT))) event->kbstat |= META_CTRL;
if ((meta & (1 << KEY_META_SHIFT_LEFT)) + (meta & (1 << KEY_META_SHIFT_RIGHT))) event->kbstat |= META_SHIFT;
}
}
@ -156,6 +285,8 @@ void platformShutdown(void) {
__dpmi_regs r;
__dpmi_meminfo m;
free(__guiBaseColors);
if (_vbeSurface.vbeInitBoolean == 0) {
r.x.ax = 0x03; // make sure we're in 3h
__dpmi_int(0x10, &r); // for buggy vesa implementations
@ -192,32 +323,51 @@ void platformShutdown(void) {
}
void platformStartup(int16_t width, int16_t height, int16_t depth) {
uint8_t platformStartup(int16_t width, int16_t height, int16_t depth) {
uint16_t vbeModeNumber;
uint8_t i;
uint8_t EGA[16][3] = {
{ 0, 0, 0 }, /* black */
{ 0, 0, 170 }, /* blue */
{ 0, 170, 0 }, /* green */
{ 0, 170, 170 }, /* cyan */
{ 170, 0, 0 }, /* red */
{ 170, 0, 170 }, /* magenta */
{ 170, 85, 0 }, /* brown */
{ 170, 170, 170 }, /* light gray */
{ 85, 85, 85 }, /* dark gray */
{ 85, 85, 255 }, /* light blue */
{ 85, 255, 85 }, /* light green */
{ 85, 255, 255 }, /* light cyan */
{ 255, 85, 85 }, /* light red */
{ 255, 85, 255 }, /* light magenta */
{ 255, 255, 85 }, /* yellow */
{ 255, 255, 255 } /* white */
};
if (vbeGetInfo() == NULL) {
//logWrite("No VESA BIOS Extensions found.\n");
//vbeShutdown();
return;
logWrite("No VESA BIOS Extensions found.\n");
platformShutdown();
return FAIL;
}
if (_vbeInfo.vbeVersion < 0x0200) {
//logWrite("VBE Version 2.0 or better required.\n");
//vbeShutdown();
return;
logWrite("VBE Version 2.0 or better required.\n");
platformShutdown();
return FAIL;
}
vbeGetPmodeInterface();
if ((vbeModeNumber = vbeSelectModeNumber(width, height, depth)) == 0) {
//logWrite("No appropriate video mode available.\n");
//vbeShutdown();
return;
logWrite("No appropriate video mode available.\n");
platformShutdown();
return FAIL;
}
if (vbeSetMode(vbeModeNumber) == NULL) {
//vbeShutdown();
return;
platformShutdown();
return FAIL;
}
if (_vbeSurface.bitsPerPixel == 8) videoSurfacePixelSet = videoSurfacePixelSet8;
@ -225,6 +375,12 @@ void platformStartup(int16_t width, int16_t height, int16_t depth) {
if (_vbeSurface.bitsPerPixel == 15) videoSurfacePixelSet = videoSurfacePixelSet16;
if (_vbeSurface.bitsPerPixel == 32) videoSurfacePixelSet = videoSurfacePixelSet32;
__guiBaseColors = (ColorT *)malloc(sizeof(ColorT) * 16);
for (i=0; i<16; i++) {
__guiBaseColors[i] = videoColorMake(EGA[i][0], EGA[i][1], EGA[i][2]);
}
return SUCCESS;
}
@ -414,12 +570,15 @@ static uint8_t vbeIsDesiredMode(void) {
if (((_vbeModeInfo.modeAttributes) & (1<<7)) >> 7) {
// Packed or Direct Color mode.
if (_vbeModeInfo.memoryModel == VBE_MM_PACKED || _vbeModeInfo.memoryModel == VBE_MM_DCOLOR) {
// Resolution minimum of 640x480.
if (_vbeModeInfo.xResolution >= 640 && _vbeModeInfo.yResolution >= 480) {
// Multiple of 8
if (DIVISIBLE_BY_EIGHT(_vbeModeInfo.xResolution) && DIVISIBLE_BY_EIGHT(_vbeModeInfo.yResolution)) {
// Valid mode!
return 1;
// We only handle these bit depths.
if ((_vbeModeInfo.bitsPerPixel == 8) || (_vbeModeInfo.bitsPerPixel == 15) || (_vbeModeInfo.bitsPerPixel == 16) || (_vbeModeInfo.bitsPerPixel == 32)) {
// Resolution minimum of 640x480.
if (_vbeModeInfo.xResolution >= 640 && _vbeModeInfo.yResolution >= 480) {
// Multiple of 8
if (DIVISIBLE_BY_EIGHT(_vbeModeInfo.xResolution) && DIVISIBLE_BY_EIGHT(_vbeModeInfo.yResolution)) {
// Valid mode!
return 1;
}
}
}
}
@ -568,7 +727,61 @@ uint16_t videoDisplayWidthGet(void) {
void videoModesShow(void) {
int8_t counter;
// 0 1 2 3 4 5 6 7 8
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
//printf("VBE 2.0 driver v1.0 (c) 2021, Scott Duensing <scott@kangaroopunch.com>\n");
//printf("Based on: VBE 2.0 driver v1.0 (c) 1999, Tobias Koch <tobias.koch@gmail.com>\n\n");
if (vbeGetInfo() == NULL) {
logWrite("No VESA BIOS Extensions found.\n");
platformShutdown();
return;
}
logWrite(
"Video Memory - %d KB\n"
"VBE Version - %d.%d detected\n"
"OEM Specification - %s\n",
_vbeInfo.totalMemory * 64,
_vbeInfo.vbeVersion >> 8, _vbeInfo.vbeVersion & 0x00FF,
_vbeInfo.oemStringPtr);
if (_vbeInfo.vbeVersion >= 0x0200) {
logWrite(
"OEM Software Revision - %d.%d\n"
"OEM Vendor Name - %s\n"
"OEM Product Name - %s\n"
"OEM Product Revision - %s\n"
"Protected Mode Interface - %s\n\n",
_vbeInfo.oemSoftwareRev >> 8, _vbeInfo.oemSoftwareRev & 0x00FF,
_vbeInfo.oemVendorNamePtr,
_vbeInfo.oemProductNamePtr,
_vbeInfo.oemProductRevPtr,
vbeGetPmodeInterface() ? "Found" : "Missing");
} else {
logWrite("VESA BIOS Extension 2.0 or better required!\n");
platformShutdown();
return;
}
for (counter=0; ; counter++) {
if (_vbeInfo.videoModePtr[counter] == 0xFFFF) break;
vbeGetModeInfo(_vbeInfo.videoModePtr[counter]);
if (vbeIsDesiredMode()) {
logWrite("Mode %Xh - %4d x %4d x %2d - RGB %d:%d:%d - %s\n",
_vbeInfo.videoModePtr[counter],
_vbeModeInfo.xResolution,
_vbeModeInfo.yResolution,
_vbeModeInfo.bitsPerPixel,
_vbeModeInfo.redMaskSize,
_vbeModeInfo.greenMaskSize,
_vbeModeInfo.blueMaskSize,
((_vbeModeInfo.memoryModel / 2) - 2) ? "DCOLOR" : "PACKED");
}
}
platformShutdown();
}

View file

@ -18,8 +18,8 @@
#endif
void platformShutdown(void);
void platformStartup(int16_t width, int16_t height, int16_t depth);
void platformShutdown(void);
uint8_t platformStartup(int16_t width, int16_t height, int16_t depth);
void videoModesShow(void);

View file

@ -3,6 +3,7 @@
#include "os.h"
#include "thirdparty/stb_ds.h"

93
shared/log.c Normal file
View file

@ -0,0 +1,93 @@
// ***TODO*** On DOS, open and close the log on every write so the file gets updated and we still have data on a crash.
#include "log.h"
static FILE *_log = NULL;
static uint8_t _ourHandle = 0;
static char *_logBuffer = NULL;
static uint16_t _logBufferSize = 0;
static logCallback _logCallback = NULL;
void logCallbackSet(logCallback callback) {
_logCallback = callback;
}
void logClose(void) {
if (_log && _ourHandle) {
fclose(_log);
_ourHandle = 0;
}
DEL(_logBuffer);
}
uint8_t logOpen(char *filename, uint8_t append) {
_log = fopen(filename, append ? "a" : "w");
if (_log) {
_ourHandle = 1;
return 1;
}
return 0;
}
void logOpenByHandle(FILE *handle) {
_log = handle;
}
void logWrite(char *format, ...) {
va_list args = { 0 };
va_list args2 = { 0 };
uint16_t length = 0;
if (_log) {
va_start(args, format);
va_copy(args2, args);
// Attempt to write into current log buffer.
length = vsnprintf(_logBuffer, _logBufferSize, format, args);
// Did it fit?
if (length >= _logBufferSize) {
// Nope. Resize buffer to fit plus a bit more.
if (_logBuffer) free(_logBuffer);
_logBufferSize = length + 32;
_logBuffer = (char *)malloc(_logBufferSize);
// Do it again.
vsnprintf(_logBuffer, _logBufferSize, format, args2);
}
//#ifdef __linux__
// Also output to stdout on Linux.
fprintf(stdout, "%s", _logBuffer);
fflush(stdout);
//#endif
if (_logCallback) _logCallback(_logBuffer);
fprintf(_log, "%s", _logBuffer);
fflush(_log);
va_end(args2);
va_end(args);
}
}
void logWriteToFileOnly(char *format, ...) {
va_list args;
va_start(args, format);
if (_log) {
vfprintf(_log, format, args);
fflush(_log);
}
va_end(args);
}

19
shared/log.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef LOG_H
#define LOG_H
#include "os.h"
typedef void (*logCallback)(char *message);
void logCallbackSet(logCallback callback);
void logClose(void);
uint8_t logOpen(char *filename, uint8_t append);
void logOpenByHandle(FILE *handle);
void logWrite(char *format, ...);
void logWriteToFileOnly(char *format, ...);
#endif // LOG_H

View file

@ -12,6 +12,13 @@
#define DEL(v) {if(v) {free(v); v=NULL;}}
// Some helper defines.
#define DIVISIBLE_BY_EIGHT(x) ((((x) >> 3) << 3) == (x))
#define HIGH_BYTE(b) ((uint8_t)(((b) & 0xFF00) >> 8))
#define LOW_BYTE(b) ((uint8_t)((b) & 0x00FF))
// Return codes.
#define SUCCESS 0
#define FAIL 1

View file

@ -1 +1,51 @@
#include "memory.h"
FILE *_memoryLog = NULL;
#ifdef MEMORY_CHECK_ENABLED
void mwLogW(FILE *p);
#endif
FILE *memoryLogHandleGet(void) {
return _memoryLog;
}
#ifdef MEMORY_CHECK_ENABLED
void memoryOutput(int c) {
fputc(c, _memoryLog);
fflush(_memoryLog);
#ifdef __linux__
// Also output to stdout on Linux.
fputc(c, stdout);
fflush(stdout);
#endif
}
#endif
void memoryShutdown(void) {
#ifdef MEMORY_CHECK_ENABLED
unlink("memwatch.log"); // It just insists on creating this!
mwTerm();
#endif
}
void memoryStartup(char *appName) {
char *logName = utilAppNameWithNewExtensionGet(appName, "log");
_memoryLog = fopen(logName, "w");
#ifdef MEMORY_CHECK_ENABLED
mwLogW(_memoryLog);
mwSetOutFunc(memoryOutput);
mwInit();
#endif
free(logName);
}

View file

@ -4,10 +4,16 @@
#include "os.h"
#ifdef MEMORY_CHECK_ENABLED
#define MEMWATCH
#include "thirdparty/memwatch/memwatch.h"
#endif
FILE *memoryLogHandleGet(void);
void memoryShutdown(void);
void memoryStartup(char *appName);
#endif // MEMORY_H

File diff suppressed because it is too large Load diff

181
shared/util.c Normal file
View file

@ -0,0 +1,181 @@
#include "array.h"
#include "util.h"
char __scratch[SCRATCH_SIZE];
char *utilAppNameWithNewExtensionGet(char *appName, char *extension) {
char *c = NULL;
char *newName = NULL;
int16_t x = strlen(appName);
uint16_t len = 2 + strlen(extension); // 2 = dot in extension and 0 terminator.
// Find last portion of filename.
while (x > 0) {
if (appName[x] == '/' || appName[x] == '\\') break;
x--;
len++;
}
// We use this + length of new extension for new string length.
newName = (char *)malloc(len);
if (newName) {
if (strlen(appName) - x < len) {
// Replace extension
strncpy(newName, &appName[x + 1], len - 1);
c = strstr(newName, ".");
if (c) *c = 0;
strncat(newName, ".", len - 1);
strncat(newName, extension, len - 1);
}
}
return newName;
}
void utilBitsPrint(uint8_t byte) {
int i = 0;
for (i = 7; 0 <= i; i--) {
printf("%c", (byte & (1 << i)) ? '1' : '0');
}
}
char *utilCreateString(char *format, ...) {
va_list args;
char *string;
va_start(args, format);
string = utilCreateStringVArgs(format, args);
va_end(args);
return string;
}
__attribute__((__format__(__printf__, 1, 0)))
char *utilCreateStringVArgs(char *format, va_list args) {
va_list argsCopy;
int32_t size = 0;
char *buffer = NULL;
va_copy(argsCopy, args);
size = vsnprintf(NULL, 0, format, argsCopy) + 1;
va_end(argsCopy);
buffer = calloc(1, (size_t)size);
if (buffer) {
vsnprintf(buffer, (size_t)size, format, args);
}
return buffer;
}
void utilDie(const char *why, ...) {
va_list args;
char msg[2048];
va_start(args, why);
vsprintf(msg, why, args);
va_end(args);
logWrite("DIE: %s", msg);
exit(1);
}
uint8_t utilFileExists(char *filename) {
FILE *f = fopen(filename, "rb");
if (f) {
fclose(f);
return SUCCESS;
}
return FAIL;
}
uint8_t utilFromFileReadByte(FILE *f, uint8_t *result) {
unsigned char c;
// Get next byte.
c = fgetc(f);
// End of file?
if (feof(f)) return FAIL;
// Add to result.
*result = c;
return SUCCESS;
}
uint8_t utilFromFileReadString(FILE *f, char **result) {
unsigned char c;
uint16_t x = 0;
// Something already in 'result'?
DEL(*result);
while (1) {
// Get next byte.
c = fgetc(f);
// End of file?
if (feof(f)) return FAIL;
// Add to result.
__scratch[x++] = c;
__scratch[x] = 0;
// End of string?
if (c == 0) {
*result = strdup(__scratch);
return SUCCESS;
}
}
}
void utilStringToLower(char *string) {
uint16_t i;
for (i=0; i<strlen(string); i++) string[i] = tolower(string[i]);
}
char **utilWrapText(char *text, uint16_t width) {
char **lines = NULL;
char *head = text;
char *buffer = NULL;
int32_t bufferPos;
int32_t pos;
int32_t lastSpace;
int32_t isLf;
buffer = (char *)malloc(width + 2);
if (buffer) {
pos = lastSpace = bufferPos = 0;
while (head[pos] != 0) {
isLf = (head[pos] == '\n');
if (isLf || pos == width) {
if (isLf || lastSpace == 0) lastSpace = pos; // just cut it
while (*head!=0 && lastSpace-- > 0) {
buffer[bufferPos++] = *head++;
buffer[bufferPos] = 0;
}
arrput(lines, strdup(buffer));
if (isLf) head++; // jump the line feed
while (*head!=0 && *head==' ') head++; // clear the leading space
lastSpace = pos = bufferPos = 0;
} else {
if (head[pos] == ' ') lastSpace = pos;
pos++;
}
}
arrput(lines, strdup(head));
free(buffer);
}
return lines;
}

26
shared/util.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef UTIL_H
#define UTIL_H
#include "os.h"
#define SCRATCH_SIZE 4096
extern char __scratch[SCRATCH_SIZE];
char *utilAppNameWithNewExtensionGet(char *appName, char *extension);
void utilBitsPrint(uint8_t byte);
char *utilCreateString(char *format, ...);
char *utilCreateStringVArgs(char *format, va_list args);
void utilDie(const char *why, ...);
uint8_t utilFileExists(char *filename);
uint8_t utilFromFileReadByte(FILE *f, uint8_t *result);
uint8_t utilFromFileReadString(FILE *f, char **result);
void utilStringToLower(char *string);
char **utilWrapText(char *text, uint16_t width);
#endif // UTIL_H