Compare commits

..

20 Commits

Author SHA1 Message Date
b700113b88 changed mind, added helpers to derive pausedTime without a global 2023-08-21 00:40:13 +10:00
dbf8dce580 removed pausedTime global, its better for clients to derive 2023-08-20 21:15:16 +10:00
9c0d4befba added separate reset button in addition to stop 2023-08-20 18:13:53 +10:00
d09b991771 added getter for event types, and fixed a pausedTime bug 2023-08-20 03:54:29 +10:00
057d051a3a add basic setter for meta-data values 2023-08-16 17:02:25 +10:00
073e610966 added requesting event times 2023-07-25 22:07:33 +10:00
47369a86e9 fixed some undo and redo bugs 2023-07-25 21:30:55 +10:00
0812d68dd1 generalised request commands for metadata tags 2023-07-25 20:24:05 +10:00
f1e2c85e3a made the communication between the server and cmdline client more direct 2023-07-25 19:48:27 +10:00
1bb5864a29 swap out single byte socket communication with 256 byte buffer 2023-07-25 02:01:07 +10:00
fd8bbadb1e added convenient combination command functions 2023-07-24 22:41:54 +10:00
b93f2f0aa7 added proper run clearing function 2023-07-24 18:44:01 +10:00
c24f516380 added segments array 2023-07-20 22:25:09 +10:00
1aa1ed514a added run count 2023-07-20 20:38:06 +10:00
c7387f5acb fixed some infinite loop bug 2023-07-20 20:10:23 +10:00
d3f7a25a64 added saving previous run to file on new run start 2023-07-20 01:18:44 +10:00
f5e5c506af fixed a dumb buffer overflow i think 2023-07-19 01:45:26 +10:00
b208382e60 added nix build stuff 2023-07-18 23:53:22 +10:00
9c7ccfe0b0 big display kind of works 2023-04-11 14:06:42 +10:00
65414c2d64 initial commit for new format 2023-04-04 01:57:29 +10:00
9 changed files with 1346 additions and 1 deletions

View File

@ -1,5 +1,5 @@
TARGET = quest TARGET = quest
LIBS = -lm -luiohook -lcjson LIBS = -lm -luiohook -lcjson -lxcb -lXinerama -lX11
CC = gcc CC = gcc
CFLAGS = -g -Wall CFLAGS = -g -Wall
INSTALL_PATH = /usr/local INSTALL_PATH = /usr/local

2
default.nix Normal file
View File

@ -0,0 +1,2 @@
{pkgs ? import <nixpkgs> {} }:
pkgs.callPackage ./derivation.nix {}

23
derivation.nix Normal file
View File

@ -0,0 +1,23 @@
{ stdenv }:
stdenv.mkDerivation rec {
name = "quest-${version}";
version = "0.7";
src = ./src2/.;
nativeBuildInputs = [ ];
buildInputs = [ ];
buildPhase = ''
gcc server.c -o quest-daemon
gcc client.c -o quest-log
gcc tui.c -o quest
'';
installPhase = ''
mkdir -p $out/bin
cp quest-daemon $out/bin
cp quest-log $out/bin
cp quest $out/bin
'';
}

187
formatspec.md Normal file
View File

@ -0,0 +1,187 @@
Introduction
============
The quest file format is a declarative computer language. It is made up
of statements that when read by the software define how it should behave and
statements that are written by the software to declare what it recorded.
Each statement begins with a directive followed by options or arguments indented
by one tab on the following lines.
The order of the directives is important, they are read by the program
line by line and may inherit data from those placed above.
If a line does not begin with a recognizable directive, it is ignored.
Care should be taken when inserting comments as one program may recognize
directives that another does not, for this reason double backslashes (//) are
reserved for leaving comments
List of directives
==================
The indented section of the directives reference show example values first,
followed by the range of possible values or brief explaination in brackets
Option directives
-----------------
Any client is free to define their own directives to look for in this section.
These directives override a programs defaults, they should be placed at the
top of the file alongside MetaData directives
Foreground-Color
247, 248, 242 (0-255, 0-255, 0-255)
Background-Color
47, 53, 66 (0-255, 0-255, 0-255)
Best-Color
249, 255, 79 (0-255, 0-255, 0-255)
Ahead-Gaining-Color
24, 240, 31 (0-255, 0-255, 0-255)
Ahead-Losing-Color
79, 255, 85 (0-255, 0-255, 0-255)
Behind-Gaining-Color
255, 79, 79 (0-255, 0-255, 0-255)
Behind-Losing-Color
224, 34, 34 (0-255, 0-255, 0-255)
Comparison
Personal-Best (Personal-Best/World-Record/Sum-of-Best)
Show-Delta-Column
True (True/False)
Show-Segment-Column
True (True/False)
Show-Time-Column
True (True/False)
Show-Comparison-Column
True (True/False)
Toggle-Hotkeys
t (See key name section)
Start-Key
r (See key name section)
Stop-Key
f (See key name section)
Pause-Key
y (See key name section)
Split-Key
e (See key name section)
Unsplit-Key
g (See key name section)
Skip-Key
v (See key name section)
MetaData directives
---------------
These directives provide metadata to runs that follow them,
multiple of these can appear in a single file if multiple
games or categories are run after one another
Title
Elden Ring (Title of the game being run)
Category
Any% (Name of the category being run)
Runner
SuperCoolGuy04 (The name you as the runner are known by)
Segment and Route Directives
----------------------------
Runs by themselves are simply a list of events that occured
(such as splitting or pausing) in the quest language, in order to give meaning
to this data, these more complicated directives are used to define segments
that are played between splits and routes made up of these segments.
Define all your possible segments first, followed by all routes.
If no segments are defined, a single unnamed segment is to be assumed.
If no routes are defined, a single unnamed route that passes through all
segments in the order of their definition is to be assumed.
Segment
Shortname
Stage One (A name for the segment that should identify it
within the file and refer to the single
objective completed.
This name should not change.)
Longname
Murderize Chad (The display name for the segment if it should
differ from the proper name, this name can be
changed freely and may be used for
inside-jokes without issue.)
Description
Go to the golden palace and kill
the big dude with the ugly sunglasses
(Optional third argument for use as a more
detailed note or reminder of the objective
for the segment that may be displayed
alongside the name)
Route
Name
Magic Swordless (Name for the route, keeping in mind routes
are not categories, you may simply run
multiple different routes because you're unsure
yet which strategies are the fastest)
Segments
Stage One (The ordered list of segments the route
Stage Two consists of, these names should match the
Stage Four segment Shortnames)
Run Directives
--------------
These directives are much more complicated and are not intended to be written
by a human but rather by the timer software, they will make up the majority
of a file as they are the run history which may be quite long.
The data passed by these directives exists agnostic of segments, route, games,
or categories, rather they are either explicitly matched with metadata that is
applicable, or by default is matched with the last set of metadata declared by
the time of the run directive
Run
Route
Magic Swordless
Start
2016-10-23 10:03:12.034Z
(The first event in a run must be Start, and it
must be accompanied by an RFC 3339 timestamp)
Split
120052 (Following events may be accompanied by
RFC 3339 timestamps or simply a millisecond
offset from the previous event)
Skip
323481
Split
3121111
Pause
421397
Resume
2016-10-23 11:16:04.175Z
Stop (The last event in a run is always a Stop)
123111
The 'Then' Directive
------------------
The default behaviour the timer should exhibit when multiple games, catagories,
or routes exist in the same quest file is to allow the user to select
which one should be run, but the 'Then' directive instead allows for multiple
routes or catagories to be run one straight after the other as a single longer
"route". The timer should insert a dummy "segment" between the two routes for
the user to spend resetting the game and record the resulting run in two places:
A run directive should be recorded with every event including the dummy split
that can be exported and shared as one marathon attempt
AND
Individual run directives for each of the individual routes should be created
without the other included routes or the dummy split that can contribute to the
attempt history and stats of those individual catagories
If multiple routes exist on both or either side of a Then directive, the routes
to be stitched together should be selectable out of the options given, just as
they are normally.

View File

@ -22,6 +22,7 @@ struct pastseg *pastRuns;
int segCount; int segCount;
int currSeg = -1; int currSeg = -1;
char currentTime[10]; char currentTime[10];
int *route;
void sub_timespec(struct timespec t1, struct timespec t2, struct timespec* td) void sub_timespec(struct timespec t1, struct timespec t2, struct timespec* td)
{ {

View File

@ -24,6 +24,7 @@
struct segment struct segment
{ {
int id;
char *name; char *name;
int ms; int ms;
bool isSkipped; bool isSkipped;
@ -49,6 +50,7 @@ extern struct segment *bestsegs;
extern struct segment *wrrun; extern struct segment *wrrun;
extern struct segment *segments; extern struct segment *segments;
extern struct timespec notif; extern struct timespec notif;
extern int *route;
void sub_timespec(struct timespec t1, struct timespec t2, struct timespec* td); void sub_timespec(struct timespec t1, struct timespec t2, struct timespec* td);
void add_timespec(struct timespec t1, struct timespec t2, struct timespec* td); void add_timespec(struct timespec t1, struct timespec t2, struct timespec* td);

73
src2/client.c Normal file
View File

@ -0,0 +1,73 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
int main(int argc, char *argv[]) {
int sockfd, portno, n;
struct sockaddr_in serv_addr;
struct hostent *server;
char buffer[256];
if (argc < 2) {
fprintf(stderr,"usage %s command\n", argv[0]);
exit(0);
}
portno = 8101;
/* Create a socket point */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("ERROR opening socket");
exit(1);
}
server = gethostbyname("localhost");
if (server == NULL) {
fprintf(stderr,"ERROR, no such host\n");
exit(0);
}
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
serv_addr.sin_port = htons(portno);
/* Now connect to the server */
if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR connecting");
exit(1);
}
bzero(buffer, 256);
for (int i = 1; i < argc; i++) {
strcat(buffer, argv[i]);
strcat(buffer, " ");
}
/* Send message to the server */
n = write(sockfd, &buffer, 256);
if (n < 0) {
perror("ERROR writing to socket");
exit(1);
}
/* Now read server response */
bzero(buffer,256);
n = read(sockfd, &buffer, 256);
if (n < 0) {
perror("ERROR reading from socket");
exit(1);
}
if (buffer != NULL)
printf("%s\n", buffer);
return 0;
}

820
src2/server.c Normal file
View File

@ -0,0 +1,820 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
#include <time.h>
#include <stdbool.h>
#define NS_PER_S 1000000000
struct timespec finish, delta;
bool timerActive = false;
bool paused = false;
bool alive = true;
bool hasUndoneAtLeastOnce = false;
bool runUnsaved = false;
int timerOffset = 0;
enum event_type {
START,
SPLIT,
SKIP,
PAUSE,
RESUME,
RESET,
STOP
};
struct run_event {
enum event_type type;
struct timespec time;
};
struct segment {
char *shortname;
char *longname;
char *description;
};
struct route {
char *name;
int segment_count;
struct segment *segments;
};
struct run_event *run;
//Enough to hold a sm64 16 star, can realloc later
int runMaxLength = 12;
int runMarker = 0;
int runMarker2 = 0;
//save file stuff
char *default_file_name = "untitled.quest";
int run_count = 0;
int files = 0;
char **filePaths = NULL;
char **meta_keys, **meta_values;
int valuecount;
struct segment *segments;
int segment_count = 0;
struct route *routes;
int route_count = 0;
struct route current_route;
//functions
void sub_timespec(struct timespec t1, struct timespec t2, struct timespec* td);
void offset_timespec(int milliseconds, struct timespec* t);
int timespecToMS(struct timespec t);
void extend_run();
void add_event(enum event_type t);
void appendRunToFile();
void timespecToRFC3339(struct timespec t, char buf[]);
void loadFiles();
void add_segment(char *sname, char *lname, char *desc);
void addFile(char *path);
void sendInt(int sock, int value);
void sendValue(int sock, char* name);
void sendString(int sock, char* str);
void process_socket_input(int sock);
void set_metadata(char *key, char *value);
void save_metadata_to_file(char *token, char *token2);
void reset_timer();
int current_ms();
//basic timer commands
void start();
void split();
void stop();
void skip();
void undo();
void redo();
void pause_timer();
void resume();
void reset();
//convenient combination commands
void start_split_stop();
void start_reset();
void start_split();
void split_stop();
void start_stop();
void pause_resume();
void undo_redo();
void sub_timespec(struct timespec t1, struct timespec t2, struct timespec* td)
{
td->tv_nsec = t2.tv_nsec - t1.tv_nsec;
td->tv_sec = t2.tv_sec - t1.tv_sec;
if (td->tv_sec > 0 && td->tv_nsec < 0) {
td->tv_nsec += NS_PER_S;
td->tv_sec--;
} else if (td->tv_sec < 0 && td->tv_nsec > 0) {
td->tv_nsec -= NS_PER_S;
td->tv_sec++;
}
}
void offset_timespec(int milliseconds, struct timespec* t)
{
//this should leave milliseconds with just the remainder
int second_offset = milliseconds / 1000;
milliseconds -= second_offset * 1000;
int nanosecond_offset = milliseconds * 1000000;
t->tv_nsec -= nanosecond_offset;
if (t->tv_nsec < 0) {
second_offset++;
t->tv_nsec += 1000000000;
}
t->tv_sec -= second_offset;
}
int timespecToMS(struct timespec t)
{
return (t.tv_nsec / 1000000) + (t.tv_sec * 1000);
}
void extend_run()
{
runMaxLength *= 2;
run = realloc(run, sizeof(struct run_event) * runMaxLength);
}
void add_event(enum event_type t)
{
if (runMarker == runMaxLength)
extend_run();
run[runMarker].type = t;
clock_gettime(CLOCK_REALTIME, &run[runMarker].time);
if (t == START)
offset_timespec(timerOffset, &run[runMarker].time);
runMarker++;
runMarker2 = runMarker;
hasUndoneAtLeastOnce = false;
}
void reset_timer()
{
runMarker = 0;
runMarker2 = 0;
}
void start()
{
if (timerActive) return;
//Save the old run to the file before the new one starts,
//the reason to do this here is it gives the runner a chance to undo
//if they accidentally hit the stop button
appendRunToFile();
reset_timer();
timerActive = true;
add_event(START);
}
void stop()
{
if (!timerActive) return;
timerActive = false;
add_event(STOP);
//this makes sure the time clients recieve from time
//requests match the time on the stop event
finish = run[runMarker - 1].time;
runUnsaved = true;
}
//Identical function to stop() but with a RESET event
void reset()
{
if (!timerActive) return;
timerActive = false;
add_event(RESET);
finish = run[runMarker - 1].time;
runUnsaved = true;
}
void start_split_stop()
{
if (!timerActive) {
start();
} else {
if (runMarker < current_route.segment_count) {
split();
} else {
stop();
}
}
}
void start_split()
{
if (!timerActive) start();
else split();
}
void split_stop()
{
if (runMarker < current_route.segment_count) split();
else stop();
}
void start_stop()
{
if (!timerActive) start();
else stop();
}
void start_reset()
{
if (!timerActive) start();
else reset();
}
void split()
{
if (!timerActive) return;
add_event(SPLIT);
}
void skip()
{
if (!timerActive) return;
add_event(SKIP);
}
void undo()
{
if (runMarker > 0) {
runMarker--;
if (run[runMarker].type == STOP) {
timerActive = true;
runUnsaved = false;
}
if (run[runMarker].type == START)
timerActive = false;
if (run[runMarker].type == PAUSE)
paused = false;
if (run[runMarker].type == RESUME) {
paused = true;
}
hasUndoneAtLeastOnce = true;
}
}
void redo()
{
if (!timerActive) return;
if (runMarker < runMarker2) {
runMarker++;
if (run[runMarker - 1].type == STOP) {
timerActive = false;
runUnsaved = true;
finish = run[runMarker - 1].time;
}
if (run[runMarker - 1].type == START)
timerActive = true;
if (run[runMarker - 1].type == PAUSE)
paused = true;
if (run[runMarker - 1].type == RESUME) {
paused = false;
}
}
if (runMarker == runMarker2) {
hasUndoneAtLeastOnce = false;
}
}
void undo_redo()
{
if (hasUndoneAtLeastOnce) redo();
else undo();
}
//this isnt just called pause() because that would overlap with <unistd.h>
void pause_timer()
{
if (!timerActive) return;
if (!paused) {
add_event(PAUSE);
paused = true;
}
}
void resume()
{
if (!timerActive) return;
if (paused) {
add_event(RESUME);
paused = false;
}
}
void pause_resume()
{
if (paused) resume();
else pause_timer();
}
void appendRunToFile()
{
if (!runUnsaved)
return;
char* save_path = NULL;
if (files <= 0)
save_path = default_file_name;
else
save_path = filePaths[0];
FILE* fp;
fp = fopen(save_path, "a+");
fprintf(fp, "%s\n", "Run");
if (current_route.name != NULL) {
fprintf(fp, "\t%s\n", "Route");
fprintf(fp, "\t\t%s\n", current_route.name);
}
int i = 0;
bool done = false;
while (!done) {
if (run[i].type == STOP) {
done = true;
}
switch (run[i].type) {
case START:
fprintf(fp, "\t%s\n", "Start");
break;
case SPLIT:
fprintf(fp, "\t%s\n", "Split");
break;
case SKIP:
fprintf(fp, "\t%s\n", "Skip");
break;
case PAUSE:
fprintf(fp, "\t%s\n", "Pause");
break;
case RESUME:
fprintf(fp, "\t%s\n", "Resume");
break;
case RESET:
fprintf(fp, "\t%s\n", "Reset");
break;
case STOP:
fprintf(fp, "\t%s\n", "Stop");
break;
}
if (i == 0) {
char buf[25];
timespecToRFC3339(run[i].time, buf);
fprintf(fp, "\t\t%s\n", buf);
}
else {
sub_timespec(run[i - 1].time, run[i].time, &delta);
fprintf(fp, "\t\t%d\n", timespecToMS(delta));
}
i++;
}
fprintf(fp, "\n");
fclose(fp);
run_count++;
runUnsaved = false;
}
void timespecToRFC3339(struct timespec t, char buf[])
{
const int tmpsize = 21;
struct tm tm;
gmtime_r(&t.tv_sec, &tm);
strftime(buf, tmpsize, "%Y-%m-%d %H:%M:%S.", &tm);
sprintf(buf + tmpsize - 1, "%03luZ", (t.tv_nsec / 1000000));
}
void loadFiles()
{
FILE* fp;
char buff[255];
char buff2[255];
for (int i = 0; i < files; i++) {
fp = fopen(filePaths[i], "r+");
while(1) {
if (!fgets(buff, 255, fp))
break;
if (buff[0] == '/' && buff[1] == '/' || buff[0] == '\n' || buff[0] == '\t')
continue;
if (!strcmp(buff, "Segment\n")) {
char *s = NULL;
char *l = NULL;
char *d = NULL;
for (int x = 0; x < 3; x++) {
if (!fgets(buff2, 255, fp))
break;
if (!strcmp(buff2, "\tShortname\n")) {
if (!fgets(buff2, 255, fp))
break;
s = malloc(strlen(buff2) - 2);
s = strncpy(s, buff2 + 2, strlen(buff2) - 2);
s[strlen(s) - 1] = '\0';
} else if (!strcmp(buff2, "\tLongname\n")) {
if (!fgets(buff2, 255, fp))
break;
l = malloc(strlen(buff2) - 2);
l = strncpy(l, buff2 + 2, strlen(buff2) - 2);
l[strlen(l) - 1] = '\0';
} else if (!strcmp(buff2, "\tDescription\n")) {
if (!fgets(buff2, 255, fp))
break;
d = malloc(strlen(buff2) - 2);
d = strncpy(d, buff2 + 2, strlen(buff2) - 2);
d[strlen(d) - 1] = '\0';
}
}
add_segment(s, l, d);
continue;
}
if (!strcmp(buff, "Route\n"))
continue;
if (!strcmp(buff, "Run\n")) {
run_count++;
continue;
}
if (!fgets(buff2, 255, fp))
break;
if (buff2[0] == '\t') {
buff[strlen(buff) - 1] = '\0';
buff2[strlen(buff2) - 1] = '\0';
set_metadata(buff, buff2 + 1);
}
}
fclose(fp);
}
}
void add_segment(char *sname, char *lname, char *desc)
{
segment_count++;
segments = realloc(segments, sizeof(struct segment) * segment_count);
segments[segment_count - 1].shortname = sname;
segments[segment_count - 1].longname = lname;
segments[segment_count - 1].description = desc;
}
//TODO: eventually file loading should support loading multiple files
void addFile(char *path)
{
files++;
filePaths = realloc(filePaths, sizeof(char*) * files);
filePaths[files - 1] = path;
loadFiles();
}
int current_ms()
{
if (timerActive)
clock_gettime(CLOCK_REALTIME, &finish);
//if (paused) {
// sub_timespec(run[0].time, run[runMarker - 1].time, &delta);
//} else {
sub_timespec(run[0].time, finish, &delta);
//}
return timespecToMS(delta);
}
void sendInt(int sock, int value)
{
char buffer[256];
sprintf(buffer, "%d", value);
int n = write(sock, &buffer, 256);
if (n < 0) {
perror("ERROR writing to socket");
exit(1);
}
}
void sendValue(int sock, char* name)
{
char buffer[256];
int n, x;
bool namefound = false;
if (name == NULL) {
strcpy(buffer, "DATA NOT PRESENT");
} else {
for(int i = 0; i < valuecount; i++) {
if (!strcmp(meta_keys[i], name)) {
x = i;
namefound = true;
}
}
if (namefound)
strcpy(buffer, meta_values[x]);
else
strcpy(buffer, "DATA NOT PRESENT");
}
n = write(sock, &buffer, 256);
if (n < 0) {
perror("ERROR writing to socket");
exit(1);
}
}
void sendString(int sock, char* str)
{
char buffer[256];
strcpy(buffer, str);
int n = write(sock, &buffer, 256);
if (n < 0) {
perror("ERROR writing to socket");
exit(1);
}
}
void set_metadata(char *key, char *value)
{
char key_pos = -1;
for (int i = 0; i < valuecount; i++)
if (!strcmp(meta_keys[i], key))
key_pos = i;
if (key_pos > -1) {
meta_values[key_pos] = realloc(meta_values[key_pos], strlen(value));
strncpy(meta_values[key_pos], value, strlen(value));
meta_values[key_pos][strlen(value)] = '\0';
} else {
valuecount++;
meta_keys = realloc(meta_keys, sizeof(char*) * valuecount);
meta_keys[valuecount - 1] = malloc(strlen(key));
strncpy(meta_keys[valuecount - 1], key, strlen(key));
meta_keys[valuecount - 1][strlen(key)] = '\0';
meta_values = realloc(meta_values, sizeof(char*) * valuecount);
meta_values[valuecount - 1] = malloc(strlen(value));
strncpy(meta_values[valuecount - 1], value, strlen(value));
meta_values[valuecount - 1][strlen(value)] = '\0';
}
}
void save_metadata_to_file(char *token, char *token2)
{
char* save_path = NULL;
if (files <= 0)
save_path = default_file_name;
else
save_path = filePaths[0];
FILE* fp;
fp = fopen(save_path, "r+");
fprintf(fp, "%s\n", token);
fprintf(fp, "\t%s\n\n", token2);
fclose(fp);
}
void process_socket_input(int sock)
{
int n;
char buffer[256];
n = read(sock, &buffer, 256);
if (n < 0) {
perror("ERROR reading from socket");
exit(1);
}
char *token = strtok(buffer, " ");
//Imperative commands
if (!strcmp(token, "start")) {
start();
} else if (!strcmp(token, "stop")) {
stop();
} else if (!strcmp(token, "reset")) {
reset();
} else if (!strcmp(token, "kill")) {
alive = false;
} else if (!strcmp(token, "split")) {
split();
} else if (!strcmp(token, "skip")) {
skip();
} else if (!strcmp(token, "pause")) {
pause_timer();
} else if (!strcmp(token, "resume")) {
resume();
} else if (!strcmp(token, "undo")) {
undo();
} else if (!strcmp(token, "redo")) {
redo();
} else if (!strcmp(token, "save")) {
appendRunToFile();
} else if (!strcmp(token, "start-split-stop")) {
start_split_stop();
} else if (!strcmp(token, "pause-resume")) {
pause_resume();
} else if (!strcmp(token, "start-stop")) {
start_stop();
} else if (!strcmp(token, "start-reset")) {
start_reset();
} else if (!strcmp(token, "start-split")) {
start_split();
} else if (!strcmp(token, "split-stop")) {
split_stop();
} else if (!strcmp(token, "undo-redo")) {
undo_redo();
//Getters
} else if (!strcmp(token, "get")) {
token = strtok(NULL, " ");
if (!strcmp(token, "current_time")) {
sendInt(sock, current_ms());
} else if (!strcmp(token, "current_time_with_pause")) {
int running_pause = 0;
struct timespec p, r;
bool tracking_pause = false;
for (int i = 0; i < runMarker; i++) {
if (run[i].type == PAUSE) {
sub_timespec(run[0].time, run[i].time, &p);
tracking_pause = true;
}
if (run[i].type == RESUME) {
sub_timespec(run[0].time, run[i].time, &r);
running_pause += timespecToMS(r) - timespecToMS(p);
tracking_pause = false;
} else if (i == runMarker - 1 && tracking_pause) {
running_pause += current_ms() - timespecToMS(p);
}
}
sendInt(sock, current_ms() - running_pause);
} else if (!strcmp(token, "run_count")) {
sendInt(sock, run_count);
} else if (!strcmp(token, "segment_count")) {
sendInt(sock, segment_count);
} else if (!strcmp(token, "route_count")) {
sendInt(sock, route_count);
} else if (!strcmp(token, "event_count")) {
sendInt(sock, runMarker);
} else if (!strcmp(token, "segment_shortname")) {
token = strtok(NULL, " ");
int x = atoi(token);
sendString(sock, segments[x].shortname);
} else if (!strcmp(token, "segment_longname")) {
token = strtok(NULL, " ");
int x = atoi(token);
sendString(sock, segments[x].longname);
} else if (!strcmp(token, "segment_description")) {
token = strtok(NULL, " ");
int x = atoi(token);
sendString(sock, segments[x].description);
} else if (!strcmp(token, "route_name")) {
token = strtok(NULL, " ");
int x = atoi(token);
sendString(sock, routes[x].name);
} else if (!strcmp(token, "route_segment_count")) {
token = strtok(NULL, " ");
int x = atoi(token);
sendInt(sock, routes[x].segment_count);
} else if (!strcmp(token, "route_segment_shortname")) {
token = strtok(NULL, " ");
int x = atoi(token);
token = strtok(NULL, " ");
int y = atoi(token);
sendString(sock, routes[x].segments[y].shortname);
} else if (!strcmp(token, "event_time")) {
token = strtok(NULL, " ");
int x;
if (!strcmp(token, "last"))
x = runMarker - 1;
else if (!strcmp(token, "first"))
x = 0;
else
x = atoi(token);
struct timespec t;
sub_timespec(run[0].time, run[x].time, &t);
sendInt(sock, timespecToMS(t));
} else if (!strcmp(token, "event_time_with_pause")) {
token = strtok(NULL, " ");
int x;
if (!strcmp(token, "last"))
x = runMarker - 1;
else if (!strcmp(token, "first"))
x = 0;
else
x = atoi(token);
int running_pause = 0;
struct timespec p, r;
bool tracking_pause = false;
for (int i = 0; i < x; i++) {
if (run[i].type == PAUSE) {
sub_timespec(run[0].time, run[i].time, &p);
tracking_pause = true;
}
if (run[i].type == RESUME) {
sub_timespec(run[0].time, run[i].time, &r);
running_pause += timespecToMS(r) - timespecToMS(p);
tracking_pause = false;
} else if (i == x - 1 && tracking_pause) {
sub_timespec(run[0].time, run[x].time, &r);
running_pause += timespecToMS(r) - timespecToMS(p);
}
}
struct timespec t;
sub_timespec(run[0].time, run[x].time, &t);
sendInt(sock, timespecToMS(t) - running_pause);
} else if (!strcmp(token, "event_type")) {
token = strtok(NULL, " ");
int x;
if (!strcmp(token, "last"))
x = runMarker - 1;
else if (!strcmp(token, "first"))
x = 0;
else
x = atoi(token);
char *reply;
switch (run[x].type) {
case START:
reply = "START";
break;
case SPLIT:
reply = "SPLIT";
break;
case SKIP:
reply = "SKIP";
break;
case PAUSE:
reply = "PAUSE";
break;
case RESUME:
reply = "RESUME";
break;
case RESET:
reply = "RESET";
break;
case STOP:
reply = "STOP";
break;
}
sendString(sock, reply);
} else if (!strcmp(token, "meta")) {
token = strtok(NULL, " ");
sendValue(sock, token);
}
//Setters
} else if (!strcmp(token, "set")) {
token = strtok(NULL, " ");
if (!strcmp(token, "meta")) {
token = strtok(NULL, " ");
char *token2 = strtok(NULL, " ");
set_metadata(token, token2);
save_metadata_to_file(token, token2);
}
}
}
int main(int argc, char *argv[])
{
int sockfd, newsockfd, portno, clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n, pid;
run = malloc(sizeof(struct run_event) * runMaxLength);
//TODO: remove this file testing boilerplate
if (argc > 1)
addFile(argv[1]);
/* First call to socket() function */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("ERROR opening socket");
exit(1);
}
/* Initialize socket structure */
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = 8101;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
/* Now bind the host address using bind() call.*/
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR on binding");
exit(1);
}
listen(sockfd,5);
clilen = sizeof(cli_addr);
printf("Ready!\n");
while (alive) {
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0) {
perror("ERROR on accept");
exit(1);
}
process_socket_input(newsockfd);
close(newsockfd);
}
free(run);
close(sockfd);
}

237
src2/tui.c Normal file
View File

@ -0,0 +1,237 @@
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <signal.h>
char numbermap[5][44] = {
"xxx..x..xxx.xxx.x.x.xxx.xxx.xxx.xxx.xxx.....",
"x.x..x....x...x.x.x.x...x.....x.x.x.x.x.x...",
"x.x..x..xxx.xxx.xxx.xxx.xxx...x.xxx.xxx.....",
"x.x..x..x.....x...x...x.x.x...x.x.x...x.x...",
"xxx..x..xxx.xxx...x.xxx.xxx...x.xxx...x...x."
};
struct termios base;
int fps = 60;
struct color {
int r;
int g;
int b;
};
struct color b = { 47, 53, 66}; //Background color
struct color f = {247, 248, 242}; //Text foreground color
struct color g = {249, 255, 79}; //Best ever segment time color
struct color ag = { 24, 240, 31}; //Ahead, and gaining time segment color
struct color al = { 79, 255, 85}; //Ahead, but losing time segment color
struct color bg = {255, 79, 79}; //Behind, but gaining time segment color
struct color bl = {224, 34, 34}; //Behind, and losing time segment color
int w, h;
//functions
void timestring(char *str, int ms);
void printbig(int x, int y, int ms);
int timestringDigits(int ms);
void resize(int i);
void initScreen();
void resetScreen();
void die(int i);
void processColorString(struct color *c, char* s);
int timestringDigits(int ms)
{
int chars = 4;
if (ms >= 10000)
chars += 1;
if (ms >= 60000)
chars += 2;
if (ms >= 600000)
chars += 1;
if (ms >= 3600000)
chars += 2;
if (ms >= 36000000)
chars += 1;
if (ms >= 360000000)
chars += 1;
return chars;
}
//Attempt 2 at thinking through how to print the big numbies
void printbig(int x, int y, int ms)
{
char small[13];
timestring(small, ms);
if (w < strlen(small)) {
printf("2smol\n");
return;
}
int bigstringw = 42; //Minimum width and height the big timer string
int bigstringh = 5; //bigger sizes are just multiples of these numbers.
int bigstrings = 1;
x = (w - (bigstringw - 1 * bigstrings)) / 2; //theres a -1 because theres extra whitespace on the last printed digit
y = (h - (bigstringh * bigstrings)) / 2;
for (int sy = 0; sy < 5; sy++) { //for every row
printf("\033[%d;%dH", y + sy, x); //go to position
for (int cc = 0; cc < 12; cc++) { //then, for every character
int c = small[cc]; //check what character we're on
int mapcharacterwidth = (c >= 48 && c <= 57) ? 4 : 2;//if its a number, print 4 pixels, if its punctuation, print 2 pixels
int mapoffset;
if (c >= 48 && c <= 57)
mapoffset = (c - 48) * 4;
else if (c == 46)
mapoffset = 42;
else
mapoffset = 40;
for (int xx = 0; xx < mapcharacterwidth; xx++) {
if (numbermap[sy][mapoffset + xx] == 'x')
printf("\033[48;2;%d;%d;%dm ", f.r, f.g, f.b);
if (numbermap[sy][mapoffset + xx] == '.')
printf("\033[48;2;%d;%d;%dm ", b.r, b.g, b.b);
}
}
}
printf("\n");
//printf("\033[%d;%dH%s\n", y, x, small + (12 - timestringDigits(time)));
}
void timestring(char *str, int ms)
{
int msdigits = (ms / 10) % 100;
int seconds = (ms / 1000) % 60;
int minutes = ((ms / 1000) / 60) % 60;
int hours = ((ms / 1000) / 60) / 60;
sprintf(str, "%03d:%02d:%02d.%02d", hours, minutes, seconds, msdigits);
}
void resize(int i)
{
struct winsize ws;
ioctl(1, TIOCGWINSZ, &ws);
w = ws.ws_col;
h = ws.ws_row;
}
void initScreen()
{
struct termios t;
tcgetattr(1, &base);
t = base;
t.c_lflag &= (~ECHO & ~ICANON);
tcsetattr(1, TCSANOW, &t);
//TODO:Figure out why i did this
//dup(0);
fcntl(0, F_SETFL, O_NONBLOCK);
printf("\033[?1049h\n"); //Switch to TUI mode (alternate buffer)
printf("\033[?25l\n"); //Hide text cursor
printf("\033[2J\n"); //Clear screen
}
void resetScreen()
{
tcsetattr(1, TCSANOW, &base);
printf("\033[2J\n"); //Clear screen
printf("\033[?1049l\n"); //Switch back to regular mode
printf("\033[?25h\n"); //Show cursor
}
void die(int i)
{
exit(1);
}
void processColorString(struct color *c, char* s)
{
int i = 0;
int length = strlen(s);
char comp[4];
int compcount = 0;
int colorcompsdone = 0;
//TODO: if we know the length now that we're not doing fgetc we dont
//need a while loop; convert to for loop.
//Why? what makes a for loop better than a while loop?
while (1) {
char x = s[i++];
if (x >= 48 && x <= 57) {
comp[compcount] = x;
compcount++;
}
if (x == 44 || i == length) {
comp[compcount] = '\0';
switch(colorcompsdone) {
case 0:
c->r = atoi(comp);
break;
case 1:
c->g = atoi(comp);
break;
case 2:
c->b = atoi(comp);
}
colorcompsdone++;
compcount = 0;
}
if (i == length)
break;
}
}
int main (int argc, char *argv[])
{
initScreen();
atexit(resetScreen);
signal(SIGTERM, die);
signal(SIGINT, die);
signal(SIGWINCH, resize);
resize(0);
FILE *fp;
char path[1000];
char ti[13];
//Request foreground color from config file
fp = popen("./result/bin/quest-log Foreground-Color", "r");
if (fgets(path, sizeof(path), fp)) {
if (strcmp(path, "DATA NOT PRESENT"))
processColorString(&f, path);
printf("\033[38;2;%d;%d;%dm", f.r, f.g, f.b);
}
pclose(fp);
//Request background color from config file
fp = popen("./result/bin/quest-log Background-Color", "r");
if (fgets(path, sizeof(path), fp)) {
if (strcmp(path, "DATA NOT PRESENT"))
processColorString(&b, path);
printf("\033[48;2;%d;%d;%dm", b.r, b.g, b.b);
}
pclose(fp);
//Set fps from command line argument
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-fps"))
fps = atoi(argv[i + 1]);
}
while (1) {
int time = 0;
fp = popen("./result/bin/quest-log current_time", "r");
if (fp == NULL) {
printf("Failed to run command\n");
exit(1);
}
//TODO: why is this a while loop?
while (fgets(path, sizeof(path), fp) != NULL) {
time = atoi(path);
}
pclose(fp);
printf("\033[2J\n");
//timestring(&ti, time);
//printf("%s\n", ti + (12 - timestringDigits(time)));
printbig(3, 4, time);
usleep(1000000 / fps);
}
}