initial commit main
authorJann Horn <jann@thejh.net>
Sun, 9 Aug 2026 21:45:12 +0000 (23:45 +0200)
committerJann Horn <jann@thejh.net>
Sun, 9 Aug 2026 21:45:12 +0000 (23:45 +0200)
.gitignore [new file with mode: 0644]
.gitmodules [new file with mode: 0644]
Makefile [new file with mode: 0644]
imgui [new submodule]
logscroll.cpp [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..1bd38b4
--- /dev/null
@@ -0,0 +1,4 @@
+*.o
+logscroll
+imgui.ini
+*.mp4
diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..028a4fb
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "imgui"]
+       path = imgui
+       url = https://github.com/ocornut/imgui
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..ed261f2
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,25 @@
+IMGUI_DIR = imgui/
+SOURCES = logscroll.cpp
+SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp
+SOURCES += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp
+OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES))))
+
+CXXFLAGS = -std=c++20 -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends
+CXXFLAGS += -g -Wall -Wformat
+LIBS = -lGL `pkg-config --static --libs glfw3`
+CXXFLAGS += `pkg-config --cflags glfw3`
+
+logscroll: $(OBJS)
+       $(CXX) -o $@ $^ $(CXXFLAGS) $(LIBS)
+
+%.o:%.cpp
+       $(CXX) $(CXXFLAGS) -c -o $@ $<
+
+%.o:$(IMGUI_DIR)/%.cpp
+       $(CXX) $(CXXFLAGS) -c -o $@ $<
+
+%.o:$(IMGUI_DIR)/backends/%.cpp
+       $(CXX) $(CXXFLAGS) -c -o $@ $<
+
+clean:
+       rm -f $(EXE) $(OBJS)
diff --git a/imgui b/imgui
new file mode 160000 (submodule)
index 0000000..46d39d5
--- /dev/null
+++ b/imgui
@@ -0,0 +1 @@
+Subproject commit 46d39d56febc2a00bdd2270dc88c8a13f2a0441a
diff --git a/logscroll.cpp b/logscroll.cpp
new file mode 100644 (file)
index 0000000..a6fb170
--- /dev/null
@@ -0,0 +1,161 @@
+#include <string>
+#include <vector>
+#include <err.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "imgui.h"
+#include "imgui_impl_glfw.h"
+#include "imgui_impl_opengl3.h"
+#include <GLFW/glfw3.h>
+
+static void glfw_error_callback(int error, const char* description) {
+  errx(1, "GLFW Error %d: %s", error, description);
+}
+
+static unsigned int physical_line_length = 30;
+static std::vector<std::string> physical_lines;
+static std::vector<std::string> logical_lines;
+static std::string next_line_buf;
+static size_t next_line_buf_used;
+static void add_line_(std::string& line) {
+  size_t num_phys_lines = (line.size() + (physical_line_length-1)) / physical_line_length;
+  if (num_phys_lines == 0)
+    physical_lines.push_back("");
+  for (size_t i=0; i<num_phys_lines; i++)
+    physical_lines.push_back(line.substr(i * physical_line_length, physical_line_length));
+}
+static void add_line(std::string line) {
+  logical_lines.push_back(line);
+  add_line_(line);
+}
+
+int main(void) {
+  fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) | O_NONBLOCK);
+
+  glfwSetErrorCallback(glfw_error_callback);
+  if (!glfwInit())
+      return 1;
+
+  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+
+  float main_scale = ImGui_ImplGlfw_GetContentScaleForMonitor(glfwGetPrimaryMonitor());
+  GLFWwindow* window = glfwCreateWindow((int)(1280 * main_scale), (int)(800 * main_scale), "logscroll", nullptr, nullptr);
+  if (window == nullptr)
+      return 1;
+  glfwMakeContextCurrent(window);
+  glfwSwapInterval(1);
+
+  IMGUI_CHECKVERSION();
+  ImGui::CreateContext();
+  ImGuiIO& io = ImGui::GetIO(); (void)io;
+  io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+  ImGui::StyleColorsDark();
+  ImGuiStyle& style = ImGui::GetStyle();
+  style.ScaleAllSizes(main_scale);
+  style.FontScaleDpi = main_scale;
+
+  ImGui_ImplGlfw_InitForOpenGL(window, true);
+  ImGui_ImplOpenGL3_Init(nullptr);
+
+  while (!glfwWindowShouldClose(window)) {
+    glfwPollEvents();
+
+    bool want_scroll_to_bottom = false;
+    while (1) {
+      next_line_buf.resize(next_line_buf_used + 512);
+      ssize_t read_res = read(0, next_line_buf.data()+next_line_buf_used, 512);
+      if (read_res < 0 && errno == EAGAIN)
+        break;
+      if (read_res == 0)
+        break;
+      if (read_res < 0)
+        err(1, "read failed");
+      next_line_buf_used += (size_t)read_res;
+
+      while (1) {
+        size_t newline_pos = next_line_buf.find('\n', 0);
+        if (newline_pos >= next_line_buf_used)
+          break;
+        add_line(next_line_buf.substr(0, newline_pos));
+        want_scroll_to_bottom = true;
+        next_line_buf = next_line_buf.substr(newline_pos+1);
+        next_line_buf_used -= newline_pos+1;
+      }
+    }
+
+    float line_height = ImGui::GetTextLineHeightWithSpacing();
+
+    // Start the Dear ImGui frame
+    ImGui_ImplOpenGL3_NewFrame();
+    ImGui_ImplGlfw_NewFrame();
+    ImGui::NewFrame();
+
+    //ImGui::ShowDemoWindow(nullptr);
+
+    const ImGuiViewport* viewport = ImGui::GetMainViewport();
+    unsigned int num_lines_in_window = viewport->Size.y / line_height;
+    ImGui::SetNextWindowPos(viewport->Pos);
+    ImGui::SetNextWindowSize(viewport->Size);
+
+    ImGui::SetNextWindowContentSize(ImVec2(0, line_height * (physical_lines.size()+1)));
+    if (1 && want_scroll_to_bottom) {
+      ssize_t scroll_y = (ssize_t)physical_lines.size() - (ssize_t)num_lines_in_window + 1;
+      if (scroll_y < 0)
+        scroll_y = 0;
+      ImGui::SetNextWindowScroll(ImVec2(0, line_height*scroll_y));
+    }
+    ImGui::Begin("logscroll", nullptr,
+        ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
+        ImGuiWindowFlags_AlwaysVerticalScrollbar);
+    unsigned int new_physical_line_length = ImGui::GetContentRegionAvail().x / ImGui::CalcTextSize("A").x;
+    if (physical_line_length != new_physical_line_length) {
+      physical_line_length = new_physical_line_length;
+      physical_lines.clear();
+      for (std::string& line : logical_lines)
+        add_line_(line);
+    }
+    size_t phys_line_startidx = ImGui::GetScrollY() / line_height;
+    size_t top_line_idx = phys_line_startidx - (phys_line_startidx % num_lines_in_window);
+    ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y + ImGui::GetScrollY()));
+    for (size_t i=top_line_idx; i<top_line_idx + num_lines_in_window; i++) {
+      if (i % num_lines_in_window == (phys_line_startidx - 1 + num_lines_in_window) % num_lines_in_window) {
+        ImDrawList *dl = ImGui::GetWindowDrawList();
+        ImVec2 p0 = ImGui::GetCursorScreenPos();
+        ImVec2 p1(p0.x + ImGui::GetContentRegionAvail().x, p0.y + line_height);
+        dl->AddRectFilled(p0, p1, ImGui::GetColorU32(ImVec4(1, 0, 0, 1)));
+        ImGui::Text(" ");
+        continue;
+      }
+      if (i == top_line_idx + num_lines_in_window)
+        break;
+      size_t cur_i = (i < phys_line_startidx) ? (i + num_lines_in_window) : i;
+      if (cur_i < physical_lines.size())
+        ImGui::Text("%s", physical_lines[cur_i].c_str());
+      else
+        ImGui::Text(" ");
+    }
+    ImGui::End();
+
+    // Rendering
+    ImGui::Render();
+    int display_w, display_h;
+    glfwGetFramebufferSize(window, &display_w, &display_h);
+    glViewport(0, 0, display_w, display_h);
+    glClearColor(0, 0, 0, 1);
+    glClear(GL_COLOR_BUFFER_BIT);
+    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+
+    glfwSwapBuffers(window);
+  }
+
+  // Cleanup
+  ImGui_ImplOpenGL3_Shutdown();
+  ImGui_ImplGlfw_Shutdown();
+  ImGui::DestroyContext();
+
+  glfwDestroyWindow(window);
+  glfwTerminate();
+
+  return 0;
+}