From 26ed4b74ce0260da385b6848bd295b58d1caf590 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Sat, 2 May 2026 22:53:35 -0400 Subject: [PATCH] There's now a bezos_ctl and bezos_cli that can control the presentation window. The bezos_ctl is compile as a windows app so that it won't flash a cmd.exe window when run from launchers like Stream Deck. The bezos_cli is compiled as a console app so you can use it from the command line. --- Makefile | 6 +-- meson.build | 45 +++++++++++++++++++-- sample/about-bezos.md | 4 +- src/bezos_ctl.cpp | 71 +++++++++++++++++++++++++++++++++ src/main.cpp | 91 +++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 204 insertions(+), 13 deletions(-) create mode 100644 src/bezos_ctl.cpp diff --git a/Makefile b/Makefile index 99d5518..93ba77e 100644 --- a/Makefile +++ b/Makefile @@ -27,16 +27,16 @@ debug_build: run: build ifeq '$(OS)' 'Windows_NT' powershell "cp ./builddir/bezos.exe ." - ./bezos ${SAMPLE} + ./bezos -d ${SAMPLE} else - ./builddir/bezos ${SAMPLE} + ./builddir/bezos -d ${SAMPLE} endif debug: build gdb --nx -x .gdbinit --ex run --args builddir/bezos debug_run: build - gdb --nx -x .gdbinit --batch --ex run --ex bt --ex q --args builddir/bezos ${SAMPLE} + gdb --nx -x .gdbinit --batch --ex run --ex bt --ex q --args builddir/bezos -d ${SAMPLE} clean: meson compile --clean -C builddir diff --git a/meson.build b/meson.build index 645a83f..fa73d09 100644 --- a/meson.build +++ b/meson.build @@ -3,7 +3,7 @@ project('bezos-loves-slides', 'cpp', version: '0.2.0', default_options: [ - 'cpp_std=c++20', + 'cpp_std=c++23', 'cpp_args=-D_GLIBCXX_DEBUG=1 -D_GLIBCXX_DEBUG_PEDANTIC=1', ]) @@ -87,6 +87,8 @@ dependencies += [ ] +inc_dirs = include_directories('src') + sources = [ 'src/dbc.cpp', 'src/control_ui.cpp', @@ -99,16 +101,51 @@ tests = [ 'tests/parsing.cpp' ] -executable('bezos', sources + ['src/main.cpp'], + +bezos_lib = static_library('bezos', + sources, + cpp_args: cpp_args, + include_directories: inc_dirs, + override_options: exe_defaults, + dependencies: dependencies) + +bezos_dep = declare_dependency( + link_with: bezos_lib, + include_directories: inc_dirs) + +dependencies += [ bezos_dep ] + +executable('bezos', ['src/main.cpp'], cpp_args: cpp_args, link_args: link_args, - include_directories: include_directories('src'), + include_directories: inc_dirs, + win_subsystem: 'console', override_options: exe_defaults, dependencies: dependencies) -executable('runtests', sources + tests, +# bezos_ctrl is a version of bezos_cli compiled to be a windows +# app instead of console. Ironically you have to do this so that +# stream deck and other launchers don't open a cmd.exe to run it. +executable('bezos_ctrl', ['src/bezos_ctl.cpp'], + cpp_args: cpp_args, + link_args: link_args, + include_directories: inc_dirs, + override_options: exe_defaults, + win_subsystem: 'windows', + dependencies: dependencies) + +executable('bezos_cli', ['src/bezos_ctl.cpp'], cpp_args: cpp_args, link_args: link_args, include_directories: include_directories('src'), + win_subsystem: 'console', + override_options: exe_defaults, + dependencies: dependencies) + +executable('runtests', tests, + cpp_args: cpp_args, + link_args: link_args, + include_directories: inc_dirs, override_options: exe_defaults, + win_subsystem: 'console', dependencies: dependencies + [catch2]) diff --git a/sample/about-bezos.md b/sample/about-bezos.md index 14d98fb..354bac5 100644 --- a/sample/about-bezos.md +++ b/sample/about-bezos.md @@ -79,8 +79,8 @@ Bare content like this { "bg_color": [10, 10, 25, 255], "font_color": [255, 146, 0, 255], - "title_color": [255, 255, 0, 255], - "font_size": 80, + "title_color": [255, 200, 0, 255], + "font_size": 120, "title_size": 120, "font_padding": 20 } diff --git a/src/bezos_ctl.cpp b/src/bezos_ctl.cpp new file mode 100644 index 0000000..3855f8c --- /dev/null +++ b/src/bezos_ctl.cpp @@ -0,0 +1,71 @@ +#include +#include "dbc.hpp" +#include +#include +#include + +using namespace std::chrono_literals; + +const int DEFAULT_PORT=9898; + +struct Options { + bool help{false}; + bool focus{false}; + bool error{false}; + unsigned short port=DEFAULT_PORT; +}; + +void request_focus(Options& options) { + fmt::println("Moving control window front: port={}", options.port); + sf::UdpSocket sock; + uint32_t cmd = 2; + auto localhost = sf::IpAddress::getLocalAddress(); + auto result = sock.send(&cmd, sizeof cmd, *localhost, options.port); +} + +void print_usage() { + fmt::println("USAGE: bezos [-p {}] [-hf] -d deck.md", DEFAULT_PORT); +} + +Options parse_options(int argc, char* argv[]) { + int opt = 0; + Options result; + + while((opt = getopt(argc, argv, "fhp:")) != -1) { + switch(opt) { + case 'f': + result.focus=true; + break; + case 'h': + print_usage(); + return {.help=true}; + break; + case 'p': + result.port = std::stoi(optarg); + break; + default: + print_usage(); + return {.error=true}; + } + } + + if(!result.focus) { + print_usage(); + } + + return result; +} + + +int main(int argc, char *argv[]) { + auto options = parse_options(argc, argv); + + if(options.focus) { + request_focus(options); + return 0; + } else if(options.help) { + return 0; + } else if(options.error) { + return 1; + } +} diff --git a/src/main.cpp b/src/main.cpp index 5d694e8..15b7411 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,10 +9,20 @@ #include #include #include +#include #include +#include using namespace std::chrono_literals; +struct Options { + std::string deck_file{}; + bool deck_given{false}; + bool help{false}; + bool error{false}; + unsigned short port=9898; +}; + std::shared_ptr load_slides(const std::string& input_md, std::shared_ptr backend) { try { auto deck = parse_slides(input_md, [&](nlohmann::json& config) { @@ -59,8 +69,72 @@ struct ChangeDetector { } }; +void print_usage() { + fmt::println("USAGE: bezos [-p PORT] [-hf] -d deck.md"); +} + +std::optional listen_control(Options& opts) { + sf::UdpSocket socket; + auto status = socket.bind(opts.port); + + if(status == sf::Socket::Status::Error) { + fmt::println("Failed to bind port"); + return std::nullopt; + } + + socket.setBlocking(false); + + fmt::println("Focus listener on port {}", opts.port); + + return socket; +} + +Options parse_options(int argc, char* argv[]) { + int opt = 0; + Options result; + + while((opt = getopt(argc, argv, "hp:d:")) != -1) { + switch(opt) { + case 'h': + print_usage(); + return {.help=true}; + break; + case 'p': + result.port = std::stoi(optarg); + break; + case 'd': + result.deck_given=true; + result.deck_file = optarg; + break; + default: + print_usage(); + return {.error=true}; + } + } + + if(!result.deck_given) { + print_usage(); + result.error = true; + } + + return result; +} + + int main(int argc, char *argv[]) { - dbc::check(argc >= 2, "USAGE: bezos my_fucking_slides.md"); + auto options = parse_options(argc, argv); + + if(options.help) { + return 0; + } else if(options.error) { + return 1; + } + + auto control = listen_control(options); + + if(control == std::nullopt) { + fmt::println("Error binding UDP port {}", options.port); + } auto& modes = sf::VideoMode::getFullscreenModes(); auto screen_mode = std::find_if(modes.begin(), modes.end(), [=](const auto& a) -> bool { @@ -77,7 +151,7 @@ int main(int argc, char *argv[]) { auto backend = std::make_shared(); - auto slides = load_slides(argv[1], backend); + auto slides = load_slides(options.deck_file, backend); if(!slides) { fmt::println("ERROR in your .md file"); @@ -90,10 +164,19 @@ int main(int argc, char *argv[]) { dbc::check(control_ui.$status != nullptr, "bad ptr"); - ChangeDetector slides_reloader{argv[1]}; + ChangeDetector slides_reloader{options.deck_file}; ChangeDetector layout_reloader{slides->$deck->config["layouts"]}; while(controller.isOpen()) { + uint32_t cmd = 0; + std::size_t received = 0; + std::optional sender; + + if(control->receive(&cmd, sizeof cmd, received, sender, options.port) == sf::Socket::Status::Done) { + fmt::println("sender {} sent {}", sender->toString(), cmd); + sf::Mouse::setPosition({WINDOW_WIDTH - 100, WINDOW_HEIGHT - 100}, presenter); + } + while(const auto event = presenter.pollEvent()) { if(event) slides->handle_events(presenter, *event); } @@ -112,7 +195,7 @@ int main(int argc, char *argv[]) { // save the current slide auto current_slide = slides->$deck->current; // load the new one - auto new_slides = load_slides(argv[1], backend); + auto new_slides = load_slides(options.deck_file, backend); if(new_slides) { new_slides->set_slide(current_slide);