From: Emil Williams Date: Sun, 4 Feb 2024 17:46:12 +0000 (+0000) Subject: Dear ImGui moontalk X-Git-Url: https://git.xolatile.top/?a=commitdiff_plain;h=47738e21ae5dcbc807654cd73b20a2f2b2af4504;p=public-moontalk.git Dear ImGui moontalk --- diff --git a/client/moontalk-cli.c b/client/moontalk-cli.c new file mode 100644 index 0000000..7cdd855 --- /dev/null +++ b/client/moontalk-cli.c @@ -0,0 +1,168 @@ +/* moontalk.c - @BAKE cc -O2 -std=gnu99 -Wall -Wextra -pedantic $@ -o $* -lncurses -ltinfo $+ + * Written by Emil. + * Licensed under the GPLv3 only. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#define SERV "7ks473deh6ggtwqsvbqdurepv5i6iblpbkx33b6cydon3ajph73sssad.onion" +#define PORT "50000" + +#define streq(a,b) (!strcmp(a,b)) + +int g_row, g_col; + +void free_screen(void) { + endwin(); +} + +void init_screen(void) { + initscr(); + cbreak(); + noecho(); + keypad(stdscr, ERR); + nodelay(stdscr, TRUE); +} + +void handle_winch(int x) { + (void)x; + struct winsize w; + signal(SIGWINCH, SIG_IGN); + + endwin(); + init_screen(); + refresh(); + clear(); + + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + g_row = w.ws_row; + g_col = w.ws_col; + + signal(SIGWINCH, handle_winch); +} + +int g_sockfd = -1; + +void free_connection(void) { + close(g_sockfd); + g_sockfd = -1; + + /* the program should be at an end. If we're reconnecting, then at + the top level (main) we'd free, even though the main's sockfd is now + out-of-date, we can simply ignore that and take the result of socket + init */ +} + +/* always returns an accessible socket */ +int init_connection(char * serv, char * port) { + int status, sockfd; + struct addrinfo hints, * res; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6 + hints.ai_socktype = SOCK_STREAM; // TCP stream sockets + + if ((status = getaddrinfo(serv, port, &hints, &res)) != 0) { + fprintf(stderr, "init_connection: %s\n", gai_strerror(status)); + exit(1); + } + + if ((sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) + { goto error; } + if (connect(sockfd, res->ai_addr, res->ai_addrlen)) + { goto error; } + + freeaddrinfo(res); + + g_sockfd = sockfd; + return sockfd; +error: + perror("init_connection"); + exit(1); + __builtin_unreachable(); +} + +int main (int argc, char ** argv) { + char * argv0 = argv[0]; + char * serv = SERV, * port = PORT, * name = "anonymous"; + + #define PREFIX_MAX 21 + char raw[(2 << 9) + (2 << 13)]; + char * sendbuf = raw + PREFIX_MAX, * recvbuf = raw + (2 << 9); + size_t sendlen = 0, sendmax = (2 << 9) - PREFIX_MAX, + recvlen = 0, recvmax = 2 << 13; + + int sockfd; + + while (++argv, --argc) { + if (streq(*argv, "-help")) { + printf("%s: HELP\n", argv0); + return 1; + } + if (argc - 1) + { --argc; ++argv; } + else { + printf("%s: %s requires argument\n", argv0, *argv); + return 1; + } + if (streq(*(argv-1), "-serv")) { + printf("serv: %s\n", *argv); + serv = *argv; + } + else if (streq(*(argv-1), "-port")) { + printf("port: %s\n", *argv); + } + else if (streq(*(argv-1), "-name")) { + printf("name: %s\n", *argv); + } + } + + atexit(free_screen); + init_screen(); + signal(SIGWINCH, handle_winch); + printw("Connecting to %s:%s as %s\n", serv, port, name); + refresh(); + sockfd = init_connection(serv, port); + float interval = 1 / 30; + int ch; + time_t t; + struct tm * tm; + while (1) { + /* send */ + ch = getch(); + if (ch != -1 && ch != '\n') { + sendbuf[sendlen++] = ch; + } else if (ch == '\n') { + t = time(NULL); + tm = gmtime(&t); + strftime(sendbuf - PREFIX_MAX, PREFIX_MAX, "<%Y/%m/%d %H:%M:%S ", tm); + sendbuf[sendlen++] = '\n'; + send(sockfd, sendbuf - PREFIX_MAX, sendlen + PREFIX_MAX, 0); + sendlen = 0; + memset(sendbuf, 0, sendlen); + } + /* recv */ + recvlen = recv(sockfd, recvbuf, recvmax, MSG_DONTWAIT); + /* render */ + clear(); + mvaddnstr(0, 0, recvbuf, recvlen); + mvaddnstr(g_row - 1, 0, sendbuf, sendlen); + refresh(); + /* sleep */ + sleep(interval); + } +} diff --git a/client/moontalk-imgui.cpp b/client/moontalk-imgui.cpp new file mode 100644 index 0000000..d711a24 --- /dev/null +++ b/client/moontalk-imgui.cpp @@ -0,0 +1,252 @@ +/* moontalk-imgui.cpp - Dear ImGui frontend + * written by an Anon. https://boards.4chan.org/g/thread/98813374#p98826333 + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define SERV "7ks473deh6ggtwqsvbqdurepv5i6iblpbkx33b6cydon3ajph73sssad.onion" +#define PORT "50000" + +static int moontalk_fd = -1; +static std::mutex inqueue_lock; +static std::queue inqueue; +static std::vector moontalk_lines; + +int tcp_connect(const char* addr, const char *port) +{ + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + struct addrinfo* res; + int status = getaddrinfo(addr, port, &hints, &res); + if (status != 0) + errx(1, "getaddrinfo: %s", gai_strerror(status)); + + int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (fd < 0) + err(1, "socket"); + + if (connect(fd, res->ai_addr, res->ai_addrlen)) + err(1, "socket"); + + freeaddrinfo(res); + return fd; +} + +void moontalk_send(const std::string& line) +{ + char timebuf[256]; + + time_t t = time(NULL);; + struct tm * tm; + tm = gmtime(&t); + strftime(timebuf, sizeof(timebuf), "<%Y/%m/%d %H:%M:%S ", tm); + + + std::string out; + out += timebuf; + out += line; + if (line.at(line.size()-1) != '\n') out.push_back('\n'); + + send(moontalk_fd, out.data(), out.size(), 0); + printf("send %s", out.c_str()); +} + +void read_loop() +{ + char buffer[4096]; + size_t pos = 0; + + for (;;) { + ssize_t r = read(moontalk_fd, buffer + pos, sizeof(buffer) - pos); + if (r < 0) { + if (r == EINTR) + continue; + err(1, "read"); + } + pos += r; + + // scan lines and push them to inqueue + size_t start = 0; + size_t end = 0; + while (end < pos) { + if (buffer[end] == '\n') { + std::string line = {buffer + start, buffer + end+1}; + + inqueue_lock.lock(); + inqueue.push(std::move(line)); + inqueue_lock.unlock(); + + start = end+1; + end = end+1; + } else { + end++; + } + } + if (start != 0) { + memmove(buffer, buffer+start, pos-start); + pos -= start; + } + } +} + +void ui() +{ + static std::string input; + + if (ImGui::Begin("Chat")) { + for (auto& line : moontalk_lines) { + ImGui::TextUnformatted(line.c_str()); + } + ImGui::End(); + } + + if (ImGui::Begin("Input")) { + ImGui::InputText("Input", &input); + if (ImGui::Button("Send")) { + moontalk_send(input); + input.clear(); + } + + // TODO or make it separate window and handle the layout on docking layer + ImGui::End(); + } +} + +int main(int argc, char *argv[]) +{ + if (SDL_Init(SDL_INIT_EVERYTHING) != 0) + err(1, "SDL_Init: %s", SDL_GetError()); + + // From 2.0.18: Enable native IME. +#ifdef SDL_HINT_IME_SHOW_UI + SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); +#endif + + SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_Window* window = SDL_CreateWindow("moontalk", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); + if (!window) + err(1, "SDL_CreateWindow: %s", SDL_GetError()); + + SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); + if (renderer == nullptr) + err(1, "SDL_CreateRenderer: %s", SDL_GetError()); + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + // io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows + //io.ConfigViewportsNoAutoMerge = true; + //io.ConfigViewportsNoTaskBarIcon = true; + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsLight(); + + // Setup Platform/Renderer backends + ImGui_ImplSDL2_InitForSDLRenderer(window, renderer); + ImGui_ImplSDLRenderer2_Init(renderer); + + // Load Fonts + // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. + // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). + // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. + // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. + // - Read 'docs/FONTS.md' for more instructions and details. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //io.Fonts->AddFontDefault(); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //IM_ASSERT(font != nullptr); + + // setup + fprintf(stderr, "Connecting...\n"); + moontalk_fd = tcp_connect(SERV, PORT); + fprintf(stderr, "Connected\n"); + + std::thread t(read_loop); + + bool done = false; + while (!done) { + // Poll and handle events (inputs, window resize, etc.) + // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. + // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. + // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. + // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. + SDL_Event event; + while (SDL_PollEvent(&event)) { + ImGui_ImplSDL2_ProcessEvent(&event); + if (event.type == SDL_QUIT) + done = true; + if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) + done = true; + } + + inqueue_lock.lock(); + if (!inqueue.empty()) { + std::string line = std::move(inqueue.front()); + inqueue.pop(); + moontalk_lines.push_back(std::move(line)); + } + inqueue_lock.unlock(); + + // Start the Dear ImGui frame + ImGui_ImplSDLRenderer2_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + + ImGui::DockSpaceOverViewport(ImGui::GetMainViewport()); + + ui(); + + // Rendering + ImGui::Render(); + SDL_RenderSetScale(renderer, io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 1); + SDL_RenderClear(renderer); + ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData()); + SDL_RenderPresent(renderer); + } + + // Cleanup + ImGui_ImplSDLRenderer2_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + + close(moontalk_fd); + + return 0; +} diff --git a/client/moontalk.c b/client/moontalk.c deleted file mode 100644 index 7cdd855..0000000 --- a/client/moontalk.c +++ /dev/null @@ -1,168 +0,0 @@ -/* moontalk.c - @BAKE cc -O2 -std=gnu99 -Wall -Wextra -pedantic $@ -o $* -lncurses -ltinfo $+ - * Written by Emil. - * Licensed under the GPLv3 only. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -#define SERV "7ks473deh6ggtwqsvbqdurepv5i6iblpbkx33b6cydon3ajph73sssad.onion" -#define PORT "50000" - -#define streq(a,b) (!strcmp(a,b)) - -int g_row, g_col; - -void free_screen(void) { - endwin(); -} - -void init_screen(void) { - initscr(); - cbreak(); - noecho(); - keypad(stdscr, ERR); - nodelay(stdscr, TRUE); -} - -void handle_winch(int x) { - (void)x; - struct winsize w; - signal(SIGWINCH, SIG_IGN); - - endwin(); - init_screen(); - refresh(); - clear(); - - ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); - g_row = w.ws_row; - g_col = w.ws_col; - - signal(SIGWINCH, handle_winch); -} - -int g_sockfd = -1; - -void free_connection(void) { - close(g_sockfd); - g_sockfd = -1; - - /* the program should be at an end. If we're reconnecting, then at - the top level (main) we'd free, even though the main's sockfd is now - out-of-date, we can simply ignore that and take the result of socket - init */ -} - -/* always returns an accessible socket */ -int init_connection(char * serv, char * port) { - int status, sockfd; - struct addrinfo hints, * res; - - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6 - hints.ai_socktype = SOCK_STREAM; // TCP stream sockets - - if ((status = getaddrinfo(serv, port, &hints, &res)) != 0) { - fprintf(stderr, "init_connection: %s\n", gai_strerror(status)); - exit(1); - } - - if ((sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) - { goto error; } - if (connect(sockfd, res->ai_addr, res->ai_addrlen)) - { goto error; } - - freeaddrinfo(res); - - g_sockfd = sockfd; - return sockfd; -error: - perror("init_connection"); - exit(1); - __builtin_unreachable(); -} - -int main (int argc, char ** argv) { - char * argv0 = argv[0]; - char * serv = SERV, * port = PORT, * name = "anonymous"; - - #define PREFIX_MAX 21 - char raw[(2 << 9) + (2 << 13)]; - char * sendbuf = raw + PREFIX_MAX, * recvbuf = raw + (2 << 9); - size_t sendlen = 0, sendmax = (2 << 9) - PREFIX_MAX, - recvlen = 0, recvmax = 2 << 13; - - int sockfd; - - while (++argv, --argc) { - if (streq(*argv, "-help")) { - printf("%s: HELP\n", argv0); - return 1; - } - if (argc - 1) - { --argc; ++argv; } - else { - printf("%s: %s requires argument\n", argv0, *argv); - return 1; - } - if (streq(*(argv-1), "-serv")) { - printf("serv: %s\n", *argv); - serv = *argv; - } - else if (streq(*(argv-1), "-port")) { - printf("port: %s\n", *argv); - } - else if (streq(*(argv-1), "-name")) { - printf("name: %s\n", *argv); - } - } - - atexit(free_screen); - init_screen(); - signal(SIGWINCH, handle_winch); - printw("Connecting to %s:%s as %s\n", serv, port, name); - refresh(); - sockfd = init_connection(serv, port); - float interval = 1 / 30; - int ch; - time_t t; - struct tm * tm; - while (1) { - /* send */ - ch = getch(); - if (ch != -1 && ch != '\n') { - sendbuf[sendlen++] = ch; - } else if (ch == '\n') { - t = time(NULL); - tm = gmtime(&t); - strftime(sendbuf - PREFIX_MAX, PREFIX_MAX, "<%Y/%m/%d %H:%M:%S ", tm); - sendbuf[sendlen++] = '\n'; - send(sockfd, sendbuf - PREFIX_MAX, sendlen + PREFIX_MAX, 0); - sendlen = 0; - memset(sendbuf, 0, sendlen); - } - /* recv */ - recvlen = recv(sockfd, recvbuf, recvmax, MSG_DONTWAIT); - /* render */ - clear(); - mvaddnstr(0, 0, recvbuf, recvlen); - mvaddnstr(g_row - 1, 0, sendbuf, sendlen); - refresh(); - /* sleep */ - sleep(interval); - } -}