module main; import core.stdc.stdio; import core.stdc.stdlib; import core.stdc.locale; import core.stdc.signal; import core.stdc.string; import core.stdc.stdlib : malloc; import std.string : toStringz; import std.conv : to; import st; import x; import config; import patches; import arg; // Import X11 Display type import deimos.X11.Xlib : Display; // Xresources types alias XrmDatabase = void*; struct XrmValue { uint size; char* addr; } enum ResourceType { STRING = 0, INTEGER = 1, FLOAT = 2 } struct ResourcePref { const(char)* name; ResourceType type; void* dst; } enum VERSION = "0.9.2"; // Global variables __gshared { string opt_alpha; string opt_class = "St"; string opt_dir; string opt_font = ""; string opt_io; string opt_line; string opt_name = "st"; string opt_title; string opt_embed; string[] opt_cmd; int cols = 80; int rows = 24; string usedfont; } void usage() { string msg = "usage: " ~ argv0 ~ " [-aiv] [-c class]"; static if (isPatchEnabled!"WORKINGDIR_PATCH") { msg ~= " [-d path]"; } msg ~= " [-f font] [-g geometry] [-n name] [-o iofile] [-T title] [-t title] [-w windowid]"; static if (isPatchEnabled!"ALPHA_PATCH") { msg ~= " [-A alpha]"; } msg ~= " [[-e] command [args ...]]\n"; msg ~= " " ~ argv0 ~ " [-aiv] [-c class]"; static if (isPatchEnabled!"WORKINGDIR_PATCH") { msg ~= " [-d path]"; } msg ~= " [-f font] [-g geometry] [-n name] [-o iofile] [-T title] [-t title] [-w windowid]"; static if (isPatchEnabled!"ALPHA_PATCH") { msg ~= " [-A alpha]"; } msg ~= " -l line [stty_args ...]\n"; die(msg.toStringz); } void handleArg(char opt, string delegate() getArg, string delegate(lazy void) requireArg) { switch (opt) { case 'a': config.allowaltscreen = 0; break; static if (isPatchEnabled!"ALPHA_PATCH") { case 'A': opt_alpha = requireArg(usage()); break; } case 'c': opt_class = requireArg(usage()); break; static if (isPatchEnabled!"WORKINGDIR_PATCH") { case 'd': opt_dir = requireArg(usage()); break; } case 'f': opt_font = requireArg(usage()); break; case 'g': auto geom = requireArg(usage()); parseGeometry(geom); break; case 'i': xw.isfixed = 1; break; case 'o': opt_io = requireArg(usage()); break; case 'l': opt_line = requireArg(usage()); break; case 'n': opt_name = requireArg(usage()); break; case 't': case 'T': opt_title = requireArg(usage()); break; case 'w': opt_embed = requireArg(usage()); break; case 'v': die("%s %s\n", argv0.toStringz, VERSION.toStringz); break; default: usage(); } } void parseGeometry(string geom) { import std.regex; import std.conv; // Parse X11 geometry string: WIDTHxHEIGHT+X+Y auto re = regex(r"^(\d+)x(\d+)(?:\+(-?\d+)\+(-?\d+))?$"); auto m = geom.matchFirst(re); if (!m.empty) { if (m[1].length) cols = m[1].to!int; if (m[2].length) rows = m[2].to!int; if (m[3].length) xw.l = m[3].to!int; if (m[4].length) xw.t = m[4].to!int; xw.gm = 1; } } extern(C) void sigusr1_reload(int) { static if ((isPatchEnabled!"XRESOURCES_PATCH" && isPatchEnabled!"XRESOURCES_RELOAD_PATCH") || (isPatchEnabled!"BACKGROUND_IMAGE_PATCH" && isPatchEnabled!"BACKGROUND_IMAGE_RELOAD_PATCH")) { // Signal handler for reloading configuration static if (isPatchEnabled!"XRESOURCES_PATCH" && isPatchEnabled!"XRESOURCES_RELOAD_PATCH") { config_init(xw.dpy); } static if (isPatchEnabled!"BACKGROUND_IMAGE_PATCH" && isPatchEnabled!"BACKGROUND_IMAGE_RELOAD_PATCH") { import patch.background_image : reload_image; reload_image(); } redraw(); } } extern(C) int main(int argc, char** argv) { import std.logger : globalLogLevel, LogLevel, FileLogger, sharedLog, trace; import std.stdio : stderr; // Set global log level to trace for debugging globalLogLevel = LogLevel.trace; // Create a FileLogger that outputs to stderr with trace level auto stderrLogger = new FileLogger(stderr, LogLevel.trace); // Set it as the shared logger sharedLog = cast(shared)stderrLogger; // Log application startup trace("ST terminal starting up..."); // Initialize shell pointer config.shell_ptr = config._shell_array.ptr; // Parse command line arguments string[] args; args.length = argc; foreach (i; 0..argc) { args[i] = to!string(argv[i]); } // Handle -e option specially in handleArg bool hasEOption = false; parseArgs(args, (char opt, string delegate() getArg, string delegate(lazy void) requireArg) { if (opt == 'e') { hasEOption = true; // Skip the -e handling, parseArgs will leave remaining args in args array return; } handleArg(opt, getArg, requireArg); }); // After parseArgs, args contains only unparsed arguments if (hasEOption || args.length > 0) { opt_cmd = args; } // Set default title if not specified if (!opt_title) { opt_title = (opt_line || opt_cmd.length == 0) ? "st" : opt_cmd[0]; } // Set locale setlocale(LC_CTYPE, ""); // Set locale modifiers for X11 setXLocaleModifiers(); // Setup signal handlers static if ((isPatchEnabled!"XRESOURCES_PATCH" && isPatchEnabled!"XRESOURCES_RELOAD_PATCH") || (isPatchEnabled!"BACKGROUND_IMAGE_PATCH" && isPatchEnabled!"BACKGROUND_IMAGE_RELOAD_PATCH")) { signal(SIGUSR1, &sigusr1_reload); } // Open X display for xresources static if (isPatchEnabled!"XRESOURCES_PATCH") { import deimos.X11.Xlib : XOpenDisplay; xw.dpy = XOpenDisplay(null); if (!xw.dpy) { die("Can't open display\n"); } config_init(xw.dpy); } // Initialize harfbuzz if needed static if (isPatchEnabled!"LIGATURES_PATCH") { hbcreatebuffer(); } // Ensure minimum size cols = max(cols, 1); rows = max(rows, 1); // Set default background for alpha focus static if (isPatchEnabled!"ALPHA_PATCH" && isPatchEnabled!"ALPHA_FOCUS_HIGHLIGHT_PATCH") { import std.algorithm : max; config.defaultbg = max(colorname.length, 256); } // Initialize terminal tnew(cols, rows); xinit(cols, rows); // Initialize window position and fixed state xw.l = xw.t = 0; xw.isfixed = 0; // Set cursor style static if (isPatchEnabled!"BLINKING_CURSOR_PATCH") { xsetcursor(cursorstyle); } else { xsetcursor(cursorshape); } // Start terminal process if (ttynew(opt_line ? opt_line.toStringz : null, config.shell_ptr ? config.shell_ptr[0] : null, // shell opt_io ? opt_io.toStringz : null, opt_cmd.length > 0 ? toCStringArray(opt_cmd) : null) < 0) { die("Failed to start terminal process\n"); } // Force initial draw redraw(); // Initialize background image if enabled static if (isPatchEnabled!"BACKGROUND_IMAGE_PATCH") { bginit(); } // Set environment variables xsetenv(); // Initialize selection selinit(); // Change working directory if specified static if (isPatchEnabled!"WORKINGDIR_PATCH") { if (opt_dir) { import core.sys.posix.unistd : chdir; if (chdir(opt_dir.toStringz) != 0) { die("Can't change to working directory %s\n", opt_dir.toStringz); } } } // Run the main event loop run(); // Cleanup static if (isPatchEnabled!"LIGATURES_PATCH") { hbdestroybuffer(); } return 0; } // External X11 function extern(C) char* XSetLocaleModifiers(const(char)*); // Set X locale modifiers void setXLocaleModifiers() { XSetLocaleModifiers("".toStringz); } // External declarations needed extern(C) { void xsetcursor(int); void xinit(int, int); void xsetenv(); void run(); void bginit(); void hbcreatebuffer(); void hbdestroybuffer(); void redraw(); int ttynew(const(char)* line, char* cmd, const(char)* outfile, char** args); // Xresources functions void XrmInitialize(); char* XResourceManagerString(Display* dpy); XrmDatabase XrmGetStringDatabase(const(char)* data); int XrmGetResource(XrmDatabase db, const(char)* str_name, const(char)* str_class, char** str_type_return, XrmValue* value_return); // Additional C library functions uint strtoul(const(char)* nptr, char** endptr, int base); float strtof(const(char)* nptr, char** endptr); __gshared { extern int cursorstyle; extern const(char)*[] colorname; } } // Helper function to convert D string array to C string array char** toCStringArray(string[] arr) { if (arr.length == 0) return null; char** result = cast(char**)malloc((arr.length + 1) * (char*).sizeof); foreach (i, s; arr) { result[i] = cast(char*)s.toStringz; } result[arr.length] = null; return result; } // Xresources implementation static if (isPatchEnabled!"XRESOURCES_PATCH") { ResourcePref[] get_resources() { ResourcePref[] resources; resources ~= ResourcePref("font", ResourceType.STRING, cast(void*)&config.font); // Add color resources import config : colorname; for (int i = 0; i < 16; i++) { static char[16][16] color_names; snprintf(color_names[i].ptr, 16, "color%d", i); resources ~= ResourcePref(color_names[i].ptr, ResourceType.STRING, cast(void*)&colorname[i]); } resources ~= ResourcePref("background", ResourceType.STRING, cast(void*)&colorname[258]); resources ~= ResourcePref("foreground", ResourceType.STRING, cast(void*)&colorname[259]); resources ~= ResourcePref("cursorColor", ResourceType.STRING, cast(void*)&colorname[256]); resources ~= ResourcePref("termname", ResourceType.STRING, cast(void*)&config.termname); resources ~= ResourcePref("shell", ResourceType.STRING, cast(void*)&config.shell); resources ~= ResourcePref("minlatency", ResourceType.INTEGER, cast(void*)&config.minlatency); resources ~= ResourcePref("maxlatency", ResourceType.INTEGER, cast(void*)&config.maxlatency); resources ~= ResourcePref("blinktimeout", ResourceType.INTEGER, cast(void*)&config.blinktimeout); resources ~= ResourcePref("bellvolume", ResourceType.INTEGER, cast(void*)&config.bellvolume); resources ~= ResourcePref("tabspaces", ResourceType.INTEGER, cast(void*)&config.tabspaces); resources ~= ResourcePref("borderpx", ResourceType.INTEGER, cast(void*)&config.borderpx); return resources; } int resource_load(XrmDatabase db, const(char)* name, ResourceType rtype, void* dst) { import std.string : fromStringz; char[256] fullname; char[256] fullclass; char* type; XrmValue ret; const(char)* opt_name_str = (opt_name.length > 0 ? opt_name : "st").toStringz; const(char)* opt_class_str = (opt_class.length > 0 ? opt_class : "St").toStringz; snprintf(fullname.ptr, fullname.sizeof, "%s.%s", opt_name_str, name); snprintf(fullclass.ptr, fullclass.sizeof, "%s.%s", opt_class_str, name); fullname[$-1] = fullclass[$-1] = '\0'; if (!XrmGetResource(db, fullname.ptr, fullclass.ptr, &type, &ret)) return 1; if (ret.addr is null || strncmp("String", type, 64) != 0) return 1; switch (rtype) { case ResourceType.STRING: *cast(char**)dst = ret.addr; break; case ResourceType.INTEGER: *cast(int*)dst = cast(int)strtoul(ret.addr, null, 10); break; case ResourceType.FLOAT: *cast(float*)dst = strtof(ret.addr, null); break; default: return 1; } return 0; } extern(C) void config_init(Display* dpy) { char* resm; XrmDatabase db; XrmInitialize(); resm = XResourceManagerString(dpy); if (!resm) { return; } db = XrmGetStringDatabase(resm); auto resources = get_resources(); foreach (ref resource; resources) { resource_load(db, resource.name, resource.type, resource.dst); } } } else { extern(C) void config_init(Display* dpy) { // No-op when xresources is disabled } }