#include "VISIRenderer.hpp" #include #include #include #include "athena/Global.hpp" #include "logvisor/logvisor.hpp" #include "../version.h" #include #include #include #include #include #define MWM_HINTS_FUNCTIONS (1L << 0) #define MWM_HINTS_DECORATIONS (1L << 1) #define MWM_DECOR_BORDER (1L << 1) #define MWM_DECOR_RESIZEH (1L << 2) #define MWM_DECOR_TITLE (1L << 3) #define MWM_DECOR_MENU (1L << 4) #define MWM_DECOR_MINIMIZE (1L << 5) #define MWM_DECOR_MAXIMIZE (1L << 6) #define MWM_FUNC_RESIZE (1L << 1) #define MWM_FUNC_MOVE (1L << 2) #define MWM_FUNC_MINIMIZE (1L << 3) #define MWM_FUNC_MAXIMIZE (1L << 4) #define MWM_FUNC_CLOSE (1L << 5) typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); static glXCreateContextAttribsARBProc glXCreateContextAttribsARB = nullptr; static const int ContextAttribList[7][7] = { {GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 5, GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0}, {GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 4, GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0}, {GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 3, GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0}, {GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 2, GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0}, {GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 1, GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0}, {GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 0, GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0}, {GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 3, GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0}, }; static bool s_glxError; static int ctxErrorHandler(Display* /*dpy*/, XErrorEvent* /*ev*/) { s_glxError = true; return 0; } static logvisor::Module Log("visigen-xlib"); static logvisor::Module AthenaLog("Athena"); static void AthenaExc(athena::error::Level level, const char* /*file*/, const char*, int /*line*/, fmt::string_view fmt, fmt::format_args args) { AthenaLog.vreport(logvisor::Level(level), fmt, args); } static Display* xDisp; static Window windowId; static void UpdatePercent(float percent) { XLockDisplay(xDisp); std::string title = fmt::format(FMT_STRING("VISIGen [{:g}%]"), double(percent * 100.f)); XChangeProperty(xDisp, windowId, XA_WM_NAME, XA_STRING, 8, PropModeReplace, reinterpret_cast(title.c_str()), int(title.size())); XUnlockDisplay(xDisp); } /* Empty handler for SIGINT */ static void _sigint(int) {} int main(int argc, char** argv) { if (argc > 1 && !strcmp(argv[1], "--dlpackage")) { fmt::print(FMT_STRING("{}\n"), METAFORCE_DLPACKAGE); return 100; } /* Program is portable to all locales */ setlocale(LC_ALL, ""); logvisor::RegisterStandardExceptions(); logvisor::RegisterConsoleLogger(); atSetExceptionHandler(AthenaExc); VISIRenderer renderer(argc, argv); if (!XInitThreads()) { Log.report(logvisor::Error, FMT_STRING("X doesn't support multithreading")); return 1; } /* Open Xlib Display */ xDisp = XOpenDisplay(nullptr); if (!xDisp) { Log.report(logvisor::Error, FMT_STRING("Can't open X display")); return 1; } /* Default screen */ int xDefaultScreen = DefaultScreen(xDisp); Screen* screen = ScreenOfDisplay(xDisp, xDefaultScreen); /* Query framebuffer configurations */ GLXFBConfig* fbConfigs = nullptr; int numFBConfigs = 0; fbConfigs = glXGetFBConfigs(xDisp, xDefaultScreen, &numFBConfigs); if (!fbConfigs || numFBConfigs == 0) { Log.report(logvisor::Error, FMT_STRING("glXGetFBConfigs failed")); return 1; } VisualID selVisualId = 0; GLXFBConfig selFBConfig = nullptr; for (int i = 0; i < numFBConfigs; ++i) { GLXFBConfig config = fbConfigs[i]; int visualId, depthSize, colorSize, doubleBuffer; glXGetFBConfigAttrib(xDisp, config, GLX_VISUAL_ID, &visualId); glXGetFBConfigAttrib(xDisp, config, GLX_DEPTH_SIZE, &depthSize); glXGetFBConfigAttrib(xDisp, config, GLX_BUFFER_SIZE, &colorSize); glXGetFBConfigAttrib(xDisp, config, GLX_DOUBLEBUFFER, &doubleBuffer); /* Single-buffer only */ if (doubleBuffer) continue; if (colorSize >= 32 && depthSize >= 24 && visualId != 0) { selFBConfig = config; selVisualId = VisualID(visualId); break; } } XFree(fbConfigs); if (!selFBConfig) { Log.report(logvisor::Error, FMT_STRING("unable to find suitable pixel format")); return 1; } XVisualInfo visTemplate = {}; visTemplate.screen = xDefaultScreen; int numVisuals; XVisualInfo* visualList = XGetVisualInfo(xDisp, VisualScreenMask, &visTemplate, &numVisuals); Visual* selectedVisual = nullptr; for (int i = 0; i < numVisuals; ++i) { if (visualList[i].visualid == selVisualId) { selectedVisual = visualList[i].visual; break; } } XFree(visualList); /* Create colormap */ Colormap colormapId = XCreateColormap(xDisp, screen->root, selectedVisual, AllocNone); /* Create window */ XSetWindowAttributes swa; swa.colormap = colormapId; swa.border_pixmap = 0; swa.event_mask = 0; int instIdx = -1; if (argc > 3) instIdx = atoi(argv[3]); int x = 0; int y = 0; if (instIdx != -1) { x = (instIdx & 1) != 0; y = (instIdx & 2) != 0; } windowId = XCreateWindow(xDisp, screen->root, x, y, 768, 512, 10, CopyFromParent, CopyFromParent, selectedVisual, CWBorderPixel | CWEventMask | CWColormap, &swa); if (!glXCreateContextAttribsARB) { glXCreateContextAttribsARB = reinterpret_cast( glXGetProcAddressARB(reinterpret_cast("glXCreateContextAttribsARB"))); if (!glXCreateContextAttribsARB) { Log.report(logvisor::Error, FMT_STRING("unable to resolve glXCreateContextAttribsARB")); return 1; } } s_glxError = false; XErrorHandler oldHandler = XSetErrorHandler(ctxErrorHandler); GLXContext glxCtx = nullptr; for (uint32_t attribIdx = 0; attribIdx < std::extent::value; ++attribIdx) { glxCtx = glXCreateContextAttribsARB(xDisp, selFBConfig, nullptr, True, ContextAttribList[attribIdx]); if (glxCtx) break; } XSetErrorHandler(oldHandler); if (!glxCtx) { Log.report(logvisor::Fatal, FMT_STRING("unable to make new GLX context")); return 1; } GLXWindow glxWindow = glXCreateWindow(xDisp, selFBConfig, windowId, nullptr); if (!glxWindow) { Log.report(logvisor::Fatal, FMT_STRING("unable to make new GLX window")); return 1; } XMapWindow(xDisp, windowId); struct { unsigned long flags = 0; unsigned long functions = 0; unsigned long decorations = 0; long inputMode = 0; unsigned long status = 0; } wmHints; Atom motifWmHints = XInternAtom(xDisp, "_MOTIF_WM_HINTS", True); if (motifWmHints) { wmHints.flags = MWM_HINTS_DECORATIONS | MWM_HINTS_FUNCTIONS; wmHints.decorations |= MWM_DECOR_BORDER | MWM_DECOR_TITLE | MWM_DECOR_MINIMIZE | MWM_DECOR_MENU; wmHints.functions |= MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE; XChangeProperty(xDisp, windowId, motifWmHints, motifWmHints, 32, PropModeReplace, reinterpret_cast(&wmHints), 5); } /* SIGINT will be used to cancel main thread when client thread ends * (also enables graceful quitting via ctrl-c) */ pthread_t mainThread = pthread_self(); struct sigaction s; s.sa_handler = _sigint; sigemptyset(&s.sa_mask); s.sa_flags = 0; sigaction(SIGINT, &s, nullptr); sigaction(SIGUSR2, &s, nullptr); sigset_t waitmask, origmask; sigemptyset(&waitmask); sigaddset(&waitmask, SIGINT); sigaddset(&waitmask, SIGUSR2); pthread_sigmask(SIG_BLOCK, &waitmask, &origmask); int x11Fd = ConnectionNumber(xDisp); /* Spawn client thread */ std::mutex initmt; std::condition_variable initcv; std::unique_lock outerLk(initmt); std::thread clientThread([&]() { std::unique_lock innerLk(initmt); innerLk.unlock(); initcv.notify_one(); XLockDisplay(xDisp); if (!glXMakeContextCurrent(xDisp, glxWindow, glxWindow, glxCtx)) Log.report(logvisor::Fatal, FMT_STRING("unable to make GLX context current")); XUnlockDisplay(xDisp); UpdatePercent(0); renderer.Run(UpdatePercent); XLockDisplay(xDisp); XClientMessageEvent exitEvent = {}; exitEvent.type = ClientMessage; exitEvent.window = windowId; exitEvent.format = 32; XSendEvent(xDisp, windowId, 0, 0, reinterpret_cast(&exitEvent)); XFlush(xDisp); XUnlockDisplay(xDisp); pthread_kill(mainThread, SIGUSR2); }); initcv.wait(outerLk); /* Begin application event loop */ bool clientRunning = true; while (clientRunning) { fd_set fds; FD_ZERO(&fds); FD_SET(x11Fd, &fds); if (pselect(x11Fd + 1, &fds, nullptr, nullptr, nullptr, &origmask) < 0) { /* SIGINT/SIGUSR2 handled here */ if (errno == EINTR || errno == SIGUSR2) break; } if (FD_ISSET(x11Fd, &fds)) { XLockDisplay(xDisp); while (XPending(xDisp)) { XEvent event; XNextEvent(xDisp, &event); if (XFilterEvent(&event, None)) continue; if (event.type == ClientMessage) clientRunning = false; } XUnlockDisplay(xDisp); } } renderer.Terminate(); if (clientThread.joinable()) clientThread.join(); glXDestroyWindow(xDisp, glxWindow); XDestroyWindow(xDisp, windowId); XCloseDisplay(xDisp); return renderer.ReturnVal(); }