/* * Copyright (C) 2012-2015 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_TIGETSTR #include /* * Some macros in term.h interfere with our code. */ #undef lines #endif #include #include #ifdef EMSCRIPTEN #include #endif #include "sciteco.h" #include "string-utils.h" #include "cmdline.h" #include "qregisters.h" #include "ring.h" #include "interface.h" #include "interface-curses.h" #ifdef HAVE_WINDOWS_H /* here it shouldn't cause conflicts with other headers */ #define WIN32_LEAN_AND_MEAN #include #endif /** * Whether we have PDCurses-only routines: * Could be 0, even on PDCurses */ #ifndef PDCURSES #define PDCURSES 0 #endif /** * Whether we're on PDCurses/win32 */ #if defined(__PDCURSES__) && defined(G_OS_WIN32) && \ !defined(PDCURSES_WIN32A) #define PDCURSES_WIN32 #endif #ifdef NCURSES_VERSION #ifdef G_OS_UNIX /** * Whether we're on ncurses/UNIX */ #define NCURSES_UNIX #elif defined(G_OS_WIN32) /** * Whether we're on ncurses/win32 console */ #define NCURSES_WIN32 #endif #endif namespace SciTECO { extern "C" { /* * PDCurses/win32a by default assigns functions to certain * keys like CTRL+V, CTRL++, CTRL+- and CTRL+=. * This conflicts with SciTECO that must remain in control * of keyboard processing. * Unfortunately, the default mapping can only be disabled * or changed via the internal PDC_set_function_key() in * pdcwin.h. Therefore we declare it manually here. */ #ifdef PDCURSES_WIN32A int PDC_set_function_key(const unsigned function, const int new_key); #endif static void scintilla_notify(Scintilla *sci, int idFrom, void *notify, void *user_data); #if defined(PDCURSES_WIN32) || defined(NCURSES_WIN32) /** * This handler is the Windows-analogue of a signal * handler. MinGW provides signal(), but it's not * reliable. * This may also be used to handle CTRL_CLOSE_EVENTs. * NOTE: Unlike signal handlers, this is executed in a * separate thread. */ static BOOL WINAPI console_ctrl_handler(DWORD type) { switch (type) { case CTRL_C_EVENT: sigint_occurred = TRUE; return TRUE; } return FALSE; } #endif } /* extern "C" */ #define UNNAMED_FILE "(Unnamed)" /** * Curses attribute for the color combination * `f` (foreground) and `b` (background) * according to the color pairs initialized by * Scinterm. * NOTE: This depends on the global variable * `COLORS` and is thus not a constant expression. */ #define SCI_COLOR_ATTR(f, b) \ ((attr_t)COLOR_PAIR(SCI_COLOR_PAIR(f, b))) void ViewCurses::initialize_impl(void) { sci = scintilla_new(scintilla_notify); setup(); } void InterfaceCurses::main_impl(int &argc, char **&argv) { /* * We must register this handler to handle * asynchronous interruptions via CTRL+C * reliably. The signal handler we already * have won't do. */ #if defined(PDCURSES_WIN32) || defined(NCURSES_WIN32) SetConsoleCtrlHandler(console_ctrl_handler, TRUE); #endif /* * Make sure we have a string for the info line * even if info_update() is never called. */ info_current = g_strdup(PACKAGE_NAME); } #ifdef NCURSES_UNIX void InterfaceCurses::init_screen(void) { screen_tty = g_fopen("/dev/tty", "r+"); /* should never fail */ g_assert(screen_tty != NULL); screen = newterm(NULL, screen_tty, screen_tty); if (!screen) { g_fprintf(stderr, "Error initializing interactive mode. " "$TERM may be incorrect.\n"); exit(EXIT_FAILURE); } /* * If stdout or stderr would go to the terminal, * redirect it. Otherwise, they are already redirected * (e.g. to a file) and writing to them does not * interrupt terminal interaction. */ if (isatty(1)) { FILE *stdout_new; stdout_orig = dup(1); g_assert(stdout_orig >= 0); stdout_new = g_freopen("/dev/null", "a+", stdout); g_assert(stdout_new != NULL); } if (isatty(2)) { FILE *stderr_new; stderr_orig = dup(2); g_assert(stderr_orig >= 0); stderr_new = g_freopen("/dev/null", "a+", stderr); g_assert(stderr_new != NULL); } } #elif defined(XCURSES) void InterfaceCurses::init_screen(void) { const char *argv[] = {PACKAGE_NAME, NULL}; /* * This sets the program name to "SciTECO" * which may then also be used as the X11 class name * for overwriting X11 resources in .Xdefaults * FIXME: We could support passing in resource * overrides via the SciTECO command line. * But unfortunately, Xinitscr() is called too * late to modify argc/argv for command-line parsing. * Therefore this could only be supported by * adding a special option like --resource. */ Xinitscr(1, (char **)argv); } #else void InterfaceCurses::init_screen(void) { initscr(); } #endif void InterfaceCurses::init_interactive(void) { /* * Curses accesses many environment variables * internally. In order to be able to modify them in * the SciTECO profile, we must update the process * environment before initscr()/newterm(). * This is safe to do here since there are no threads. */ QRegisters::globals.update_environ(); /* * $TERM must be unset or "#win32con" for the win32 * driver to load. * So we always ignore any $TERM changes by the user. */ #ifdef NCURSES_WIN32 g_setenv("TERM", "#win32con", TRUE); #endif #ifdef PDCURSES_WIN32A /* * Necessary to enable window resizing in Win32a port */ PDC_set_resize_limits(25, 0xFFFF, 80, 0xFFFF); /* * Disable all magic function keys. * NOTE: This could also be used to assign * a "shutdown" key when program termination is requested. */ for (int i = 0; i < 5; i++) PDC_set_function_key(i, 0); #endif /* for displaying UTF-8 characters properly */ setlocale(LC_CTYPE, ""); init_screen(); cbreak(); noecho(); /* Scintilla draws its own cursor */ curs_set(0); info_window = newwin(1, 0, 0, 0); msg_window = newwin(1, 0, LINES - 2, 0); cmdline_window = newwin(0, 0, LINES - 1, 0); keypad(cmdline_window, TRUE); #ifdef EMSCRIPTEN nodelay(cmdline_window, TRUE); #endif /* * Will also initialize Scinterm, Curses color pairs * and resizes the current view. */ if (current_view) show_view(current_view); } void InterfaceCurses::restore_batch(void) { /* * Set window title to a reasonable default, * in case it is not reset immediately by the * shell. * FIXME: See set_window_title() why this * is necessary. */ #if defined(NCURSES_UNIX) && defined(HAVE_TIGETSTR) set_window_title(g_getenv("TERM") ? : ""); #endif /* * Restore ordinary terminal behaviour * (i.e. return to batch mode) */ endwin(); /* * Restore stdout and stderr, so output goes to * the terminal again in case we "muted" them. */ #ifdef NCURSES_UNIX if (stdout_orig >= 0) { int fd = dup2(stdout_orig, 1); g_assert(fd == 1); } if (stderr_orig >= 0) { int fd = dup2(stderr_orig, 2); g_assert(fd == 2); } #endif /* * See vmsg_impl(): It looks at msg_win to determine * whether we're in batch mode. */ if (msg_window) { delwin(msg_window); msg_window = NULL; } } void InterfaceCurses::resize_all_windows(void) { int lines, cols; /* screen dimensions */ getmaxyx(stdscr, lines, cols); wresize(info_window, 1, cols); wresize(current_view->get_window(), lines - 3, cols); wresize(msg_window, 1, cols)HTTP/1.1 200 OK Connection: keep-alive Connection: keep-alive Content-Disposition: inline; filename="interface-curses.cpp" Content-Disposition: inline; filename="interface-curses.cpp" Content-Length: 24212 Content-Length: 24212 Content-Security-Policy: default-src 'none' Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Type: text/plain; charset=UTF-8 Date: Sat, 18 Oct 2025 09:13:13 UTC ETag: "e46af5110a163ac99688fc1dd1c2d2c4054846bd" ETag: "e46af5110a163ac99688fc1dd1c2d2c4054846bd" Expires: Tue, 16 Oct 2035 09:13:13 GMT Expires: Tue, 16 Oct 2035 09:13:14 GMT Last-Modified: Sat, 18 Oct 2025 09:13:13 GMT Last-Modified: Sat, 18 Oct 2025 09:13:14 GMT Server: OpenBSD httpd Server: OpenBSD httpd X-Content-Type-Options: nosniff X-Content-Type-Options: nosniff /* * Copyright (C) 2012-2015 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_TIGETSTR #include /* * Some macros in term.h interfere with our code. */ #undef lines #endif #include #include #ifdef EMSCRIPTEN #include #endif #include "sciteco.h" #include "string-utils.h" #include "cmdline.h" #include "qregisters.h" #include "ring.h" #include "interface.h" #include "interface-curses.h" #ifdef HAVE_WINDOWS_H /* here it shouldn't cause conflicts with other headers */ #define WIN32_LEAN_AND_MEAN #include #endif /** * Whether we have PDCurses-only routines: * Could be 0, even on PDCurses */ #ifndef PDCURSES #define PDCURSES 0 #endif /** * Whether we're on PDCurses/win32 */ #if defined(__PDCURSES__) && defined(G_OS_WIN32) && \ !defined(PDCURSES_WIN32A) #define PDCURSES_WIN32 #endif #ifdef NCURSES_VERSION #ifdef G_OS_UNIX /** * Whether we're on ncurses/UNIX */ #define NCURSES_UNIX #elif defined(G_OS_WIN32) /** * Whether we're on ncurses/win32 console */ #define NCURSES_WIN32 #endif #endif namespace SciTECO { extern "C" { /* * PDCurses/win32a by default assigns functions to certain * keys like CTRL+V, CTRL++, CTRL+- and CTRL+=. * This conflicts with SciTECO that must remain in control * of keyboard processing. * Unfortunately, the default mapping can only be disabled * or changed via the internal PDC_set_function_key() in * pdcwin.h. Therefore we declare it manually here. */ #ifdef PDCURSES_WIN32A int PDC_set_function_key(const unsigned function, const int new_key); #endif static void scintilla_notify(Scintilla *sci, int idFrom, void *notify, void *user_data); #if defined(PDCURSES_WIN32) || defined(NCURSES_WIN32) /** * This handler is the Windows-analogue of a signal * handler. MinGW provides signal(), but it's not * reliable. * This may also be used to handle CTRL_CLOSE_EVENTs. * NOTE: Unlike signal handlers, this is executed in a * separate thread. */ static BOOL WINAPI console_ctrl_handler(DWORD type) { switch (type) { case CTRL_C_EVENT: sigint_occurred = TRUE; return TRUE; } return FALSE; } #endif } /* extern "C" */ #define UNNAMED_FILE "(Unnamed)" /** * Curses attribute for the color combination * `f` (foreground) and `b` (background) * according to the color pairs initialized by * Scinterm. * NOTE: This depends on the global variable * `COLORS` and is thus not a constant expression. */ #define SCI_COLOR_ATTR(f, b) \ ((attr_t)COLOR_PAIR(SCI_COLOR_PAIR(f, b))) void ViewCurses::initialize_impl(void) { sci = scintilla_new(scintilla_notify); setup(); } void InterfaceCurses::main_impl(int &argc, char **&argv) { /* * We must register this handler to handle * asynchronous interruptions via CTRL+C * reliably. The signal handler we already * have won't do. */ #if defined(PDCURSES_WIN32) || defined(NCURSES_WIN32) SetConsoleCtrlHandler(console_ctrl_handler, TRUE); #endif /* * Make sure we have a string for the info line * even if info_update() is never called. */ info_current = g_strdup(PACKAGE_NAME); } #ifdef NCURSES_UNIX void InterfaceCurses::init_screen(void) { screen_tty = g_fopen("/dev/tty", "r+"); /* should never fail */ g_assert(screen_tty != NULL); screen = newterm(NULL, screen_tty, screen_tty); if (!screen) { g_fprintf(stderr, "Error initializing interactive mode. " "$TERM may be incorrect.\n"); exit(EXIT_FAILURE); } /* * If stdout or stderr would go to the terminal, * redirect it. Otherwise, they are already redirected * (e.g. to a file) and writing to them does not * interrupt terminal interaction. */ if (isatty(1)) { FILE *stdout_new; stdout_orig = dup(1); g_assert(stdout_orig >= 0); stdout_new = g_freopen("/dev/null", "a+", stdout); g_assert(stdout_new != NULL); } if (isatty(2)) { FILE *stderr_new; stderr_orig = dup(2); g_assert(stderr_orig >= 0); stderr_new = g_freopen("/dev/null", "a+", stderr); g_assert(stderr_new != NULL); } } #elif defined(XCURSES) void InterfaceCurses::init_screen(void) { const char *argv[] = {PACKAGE_NAME, NULL}; /* * This sets the program name to "SciTECO" * which may then also be used as the X11 class name * for overwriting X11 resources in .Xdefaults * FIXME: We could support passing in resource * overrides via the SciTECO command line. * But unfortunately, Xinitscr() is called too * late to modify argc/argv for command-line parsing. * Therefore this could only be supported by * adding a special option like --resource. */ Xinitscr(1, (char **)argv); } #else void InterfaceCurses::init_screen(void) { initscr(); } #endif void InterfaceCurses::init_interactive(void) { /* * Curses accesses many environment variables * internally. In order to be able to modify them in * the SciTECO profile, we must update the process * environment before initscr()/newterm(). * This is safe to do here since there are no threads. */ QRegisters::globals.update_environ(); /* * $TERM must be unset or "#win32con" for the win32 * driver to load. * So we always ignore any $TERM changes by the user. */ #ifdef NCURSES_WIN32 g_setenv("TERM", "#win32con", TRUE); #endif #ifdef PDCURSES_WIN32A /* * Necessary to enable window resizing in Win32a port */ PDC_set_resize_limits(25, 0xFFFF, 80, 0xFFFF); /* * Disable all magic function keys. * NOTE: This could also be used to assign * a "shutdown" key when program termination is requested. */ for (int i = 0; i < 5; i++) PDC_set_function_key(i, 0); #endif /* for displaying UTF-8 characters properly */ setlocale(LC_CTYPE, ""); init_screen(); cbreak(); noecho(); /* Scintilla draws its own cursor */ curs_set(0); info_window = newwin(1, 0, 0, 0); msg_window = newwin(1, 0, LINES - 2, 0); cmdline_window = newwin(0, 0, LINES - 1, 0); keypad(cmdline_window, TRUE); #ifdef EMSCRIPTEN nodelay(cmdline_window, TRUE); #endif /* * Will also initialize Scinterm, Curses color pairs * and resizes the current view. */ if (current_view) show_view(current_view); } void InterfaceCurses::restore_batch(void) { /* * Set window title to a reasonable default, * in case it is not reset immediately by the * shell. * FIXME: See set_window_title() why this * is necessary. */ #if defined(NCURSES_UNIX) && defined(HAVE_TIGETSTR) set_window_title(g_getenv("TERM") ? : ""); #endif /* * Restore ordinary terminal behaviour * (i.e. return to batch mode) */ endwin(); /* * Restore stdout and stderr, so output goes to * the terminal again in case we "muted" them. */ #ifdef NCURSES_UNIX if (stdout_orig >= 0) { int fd = dup2(stdout_orig, 1); g_assert(fd == 1); } if (stderr_orig >= 0) { int fd = dup2(stderr_orig, 2); g_assert(fd == 2); } #endif /* * See vmsg_impl(): It looks at msg_win to determine * whether we're in batch mode. */ if (msg_window) { delwin(msg_window); msg_window = NULL; } } void InterfaceCurses::resize_all_windows(void) { int lines, cols; /* screen dimensions */ getmaxyx(stdscr, lines, cols); wresize(info_window, 1, cols); wresize(current_view->get_window(), lines - 3, cols); wresize(msg_window, 1, cols); mvwin(msg_window, lines - 2, 0); wresize(cmdline_window, 1, cols); mvwin(cmdline_window, lines - 1, 0); draw_info(); msg_clear(); /* FIXME: use saved message */ popup_clear(); draw_cmdline(); } void InterfaceCurses::vmsg_impl(MessageType type, const gchar *fmt, va_list ap) { attr_t attr; /* * On most platforms we can write to stdout/stderr * even in interactive mode. */ #if defined(XCURSES) || defined(PDCURSES_WIN32A) || \ defined(NCURSES_UNIX) || defined(NCURSES_WIN32) stdio_vmsg(type, fmt, ap); if (!msg_window) /* batch mode */ return; #else if (!msg_window) { /* batch mode */ stdio_vmsg(type, fmt, ap); return; } #endif switch (type) { default: case MSG_USER: attr = SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE); break; case MSG_INFO: attr = SCI_COLOR_ATTR(COLOR_BLACK, COLOR_GREEN); break; case MSG_WARNING: attr = SCI_COLOR_ATTR(COLOR_BLACK, COLOR_YELLOW); break; case MSG_ERROR: attr = SCI_COLOR_ATTR(COLOR_BLACK, COLOR_RED); beep(); break; } wmove(msg_window, 0, 0); wbkgdset(msg_window, ' ' | attr); vw_printw(msg_window, fmt, ap); wclrtoeol(msg_window); } void InterfaceCurses::msg_clear(void) { if (!msg_window) /* batch mode */ return; wmove(msg_window, 0, 0); wbkgdset(msg_window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE)); wclrtoeol(msg_window); } void InterfaceCurses::show_view_impl(ViewCurses *view) { int lines, cols; /* screen dimensions */ WINDOW *current_view_win; current_view = view; if (!cmdline_window) /* batch mode */ return; current_view_win = current_view->get_window(); /* * screen size might have changed since * this view's WINDOW was last active */ getmaxyx(stdscr, lines, cols); wresize(current_view_win, lines - 3, cols); /* Set up window position: never changes */ mvwin(current_view_win, 1, 0); } #if PDCURSES void InterfaceCurses::set_window_title(const gchar *title) { static gchar *last_title = NULL; /* * PDC_set_title() can result in flickering * even when executed only once per pressed key, * so we check whether it is really necessary to change * the title. * This is an issue at least with PDCurses/win32. */ if (!g_strcmp0(title, last_title)) return; PDC_set_title(title); g_free(last_title); last_title = g_strdup(title); } #elif defined(NCURSES_UNIX) && defined(HAVE_TIGETSTR) void InterfaceCurses::set_window_title(const gchar *title) { if (!has_status_line || !to_status_line || !from_status_line) return; /* * Modern terminal emulators map the window title to * the historic status line. * This feature is not standardized in ncurses, * so we query the terminfo database. * This feature may make problems with terminal emulators * that do support a status line but do not map them * to the window title. Some emulators (like xterm) * support setting the window title via custom escape * sequences and via the status line but their * terminfo entry does not say so. (xterm can also * save and restore window titles but there is not * even a terminfo capability defined for this.) * Taken the different emulator incompatibilites * it may be best to make this configurable. * Once we support configurable status lines, * there could be a special status line that's sent * to the terminal that may be set up in the profile * depending on $TERM. * * NOTE: The terminfo manpage advises us to use putp() * but on ncurses/UNIX (where terminfo is available), * we do not let curses write to stdout. * NOTE: This leaves the title set after we quit. */ fputs(to_status_line, screen_tty); fputs(title, screen_tty); fputs(from_status_line, screen_tty); fflush(screen_tty); } #else void InterfaceCurses::set_window_title(const gchar *title) { /* no way to set window title */ } #endif void InterfaceCurses::draw_info(void) { if (!info_window) /* batch mode */ return; wmove(info_window, 0, 0); wbkgdset(info_window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE)); waddstr(info_window, info_current); wclrtoeol(info_window); set_window_title(info_current); } void InterfaceCurses::info_update_impl(const QRegister *reg) { /* * We cannot rely on Curses' control character drawing * and we need the info_current string for other purposes * (like PDC_set_title()), so we "canonicalize" the * register name here: */ gchar *name = String::canonicalize_ctl(reg->name); g_free(info_current); info_current = g_strconcat(PACKAGE_NAME " - ", name, NIL); g_free(name); /* NOTE: drawn in event_loop_iter() */ } void InterfaceCurses::info_update_impl(const Buffer *buffer) { g_free(info_current); info_current = g_strconcat(PACKAGE_NAME " - ", buffer->filename ? : UNNAMED_FILE, buffer->dirty ? "*" : "", NIL); /* NOTE: drawn in event_loop_iter() */ } void InterfaceCurses::format_chr(chtype *&target, gchar chr, attr_t attr) { /* * NOTE: This mapping is similar to * View::set_representations() */ switch (chr) { case CTL_KEY_ESC: *target++ = '$' | attr | A_REVERSE; break; case '\r': *target++ = 'C' | attr | A_REVERSE; *target++ = 'R' | attr | A_REVERSE; break; case '\n': *target++ = 'L' | attr | A_REVERSE; *target++ = 'F' | attr | A_REVERSE; break; case '\t': *target++ = 'T' | attr | A_REVERSE; *target++ = 'A' | attr | A_REVERSE; *target++ = 'B' | attr | A_REVERSE; break; default: if (IS_CTL(chr)) { *target++ = '^' | attr | A_REVERSE; *target++ = CTL_ECHO(chr) | attr | A_REVERSE; } else { *target++ = chr | attr; } } } void InterfaceCurses::cmdline_update_impl(const Cmdline *cmdline) { gsize alloc_len = 1; chtype *p; /* * AFAIK bold black should be rendered grey by any * common terminal. * If not, this problem will be gone once we support * a Scintilla view command line. * Also A_UNDERLINE is not supported by PDCurses/win32 * and causes weird colors, so we better leave it away. */ const attr_t rubout_attr = #ifndef PDCURSES_WIN32 A_UNDERLINE | #endif A_BOLD | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_BLACK); /* * Replace entire pre-formatted command-line. * We don't know if it is similar to the last one, * so realloc makes no sense. * We approximate the size of the new formatted command-line, * wasting a few bytes for control characters. */ delete[] cmdline_current; for (guint i = 0; i < cmdline->len+cmdline->rubout_len; i++) alloc_len += IS_CTL((*cmdline)[i]) ? 3 : 1; p = cmdline_current = new chtype[alloc_len]; /* format effective command line */ for (guint i = 0; i < cmdline->len; i++) format_chr(p, (*cmdline)[i]); cmdline_len = p - cmdline_current; /* Format rubbed-out command line. */ for (guint i = cmdline->len; i < cmdline->len+cmdline->rubout_len; i++) format_chr(p, (*cmdline)[i], rubout_attr); cmdline_rubout_len = p - cmdline_current - cmdline_len; /* highlight cursor after effective command lin