summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2025-02-18 14:24:16 -0500
committerpommicket <pommicket@gmail.com>2025-02-18 14:24:16 -0500
commitabcc95a0eeffebec2924fa76f65aa758649c24e0 (patch)
treef68a08ab875d5a5b1f687147abd475710983c862
parentcc64f55792278bc3f336d5e000949c8e406cef67 (diff)
start menu
-rw-r--r--main.c220
-rw-r--r--meson.build6
2 files changed, 198 insertions, 28 deletions
diff --git a/main.c b/main.c
index f8c33f3..c903d02 100644
--- a/main.c
+++ b/main.c
@@ -8,6 +8,7 @@
#include <errno.h>
#include <string.h>
#include <SDL.h>
+#include <SDL_ttf.h>
#include <time.h>
#include <stdbool.h>
#include <libudev.h>
@@ -15,9 +16,35 @@
#include <GL/glcorearb.h>
#include <sys/mman.h>
#include <poll.h>
+#include <fontconfig/fontconfig.h>
#include "ds.h"
#include "lib/stb_image_write.h"
+
+typedef enum {
+ MENU_NONE,
+ MENU_MAIN,
+ MENU_COUNT
+} Menu;
+
+enum {
+ MENU_OPT_QUIT = 1,
+ MENU_OPT_RESOLUTION,
+ MENU_OPT_INPUT,
+};
+// use char for MenuOption type so that we can use strlen
+typedef char MenuOption;
+static const MenuOption main_menu[] = {
+ MENU_OPT_INPUT,
+ MENU_OPT_RESOLUTION,
+ MENU_OPT_QUIT,
+ 0
+};
+static const MenuOption *const menus[MENU_COUNT] = {
+ NULL,
+ main_menu,
+};
+
#define HASH_SIZE 16
#if crypto_generichash_BYTES_MIN > HASH_SIZE
#error "crypto_generichash what happened"
@@ -441,20 +468,26 @@ static void debug_save_24bpp_bmp(const char *filename, const uint8_t *pixels, ui
fclose(fp);
}
-char *a_sprintf(PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(1, 2);
-char *a_sprintf(const char *fmt, ...) {
- // idk if you can always just pass NULL to vsnprintf
- va_list args;
+char *va_sprintf(const char *fmt, va_list args) {
+ va_list args_copy;
+ va_copy(args_copy, args);
char fakebuf[2] = {0};
- va_start(args, fmt);
- int ret = vsnprintf(fakebuf, 1, fmt, args);
- va_end(args);
+ int ret = vsnprintf(fakebuf, 1, fmt, args_copy);
+ va_end(args_copy);
if (ret < 0) return NULL; // bad format or something
size_t n = (size_t)ret;
char *str = calloc(1, n + 1);
- va_start(args, fmt);
vsnprintf(str, n + 1, fmt, args);
+ return str;
+}
+
+char *a_sprintf(PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(1, 2);
+char *a_sprintf(const char *fmt, ...) {
+ // idk if you can always just pass NULL to vsnprintf
+ va_list args;
+ va_start(args, fmt);
+ char *str = va_sprintf(fmt, args);
va_end(args);
return str;
}
@@ -621,18 +654,65 @@ void get_cameras_from_device(const char *dev_path, const char *serial, int fd, C
}
}
+static void render_text_to_surface(TTF_Font *font, SDL_Surface *dest, int x, int y, SDL_Color color, const char *str) {
+ SDL_Surface *text = TTF_RenderText_Blended(font, str, color);
+ SDL_BlitSurface(text, NULL, dest, (SDL_Rect[1]){{x, y, 0, 0}});
+ SDL_FreeSurface(text);
+}
+
int main(void) {
+ SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); // if this program is sent a SIGTERM/SIGINT, don't turn it into a quit event
+ if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
+ fprintf(stderr, "couldn't initialize SDL\n");
+ return EXIT_FAILURE;
+ }
if (sodium_init() < 0) {
- fprintf(stderr, "couldn't initialize libsodium");
+ fprintf(stderr, "couldn't initialize libsodium\n");
return EXIT_FAILURE;
}
- SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); // if this program is sent a SIGTERM/SIGINT, don't turn it into a quit event
- if (access("/dev", R_OK) != 0) {
- fprintf(stderr, "Can't access /dev");
+ if (!FcInit()) {
+ fprintf(stderr, "couldn't initialize fontconfig\n");
+ return EXIT_FAILURE;
+ }
+ #define FcFini "don't call FcFini: it's broken on certain versions of fontconfig - https://github.com/brndnmtthws/conky/pull/1755"
+ if (TTF_Init() < 0) {
+ fprintf(stderr, "couldn't initialize SDL2_ttf: %s\n", TTF_GetError());
+ return EXIT_FAILURE;
+ }
+ char *font_path = NULL;
+ {
+ // find a suitable font
+ FcPattern *pattern = FcPatternCreate();
+ FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_REGULAR);
+ FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
+ FcPatternAddInteger(pattern, FC_WIDTH, FC_WIDTH_NORMAL);
+ FcPatternAddString(pattern, FC_FONTFORMAT, (FcChar8 *)"TrueType");
+ FcConfigSubstitute(0, pattern, FcMatchPattern);
+ FcDefaultSubstitute(pattern);
+ FcResult result = 0;
+ FcPattern *font = FcFontMatch(NULL, pattern, &result);
+ if (result == FcResultMatch) {
+ FcChar8 *file;
+ if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
+ font_path = strdup((const char *)file);
+ }
+ } else {
+ fprintf(stderr, "couldn't find any normal TTF fonts. try installing one?\n");
+ return EXIT_FAILURE;
+ }
+ FcPatternDestroy(pattern);
+ FcPatternDestroy(font);
+ }
+ TTF_Font *font = TTF_OpenFont(font_path, 18);
+ if (!font) {
+ fprintf(stderr, "couldn't open font %s: %s\n", font_path, TTF_GetError());
return EXIT_FAILURE;
}
- SDL_Init(SDL_INIT_EVERYTHING);
SDL_Window *window = SDL_CreateWindow("camlet", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE);
+ if (!window) {
+ fprintf(stderr, "couldn't create window: %s\n", SDL_GetError());
+ return EXIT_FAILURE;
+ }
int gl_version_major = 3, gl_version_minor = 0;
#if DEBUG
gl_version_major = 4;
@@ -642,7 +722,8 @@ int main(void) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, gl_version_minor);
SDL_GLContext glctx = SDL_GL_CreateContext(window);
if (!glctx) {
- printf("couldn't create GL context: %s\n", SDL_GetError());
+ fprintf(stderr, "couldn't create GL context: %s\n", SDL_GetError());
+ return EXIT_FAILURE;
}
SDL_GL_SetSwapInterval(1); // vsync
#if __GNUC__
@@ -670,13 +751,18 @@ int main(void) {
struct timespec ts = {0};
clock_gettime(CLOCK_MONOTONIC, &ts);
double last_time = (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
- GLuint texture = 0;
- gl_GenTextures(1, &texture);
- gl_BindTexture(GL_TEXTURE_2D, texture);
- gl_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- gl_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- gl_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- gl_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ GLuint textures[2] = {0};
+ gl_GenTextures(SDL_arraysize(textures), textures);
+ for (size_t i = 0; i < SDL_arraysize(textures); i++) {
+ gl_BindTexture(GL_TEXTURE_2D, textures[i]);
+ gl_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ gl_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ gl_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ gl_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+ // texture for camera output
+ GLuint texture = textures[0];
+ GLuint menu_texture = textures[1];
const char *vshader_code = "attribute vec2 v_pos;\n\
attribute vec2 v_tex_coord;\n\
uniform vec2 u_scale;\n\
@@ -839,6 +925,8 @@ void main() {\n\
return EXIT_FAILURE;
}
camera_set_format(camera, camera->best_format, CAMERA_ACCESS_MMAP);
+ Menu curr_menu = false;
+ int menu_sel = 0;
while(true) {
struct udev_device *dev = NULL;
while (udev_monitor && (dev = udev_monitor_receive_device(udev_monitor))) {
@@ -852,6 +940,7 @@ void main() {\n\
udev_device_unref(dev);
}
SDL_Event event={0};
+ bool menu_needs_rerendering = false;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) goto quit;
if (event.type == SDL_KEYDOWN) switch (event.key.keysym.sym) {
@@ -862,10 +951,82 @@ void main() {\n\
strftime(name, sizeof name, "%Y-%m-%d-%H-%M-%S.jpg", tm);
camera_write_jpg(camera, name, 90);
} break;
+ case SDLK_ESCAPE:
+ if (curr_menu == MENU_NONE) {
+ curr_menu = MENU_MAIN;
+ } else if (curr_menu == MENU_MAIN) {
+ curr_menu = MENU_NONE;
+ } else {
+ curr_menu = MENU_MAIN;
+ }
+ menu_needs_rerendering = true;
+ break;
+ case SDLK_UP: if (curr_menu) {
+ menu_sel--;
+ if (menu_sel < 0)
+ menu_sel += strlen(menus[curr_menu]);
+ menu_needs_rerendering = true;
+ }
+ break;
+ case SDLK_DOWN: if (curr_menu) {
+ menu_sel++;
+ menu_sel %= strlen(menus[curr_menu]);
+ menu_needs_rerendering = true;
+ }
+ break;
+ case SDLK_RETURN: if (curr_menu) switch (menus[curr_menu][menu_sel]) {
+ case MENU_OPT_QUIT:
+ goto quit;
+ }
+ break;
}
}
+ static int prev_window_width, prev_window_height;
int window_width = 0, window_height = 0;
SDL_GetWindowSize(window, &window_width, &window_height);
+ // not all window size changes seem to generate WINDOWEVENT_RESIZED.
+ menu_needs_rerendering |= window_width != prev_window_width;
+ menu_needs_rerendering |= window_height != prev_window_height;
+ prev_window_width = window_width;
+ prev_window_height = window_height;
+ int menu_width = window_width / 2, menu_height = window_height / 2;
+ menu_width = (menu_width + 7) / 8 * 8; // play nice with pixel store alignment
+ menu_needs_rerendering &= curr_menu != 0;
+ if (menu_needs_rerendering) {
+ // render menu
+ SDL_Surface *menu = SDL_CreateRGBSurfaceWithFormat(0, menu_width, menu_height, 8, SDL_PIXELFORMAT_RGB24);
+ SDL_FillRect(menu, NULL, 0x332244);
+ int font_size = menu_height / 12;
+ TTF_SetFontSize(font, font_size);
+ size_t n_options = strlen(menus[curr_menu]);
+ for (int opt_idx = 0; opt_idx < (int)n_options; opt_idx++) {
+ char *option = NULL;
+ switch (menus[curr_menu][opt_idx]) {
+ case MENU_OPT_QUIT:
+ option = strdup("Quit");
+ break;
+ case MENU_OPT_RESOLUTION:
+ option = strdup("Resolution: XYZ");
+ break;
+ case MENU_OPT_INPUT:
+ option = strdup("Input: XYZ");
+ break;
+ default:
+ assert(false);
+ option = strdup("???");
+ }
+ render_text_to_surface(font, menu, 5, 5 + opt_idx * (5 + font_size),
+ menu_sel == opt_idx
+ ? (SDL_Color){255,255,0,255}
+ : (SDL_Color){255,255,255,255}, option);
+ free(option);
+ }
+ gl_BindTexture(GL_TEXTURE_2D, menu_texture);
+ SDL_LockSurface(menu);
+ gl_TexImage2D(GL_TEXTURE_2D, 0, GL_RGB, menu_width, menu_height, 0, GL_RGB, GL_UNSIGNED_BYTE, menu->pixels);
+ SDL_UnlockSurface(menu);
+ SDL_FreeSurface(menu);
+ }
gl_Viewport(0, 0, window_width, window_height);
gl_ClearColor(0, 0, 0, 1);
gl_Clear(GL_COLOR_BUFFER_BIT);
@@ -873,15 +1034,15 @@ void main() {\n\
double t = (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
// printf("%.1fms frame time\n",(t-last_time)*1000);
last_time = t; (void)last_time;
+ uint32_t frame_width = camera_frame_width(camera);
+ uint32_t frame_height = camera_frame_height(camera);
+
+ gl_UseProgram(program);
gl_ActiveTexture(GL_TEXTURE0);
gl_BindTexture(GL_TEXTURE_2D, texture);
if (camera_next_frame(camera)) {
camera_update_gl_texture_2d(camera);
}
- gl_UseProgram(program);
- uint32_t frame_width = camera_frame_width(camera);
- uint32_t frame_height = camera_frame_height(camera);
-
if ((uint64_t)window_width * frame_height > (uint64_t)frame_width * window_height) {
// window is wider than picture
float letterbox_size = window_width - (float)window_height / frame_height * frame_width;
@@ -901,7 +1062,14 @@ void main() {\n\
gl_Uniform1i(u_sampler, 0);
gl_Uniform1i(u_pixel_format, camera_pixel_format(camera));
gl_DrawArrays(GL_TRIANGLES, 0, (GLsizei)(3 * ntriangles));
- gl_BindTexture(GL_TEXTURE_2D, 0);
+ if (curr_menu) {
+ gl_ActiveTexture(GL_TEXTURE0);
+ gl_BindTexture(GL_TEXTURE_2D, menu_texture);
+ gl_Uniform2f(u_scale, 0.5f, 0.5f);
+ gl_Uniform1i(u_sampler, 0);
+ gl_Uniform1i(u_pixel_format, V4L2_PIX_FMT_RGB24);
+ gl_DrawArrays(GL_TRIANGLES, 0, (GLsizei)(3 * ntriangles));
+ }
SDL_GL_SwapWindow(window);
}
quit:
diff --git a/meson.build b/meson.build
index c972045..5b031a9 100644
--- a/meson.build
+++ b/meson.build
@@ -5,14 +5,16 @@ project('camlet',
]
)
-libv4l2 = dependency('libv4l2')
+v4l2 = dependency('libv4l2')
sdl2 = dependency('SDL2')
+sdl2_ttf = dependency('SDL2_ttf')
udev = dependency('libudev')
gl = dependency('GL')
sodium = dependency('libsodium')
+fontconfig = dependency('fontconfig')
if get_option('debug')
debug_def = '-DDEBUG=1'
else
debug_def = '-DDEBUG=0'
endif
-executable('camlet', 'main.c', 'lib/stb_image_write.c', dependencies: [libv4l2, sdl2, gl, udev, sodium], c_args: ['-Wno-unused-function', '-Wno-format-truncation', '-Wshadow', debug_def])
+executable('camlet', 'main.c', 'lib/stb_image_write.c', dependencies: [v4l2, sdl2, sdl2_ttf, gl, udev, sodium, fontconfig], c_args: ['-Wno-unused-function', '-Wno-format-truncation', '-Wshadow', debug_def])