Compare commits

...

17 Commits

Author SHA1 Message Date
7fb5978b34 mild refactoring, clean up windows properly upon exit 2025-08-12 09:21:36 -04:00
790e60d23f fix format error in README.md (i have to be more careful checking these things) 2025-08-06 20:14:09 -04:00
b2fcc087af update README.md to reflect Makefile changes 2025-08-06 20:12:50 -04:00
9d151c4dad seperate GIT_TAG and GIT_HASH in version.c 2025-08-06 20:09:16 -04:00
b24d97213e Merge pull request 'feat(version): added support for building version information' (#2) from gfaraday/bbs:main into main
Reviewed-on: #2
2025-08-06 19:54:06 -04:00
97af579397 feat(version): added support for building version information 2025-08-06 16:20:43 -05:00
57e8c4a499 forgot to change locale after disabling utf8 2025-08-06 14:47:30 -04:00
d779e6f3a0 update readme to reflect makefile changes 2025-08-06 14:23:07 -04:00
02b305cc8b unicode is not working anyways, so linking to ncursesw is useless. will fix later 2025-08-06 14:20:08 -04:00
6b7a1c459b Merge pull request 'Seperate Screen Files' (#1) from seperate_screen_files into main 2025-08-05 17:39:35 -04:00
96a18b8cdb move files to src/ directory and add build/ to .gitignore 2025-08-05 17:34:42 -04:00
914ce0d7ad each screen now has it's own file, and there is a framework for adding more 2025-08-05 17:30:03 -04:00
db26be2a07 Import makefile by Gale, thank you <3 2025-08-05 17:25:21 -04:00
18eba940f1 prepare for compile time init of screens[] 2025-08-05 14:45:43 -04:00
1a90a816e5 nonfunctional, working on seperate screen files 2025-08-05 14:17:27 -04:00
7d8f3d70b3 refactoring bbs.c to prepare for screens to be seperate files 2025-08-05 12:42:58 -04:00
f48ed22602 enable compiler optimizations and gdb debug info 2025-08-05 12:42:21 -04:00
14 changed files with 291 additions and 162 deletions

4
.gitignore vendored
View File

@@ -1 +1,5 @@
bbs
build/
# Machine generated files:
src/version.c

View File

@@ -1,18 +1,46 @@
CC=gcc
CFLAGS=-std=c99 -pedantic -Wall -Wextra
LIBS=-lncursesw
EXECUTABLE=bbs
# SPDX-License-Identifier: MIT
.PHONY: all
all: make
# Makefile for Amber's BBS Interface
# Buildsys by Gale Faraday
# `generate` must be called before `all` to build version information and other
# machine generated files.
.PHONY: all clean run generate
.SUFFIXES:
.DEFAULT_GOAL := all
TARGET := $(shell basename $(CURDIR))
SOURCES := src/
BUILD := build/
GENABLES := $(SOURCES)version.c
OBJS := $(patsubst $(SOURCES)%.c,$(BUILD)%.o,$(wildcard $(SOURCES)*.c))
EXECUTABLE := $(TARGET)
make:
$(CC) $(CFLAGS) $(LIBS) bbs.c -o $(EXECUTABLE)
CC := gcc
LD := gcc
CFLAGS := -std=c99 -pedantic -Wall -Wextra -Wno-unused-parameter -O2 -ggdb
LDFLAGS := $(CFLAGS) -lncurses
.PHONY: execute
execute:
all: $(EXECUTABLE)
run: $(EXECUTABLE)
./$(EXECUTABLE)
.PHONY: run
run: make execute
.IGNORE: clean
clean:
@rm -rvf $(BUILD) $(EXECUTABLE) $(GENABLES)
generate: $(GENABLES)
$(GENABLES): genver.sh
./genver.sh
$(EXECUTABLE): $(OBJS)
$(LD) $(LDFLAGS) -o $@ $^
$(OBJS): $(BUILD)%.o : $(SOURCES)%.c
-@mkdir -p $(BUILD)
$(CC) $(CFLAGS) -o $@ -c $<

View File

@@ -4,13 +4,11 @@ This is a simple bbs program written in c. The way it is meant to work is that a
## Building
The only dependency is `ncurses`. The makefile asks for `-lncursesw` but unicode is not yet used, so `-lncurses` should be fine. `ncurses` is almost guaranteed preinstalled on your system.
`bbs` depends only upon `ncurses`.
The program is standardized on c99. It should run fine on c89, but there will be warnings. You'll have to edit the makefile to request c89 from `gcc`.
Run `make generate` to generate `version.c`, then `make` to build. Build files will be in `build/` and the executable `bbs` will be in the root of the repo.
To build, run `make`. It will drop an executable named `bbs` in the current folder.
The makefile has the additional command `make run`, which just launches the program after compilation.
`make run` will build and run the program, and `make clean` will clean up the `build/` directory.
## License

145
bbs.c
View File

@@ -1,145 +0,0 @@
#include <curses.h>
#include <string.h>
#include <locale.h>
const unsigned int GETCH_TIMEOUT = 10; /* in ms */
const unsigned int MAX_SCREENS = 2; /* size of screens array */
const char* LOCALE = "en_US.UTF-8"; /* enable unicode support, set to "ANSI_X3.4-1968" for ascii */
char errorMessage[60];
enum Status {
STATUS_QUIT,
STATUS_WAITING,
STATUS_NEED_REFRESH
};
enum ActiveScreen {
HOME,
ERROR
};
struct Screen {
char name[20];
WINDOW *win;
void (*draw_screen)(struct Screen *, char *input); /* this is run about every 10ms */
};
static void draw_home(struct Screen *screen, char *input) {
static char* banner = ""
" _ _ _ \n"
" | | | | | | \n"
" __ _ _ __ ___ | |__ ___ _ __ ___ _ __ | | __ _ ___ ___ _ __ ___| |_ \n"
" / _` | '_ ` _ \\| '_ \\ / _ \\ '__/ __| '_ \\| |/ _` |/ __/ _ \\ | '_ \\ / _ \\ __|\n"
" | (_| | | | | | | |_) | __/ | \\__ \\ |_) | | (_| | (_| __/_| | | | __/ |_ \n"
" \\__,_|_| |_| |_|_.__/ \\___|_| |___/ .__/|_|\\__,_|\\___\\___(_)_| |_|\\___|\\__|\n"
" | | \n"
" |_| \n";
mvwprintw(screen->win, 1, 2, "Thank you for visiting:");
mvwprintw(screen->win, 2, 1, "%s", banner);
mvwprintw(screen->win, 10, 1, "Your current input is: %s", input);
}
static void draw_error(struct Screen *screen, __attribute__((unused)) char *input) {
mvwprintw(screen->win, 1, 2, "%s", errorMessage);
}
static void draw_screen (struct Screen *screen, char *input) {
screen->draw_screen(screen, input);
box(screen->win, 0, 0);
// wattron(screen->win, A_STANDOUT);
mvwprintw(screen->win, 0, 1, " %s ", screen->name);
// wattroff(screen->win, A_STANDOUT);
}
int main() {
initscr(); /* start ncurses */
noecho(); /* hide keyboard input */
curs_set(0); /* disable cursor */
timeout(GETCH_TIMEOUT); /* set timeout for getch() */
setlocale(LC_CTYPE, LOCALE); /* set locale, UTF8 support is enabled here */
unsigned int start_y = 0;
unsigned int start_x = 0;
unsigned int terminal_width;
unsigned int terminal_height;
unsigned int old_terminal_width = 0;
unsigned int old_terminal_height = 0;
struct Screen screens[MAX_SCREENS];
enum Status status = STATUS_NEED_REFRESH; /* refresh screens[active_screen].window on first loop */
char input = ' ';
strcpy(errorMessage, "");
getmaxyx(stdscr, terminal_height, terminal_width);
unsigned int screen_before_error = HOME;
enum ActiveScreen active_screen = HOME;
struct Screen home = {
"home",
newwin(terminal_height, terminal_width, start_y, start_x),
draw_home
};
screens[HOME] = home;
struct Screen error = {
"error",
newwin(terminal_height, terminal_width, start_y, start_x),
draw_error
};
screens[ERROR] = error;
/* main event loop */
while (status != STATUS_QUIT) {
if ((input = getch())) { /* if any input was read */
/* TODO: factor out input to another function */
if (input == 'q') {
status = STATUS_QUIT;
} else {
status = STATUS_NEED_REFRESH;
};
};
getmaxyx(stdscr, terminal_height, terminal_width);
/* resize active screen's window */
if (old_terminal_width != terminal_width || old_terminal_height != terminal_height) {
wclear(screens[active_screen].win);
wresize(screens[active_screen].win, terminal_height, terminal_width);
mvwin(screens[active_screen].win, 0, 0);
wrefresh(screens[active_screen].win);
old_terminal_width = terminal_width;
old_terminal_height = terminal_height;
if (terminal_width < 80 || terminal_height < 24) {
if (active_screen != ERROR) {
strcpy(errorMessage, "This program expects at least 80 rows by 24 columns.");
screen_before_error = active_screen;
};
active_screen = ERROR;
} else if (active_screen != screen_before_error) {
active_screen = screen_before_error;
};
status = STATUS_NEED_REFRESH;
};
/* refresh active screen's window */
if (status == STATUS_NEED_REFRESH) {
draw_screen(&screens[active_screen], &input);
wrefresh(screens[active_screen].win);
status = STATUS_WAITING;
}
};
/* clean up */
delwin(screens[active_screen].win);
nocbreak();
endwin(); /* ends curses mode */
curs_set(1);
echo();
return 0;
}

21
genver.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Script to generate version information file
# Current git hash
HASH="$(git describe --always --dirty)"
# Current git tag
TAG="$(git describe --always --dirty --tags)"
# Output filename
OUTFILE='src/version.c'
sed -e "s/<HASH>/$HASH/g" -e "s/<TAG>/$TAG/g" <<EOF > "$OUTFILE"
/* Build version information. This file generated by genver.sh */
const char GIT_HASH[] = "<HASH>";
const char GIT_TAG[] = "<TAG>";
const char CC_VERSION[] = __VERSION__;
const char BUILD_DATE[] = __DATE__;
EOF

101
src/bbs.c Normal file
View File

@@ -0,0 +1,101 @@
/* SPDX-License-Identifier: MIT */
#include <curses.h>
#include <string.h>
#include <locale.h>
#include "globals.h"
#include "screens.h"
char error_message[MAX_ERROR_MESSAGE_SIZE];
unsigned int terminal_width;
unsigned int terminal_height;
unsigned int old_terminal_width = 0;
unsigned int old_terminal_height = 0;
enum Status status = STATUS_NEED_REFRESH; /* refresh screens[active_screen].window on first loop */
char input = ' ';
unsigned int screen_before_error = HOME;
enum ActiveScreen active_screen = HOME;
static void draw_screen (struct Screen *screen, char *input) {
screen->draw_screen(screen, input);
wattron(screen->win, A_STANDOUT);
mvwprintw(screen->win, 0, 1, " %s ", screen->name);
wattroff(screen->win, A_STANDOUT);
}
static void resize_active_screen() {
wclear(screens[active_screen].win);
wresize(screens[active_screen].win, terminal_height, terminal_width);
mvwin(screens[active_screen].win, 0, 0);
wrefresh(screens[active_screen].win);
old_terminal_width = terminal_width;
old_terminal_height = terminal_height;
if (terminal_width < 80 || terminal_height < 24) {
if (active_screen != ERROR) {
strcpy(error_message, "This program expects at least 80 rows by 24 columns.");
screen_before_error = active_screen;
};
active_screen = ERROR;
} else if (active_screen != screen_before_error) {
active_screen = screen_before_error;
};
status = STATUS_NEED_REFRESH;
}
int main() {
initscr(); /* start ncurses */
noecho(); /* hide keyboard input */
curs_set(0); /* disable cursor */
keypad(stdscr, TRUE); /* enable extra keys */
timeout(GETCH_TIMEOUT); /* set timeout for getch() */
setlocale(LC_CTYPE, ""); /* set locale to current locale */
strcpy(error_message, "");
getmaxyx(stdscr, terminal_height, terminal_width);
/* could be it's own init func within the screen's file. */
screens[HOME].win = newwin(terminal_height, terminal_width, 0, 0);
screens[ERROR].win = newwin(terminal_height, terminal_width, 0, 0);
/* main event loop */
while (status != STATUS_QUIT) {
if ((input = getch())) { /* if any input was read */
/* TODO: factor out input to another function */
if (input == 'q') {
status = STATUS_QUIT;
} else {
status = STATUS_NEED_REFRESH;
};
};
getmaxyx(stdscr, terminal_height, terminal_width);
if (old_terminal_width != terminal_width || old_terminal_height != terminal_height) {
resize_active_screen();
};
/* refresh active screen's window */
if (status == STATUS_NEED_REFRESH) {
draw_screen(&screens[active_screen], &input);
wrefresh(screens[active_screen].win);
status = STATUS_WAITING;
}
};
/* clean up */
delwin(screens[0].win); /* could be in a destructor function in the screen's file */
delwin(screens[1].win);
nocbreak();
endwin(); /* ends curses mode */
curs_set(1);
echo();
return 0;
}

10
src/error.c Normal file
View File

@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: MIT */
#include <curses.h>
#include "screens.h"
/* char *input is unused */
void draw_error(struct Screen *screen, char *input) {
box(screen->win, 0, 0);
mvwprintw(screen->win, 1, 2, "%s", error_message);
}

10
src/error.h Normal file
View File

@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: MIT */
#ifndef ERROR_H /* header guard */
#define ERROR_H
#include "screens.h"
void draw_error(struct Screen *screen, char *input);
#endif

31
src/globals.h Normal file
View File

@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: MIT */
#define GETCH_TIMEOUT 10 /* in ms */
#define MAX_SCREEN_NAME_SIZE 20 /* size of the screen name array */
#define MAX_ERROR_MESSAGE_SIZE 60 /* size of error_message array */
#ifndef GLOBALS_H /* header guard */
#define GLOBALS_H
#include <curses.h>
extern char error_message[];
struct Screen {
char name[MAX_SCREEN_NAME_SIZE];
WINDOW *win;
void (*draw_screen)(struct Screen *, char *input); /* GETCH_TIMEOUT determines how often this is run, in ms */
};
enum Status {
STATUS_QUIT,
STATUS_WAITING,
STATUS_NEED_REFRESH
};
enum ActiveScreen {
HOME,
ERROR
};
#endif

26
src/home.c Normal file
View File

@@ -0,0 +1,26 @@
/* SPDX-License-Identifier: MIT */
#include <curses.h>
#include "screens.h"
#include "version.h"
void draw_home(struct Screen *screen, char *input) {
static char* banner = ""
" _ _ _ \n"
" | | | | | | \n"
" __ _ _ __ ___ | |__ ___ _ __ ___ _ __ | | __ _ ___ ___ _ __ ___| |_ \n"
" / _` | '_ ` _ \\| '_ \\ / _ \\ '__/ __| '_ \\| |/ _` |/ __/ _ \\ | '_ \\ / _ \\ __|\n"
" | (_| | | | | | | |_) | __/ | \\__ \\ |_) | | (_| | (_| __/_| | | | __/ |_ \n"
" \\__,_|_| |_| |_|_.__/ \\___|_| |___/ .__/|_|\\__,_|\\___\\___(_)_| |_|\\___|\\__|\n"
" | | \n"
" |_| \n";
mvwprintw(screen->win, 1, 2, "Thank you for visiting:");
mvwprintw(screen->win, 2, 1, "%s", banner);
mvwprintw(screen->win, 10, 1, "Your current input is: %s", input);
wclrtoeol(screen->win); /* clear rest of line to prevent longer inputs from sticking around */
mvwprintw(screen->win, 11, 1, "Git hash: %s", GIT_HASH);
mvwprintw(screen->win, 12, 1, "Git tag: %s", GIT_TAG);
mvwprintw(screen->win, 13, 1, "CC version: %s", CC_VERSION);
mvwprintw(screen->win, 14, 1, "Build date: %s", BUILD_DATE);
box(screen->win, 0, 0);
}

10
src/home.h Normal file
View File

@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: MIT */
#ifndef HOME_H /* header guard */
#define HOME_H
#include "screens.h"
void draw_home(struct Screen *screen, char *input);
#endif

19
src/screens.c Normal file
View File

@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: MIT */
#include "globals.h"
#include "home.h"
#include "error.h"
/* NOTE: this should be compile time */
struct Screen screens[] = {
{
"home",
NULL,
draw_home
},
{
"error",
NULL,
draw_error
}
};

10
src/screens.h Normal file
View File

@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: MIT */
#ifndef SCREENS_H /* header guard */
#define SCREENS_H
#include "globals.h"
extern struct Screen screens[];
#endif

6
src/version.h Normal file
View File

@@ -0,0 +1,6 @@
/* Header for version information constants */
extern const char GIT_HASH[];
extern const char GIT_TAG[];
extern const char CC_VERSION[];
extern const char BUILD_DATE[];