]> git.xolatile.top Git - public-moontalk.git/commitdiff
Dear ImGui moontalk
authorEmil Williams <emilwilliams@tuta.io>
Sun, 4 Feb 2024 17:46:12 +0000 (17:46 +0000)
committerEmil Williams <emilwilliams@tuta.io>
Sun, 4 Feb 2024 17:46:12 +0000 (17:46 +0000)
client/moontalk-cli.c [new file with mode: 0644]
client/moontalk-imgui.cpp [new file with mode: 0644]
client/moontalk.c [deleted file]

diff --git a/client/moontalk-cli.c b/client/moontalk-cli.c
new file mode 100644 (file)
index 0000000..7cdd855
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <time.h>
+
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <ncurses.h>
+
+#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 (file)
index 0000000..d711a24
--- /dev/null
@@ -0,0 +1,252 @@
+/* moontalk-imgui.cpp - Dear ImGui frontend\r
+ * written by an Anon. https://boards.4chan.org/g/thread/98813374#p98826333\r
+ */\r
+\r
+#include <imgui.h>\r
+#include <imgui_stdlib.h>\r
+#include <imgui_impl_sdl2.h>\r
+#include <imgui_impl_sdlrenderer2.h>\r
+#include <SDL.h>\r
+\r
+#include <arpa/inet.h>\r
+#include <netdb.h>\r
+#include <netinet/in.h>\r
+#include <sys/types.h>\r
+#include <sys/socket.h>\r
+#include <err.h>\r
+#include <stdio.h>\r
+#include <unistd.h>\r
+\r
+#include <vector>\r
+#include <string>\r
+#include <thread>\r
+#include <mutex>\r
+#include <queue>\r
+\r
+#define SERV "7ks473deh6ggtwqsvbqdurepv5i6iblpbkx33b6cydon3ajph73sssad.onion"\r
+#define PORT "50000"\r
+\r
+static int moontalk_fd = -1;\r
+static std::mutex inqueue_lock;\r
+static std::queue<std::string> inqueue;\r
+static std::vector<std::string> moontalk_lines;\r
+\r
+int tcp_connect(const char* addr, const char *port)\r
+{\r
+    struct addrinfo hints;\r
+    memset(&hints, 0, sizeof(struct addrinfo));\r
+    hints.ai_family = AF_UNSPEC;\r
+    hints.ai_socktype = SOCK_STREAM;\r
+\r
+    struct addrinfo* res;\r
+    int status = getaddrinfo(addr, port, &hints, &res);\r
+    if (status != 0)\r
+        errx(1, "getaddrinfo: %s", gai_strerror(status));\r
+\r
+    int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);\r
+    if (fd < 0)\r
+        err(1, "socket");\r
+\r
+    if (connect(fd, res->ai_addr, res->ai_addrlen))\r
+        err(1, "socket");\r
+\r
+    freeaddrinfo(res);\r
+    return fd;\r
+}\r
+\r
+void moontalk_send(const std::string& line)\r
+{\r
+    char timebuf[256];\r
+\r
+    time_t t = time(NULL);;\r
+    struct tm * tm;\r
+    tm = gmtime(&t);\r
+    strftime(timebuf, sizeof(timebuf), "<%Y/%m/%d %H:%M:%S ", tm);\r
+\r
+\r
+    std::string out;\r
+    out += timebuf;\r
+    out += line;\r
+    if (line.at(line.size()-1) != '\n') out.push_back('\n');\r
+\r
+    send(moontalk_fd, out.data(), out.size(), 0);\r
+    printf("send %s", out.c_str());\r
+}\r
+\r
+void read_loop()\r
+{\r
+    char buffer[4096];\r
+    size_t pos = 0;\r
+\r
+    for (;;) {\r
+        ssize_t r = read(moontalk_fd, buffer + pos, sizeof(buffer) - pos);\r
+        if (r < 0) {\r
+            if (r == EINTR)\r
+                continue;\r
+            err(1, "read");\r
+        }\r
+        pos += r;\r
+\r
+        // scan lines and push them to inqueue\r
+        size_t start = 0;\r
+        size_t end = 0;\r
+        while (end < pos) {\r
+            if (buffer[end] == '\n') {\r
+                std::string line = {buffer + start, buffer + end+1};\r
+\r
+                inqueue_lock.lock();\r
+                inqueue.push(std::move(line));\r
+                inqueue_lock.unlock();\r
+\r
+                start = end+1;\r
+                end = end+1;\r
+            } else {\r
+                end++;\r
+            }\r
+        }\r
+        if (start != 0) {\r
+            memmove(buffer, buffer+start, pos-start);\r
+            pos -= start;\r
+        }\r
+    }\r
+}\r
+\r
+void ui()\r
+{\r
+    static std::string input;\r
+\r
+    if (ImGui::Begin("Chat")) {\r
+        for (auto& line : moontalk_lines) {\r
+            ImGui::TextUnformatted(line.c_str());\r
+        }\r
+        ImGui::End();\r
+    }\r
+\r
+    if (ImGui::Begin("Input")) {\r
+        ImGui::InputText("Input", &input);\r
+        if (ImGui::Button("Send")) {\r
+            moontalk_send(input);\r
+            input.clear();\r
+        }\r
+\r
+        // TODO or make it separate window and handle the layout on docking layer\r
+        ImGui::End();\r
+    }\r
+}\r
+\r
+int main(int argc, char *argv[])\r
+{\r
+    if (SDL_Init(SDL_INIT_EVERYTHING) != 0)\r
+        err(1, "SDL_Init: %s", SDL_GetError());\r
+\r
+    // From 2.0.18: Enable native IME.\r
+#ifdef SDL_HINT_IME_SHOW_UI\r
+    SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");\r
+#endif\r
+\r
+    SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);\r
+    SDL_Window* window = SDL_CreateWindow("moontalk", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags);\r
+    if (!window)\r
+        err(1, "SDL_CreateWindow: %s", SDL_GetError());\r
+\r
+    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED);\r
+    if (renderer == nullptr)\r
+        err(1, "SDL_CreateRenderer: %s", SDL_GetError());\r
+\r
+    // Setup Dear ImGui context\r
+    IMGUI_CHECKVERSION();\r
+    ImGui::CreateContext();\r
+    ImGuiIO& io = ImGui::GetIO(); (void)io;\r
+    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls\r
+    io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls\r
+    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;         // Enable Docking\r
+    // io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;       // Enable Multi-Viewport / Platform Windows\r
+    //io.ConfigViewportsNoAutoMerge = true;\r
+    //io.ConfigViewportsNoTaskBarIcon = true;\r
+\r
+    // Setup Dear ImGui style\r
+    ImGui::StyleColorsDark();\r
+    //ImGui::StyleColorsLight();\r
+\r
+    // Setup Platform/Renderer backends\r
+    ImGui_ImplSDL2_InitForSDLRenderer(window, renderer);\r
+    ImGui_ImplSDLRenderer2_Init(renderer);\r
+\r
+    // Load Fonts\r
+    // - 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.\r
+    // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.\r
+    // - 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).\r
+    // - 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.\r
+    // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering.\r
+    // - Read 'docs/FONTS.md' for more instructions and details.\r
+    // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !\r
+    //io.Fonts->AddFontDefault();\r
+    //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f);\r
+    //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);\r
+    //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);\r
+    //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);\r
+    //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese());\r
+    //IM_ASSERT(font != nullptr);\r
+\r
+    // setup\r
+    fprintf(stderr, "Connecting...\n");\r
+    moontalk_fd = tcp_connect(SERV, PORT);\r
+    fprintf(stderr, "Connected\n");\r
+\r
+    std::thread t(read_loop);\r
+\r
+    bool done = false;\r
+    while (!done) {\r
+        // Poll and handle events (inputs, window resize, etc.)\r
+        // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.\r
+        // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.\r
+        // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.\r
+        // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.\r
+        SDL_Event event;\r
+        while (SDL_PollEvent(&event)) {\r
+            ImGui_ImplSDL2_ProcessEvent(&event);\r
+            if (event.type == SDL_QUIT)\r
+                done = true;\r
+            if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window))\r
+                done = true;\r
+        }\r
+\r
+        inqueue_lock.lock();\r
+        if (!inqueue.empty()) {\r
+            std::string line = std::move(inqueue.front());\r
+            inqueue.pop();\r
+            moontalk_lines.push_back(std::move(line));\r
+        }\r
+        inqueue_lock.unlock();\r
+\r
+        // Start the Dear ImGui frame\r
+        ImGui_ImplSDLRenderer2_NewFrame();\r
+        ImGui_ImplSDL2_NewFrame();\r
+        ImGui::NewFrame();\r
+\r
+        ImGui::DockSpaceOverViewport(ImGui::GetMainViewport());\r
+\r
+        ui();\r
+\r
+        // Rendering\r
+        ImGui::Render();\r
+        SDL_RenderSetScale(renderer, io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y);\r
+        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 1);\r
+        SDL_RenderClear(renderer);\r
+        ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData());\r
+        SDL_RenderPresent(renderer);\r
+    }\r
+\r
+    // Cleanup\r
+    ImGui_ImplSDLRenderer2_Shutdown();\r
+    ImGui_ImplSDL2_Shutdown();\r
+    ImGui::DestroyContext();\r
+\r
+    SDL_DestroyRenderer(renderer);\r
+    SDL_DestroyWindow(window);\r
+    SDL_Quit();\r
+\r
+    close(moontalk_fd);\r
+\r
+    return 0;\r
+}\r
diff --git a/client/moontalk.c b/client/moontalk.c
deleted file mode 100644 (file)
index 7cdd855..0000000
+++ /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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <signal.h>
-#include <time.h>
-
-#include <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netdb.h>
-#include <arpa/inet.h>
-#include <netinet/in.h>
-
-#include <ncurses.h>
-
-#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);
-       }
-}