diff --git a/lib/Makefile.am b/lib/Makefile.am
index 9e0bbbb77..060e7175d 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -26,6 +26,7 @@ SUBLIB_includes = \
widget.h
SRC_mc_utils = \
+ terminal.c terminal.h \
utilunix.c \
unixcompat.h \
util.c util.h
diff --git a/lib/terminal.c b/lib/terminal.c
new file mode 100644
index 000000000..e3589a55f
--- /dev/null
+++ b/lib/terminal.c
@@ -0,0 +1,318 @@
+/*
+ Terminal emulation.
+
+ Copyright (C) 2025
+ Free Software Foundation, Inc.
+
+ This file is part of the Midnight Commander.
+ Authors:
+ Miguel de Icaza, 1994, 1995, 1996
+ Johannes Altmanninger, 2025
+
+ The Midnight Commander 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.
+
+ The Midnight Commander 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 .
+ */
+
+/** \file terminal.c
+ * \brief Source: terminal emulation.
+ * \author Johannes Altmanninger
+ * \date 2025
+ *
+ * Subshells running inside Midnight Commander may assume they run inside
+ * a terminal. This module helps us act like a real terminal in relevant
+ * aspects.
+ */
+
+#include
+
+#include "lib/util.h"
+#include "lib/strutil.h"
+
+#include "lib/terminal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Parse a CSI command, starting from the third byte (i.e. the first
+ * parameter byte, if any).
+ *
+ * On success, *sptr will point to one-past the end of the sequence.
+ * On failure, *sptr will point to the first invalid byte.
+ *
+ * Here's the format in a sort of pidgin BNF:
+ *
+ * CSI-command = Esc '[' parameters (intermediate-byte)* final-byte
+ * parameters = [0-9;:]+
+ * | [<=>?] (parameter-byte)* # private mode
+ * parameter-byte = [\x30-\x3F] # one of "0-9;:<=>?"
+ * intermediate-byte = [\x20–\x2F] # one of " !\"#$%&'()*+,-./"
+ * final-byte = [\x40-\x7e] # one of "@A–Z[\]^_`a–z{|}~"
+ */
+gboolean
+parse_csi (struct csi_command_t *out, const char **sptr, const char *end)
+{
+ gboolean ok = FALSE;
+
+ const char *s = *sptr;
+ if (s == end)
+ goto invalid_sequence;
+
+ char c = *s;
+
+#define NEXT_CHAR \
+ do \
+ { \
+ if (++s == end) \
+ goto invalid_sequence; \
+ c = *s; \
+ } \
+ while (0)
+
+ char private_mode = '\0';
+
+ if (c >= '<' && c <= '?') // "<=>?"
+ {
+ private_mode = c;
+ NEXT_CHAR;
+ }
+
+ // parameter bytes
+ size_t param_count = 0;
+
+ if (private_mode != '\0')
+ {
+ while (c >= 0x30 && c <= 0x3F)
+ NEXT_CHAR;
+ }
+ else
+ {
+ if (out != NULL)
+ // N.B. empty parameter strings are allowed. For our current use,
+ // treating them as zeroes happens to work.
+ memset (out->params, 0, sizeof (out->params));
+
+ uint32_t tmp = 0;
+ size_t sub_index = 0;
+
+ while (c >= 0x30 && c <= 0x3F)
+ {
+ if (c >= '0' && c <= '9')
+ {
+ if (param_count == 0)
+ param_count = 1;
+ if (tmp * 10 < tmp)
+ goto invalid_sequence; // overflow
+ tmp *= 10;
+ if (tmp + c - '0' < tmp)
+ goto invalid_sequence; // overflow
+ tmp += c - '0';
+ if (out != NULL)
+ out->params[param_count - 1][sub_index] = tmp;
+ }
+ else if (c == ':' && ++sub_index < G_N_ELEMENTS (out->params[0]))
+ tmp = 0;
+ else if (c == ';' && ++param_count <= G_N_ELEMENTS (out->params))
+ tmp = 0, sub_index = 0;
+ else
+ goto invalid_sequence;
+ NEXT_CHAR;
+ }
+ }
+
+ while (c >= 0x20 && c <= 0x2F) // intermediate bytes
+ NEXT_CHAR;
+#undef NEXT_CHAR
+
+ if (c < 0x40 || c > 0x7E) // final byte
+ goto invalid_sequence;
+
+ ++s;
+ ok = TRUE;
+
+ if (out != NULL)
+ {
+ out->private_mode = private_mode;
+ out->param_count = param_count;
+ }
+
+invalid_sequence:
+ *sptr = s;
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Remove all control sequences (CSI, OSC) from the argument string.
+ *
+ * The 256-color and true-color escape sequences should allow either ';' or ':' inside as
+ * separator, actually, ':' is the more correct according to ECMA-48. Some terminal emulators
+ * (e.g. xterm, gnome-terminal) support this.
+ *
+ * Non-printable characters are also removed.
+ */
+
+char *
+strip_ctrl_codes (char *s)
+{
+ char *w; // Current position where the stripped data is written
+ const char *r; // Current position where the original data is read
+
+ if (s == NULL)
+ return NULL;
+
+ const char *end = s + strlen (s);
+
+ for (w = s, r = s; *r != '\0';)
+ {
+ if (*r == ESC_CHAR)
+ {
+ // Skip the control sequence's arguments
+ // '(' need to avoid strange 'B' letter in *Suse (if mc runs under root user)
+ if (*(++r) == '[' || *r == '(')
+ {
+ ++r;
+ parse_csi (NULL, &r, end);
+ // We're already past the sequence, no need to increment.
+ continue;
+ }
+ if (*r == ']')
+ {
+ /*
+ * Skip xterm's OSC (Operating System Command)
+ * https://www.xfree86.org/current/ctlseqs.html
+ * OSC P s ; P t ST
+ * OSC P s ; P t BEL
+ */
+ const char *new_r;
+
+ for (new_r = r; *new_r != '\0'; new_r++)
+ {
+ switch (*new_r)
+ {
+ // BEL
+ case '\a':
+ r = new_r;
+ goto osc_out;
+ case ESC_CHAR:
+ // ST
+ if (new_r[1] == '\\')
+ {
+ r = new_r + 1;
+ goto osc_out;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ osc_out:;
+ }
+
+ /*
+ * Now we are at the last character of the sequence.
+ * Skip it unless it's binary 0.
+ */
+ if (*r != '\0')
+ r++;
+ }
+ else
+ {
+ const char *n;
+
+ n = str_cget_next_char (r);
+ if (str_isprint (r))
+ {
+ memmove (w, r, n - r);
+ w += n - r;
+ }
+ r = n;
+ }
+ }
+
+ *w = '\0';
+ return s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Convert "\E" -> esc character and ^x to control-x key and ^^ to ^ key
+ *
+ * @param p pointer to string
+ *
+ * @return newly allocated string
+ */
+
+char *
+convert_controls (const char *p)
+{
+ char *valcopy;
+ char *q;
+
+ valcopy = g_strdup (p);
+
+ // Parse the escape special character
+ for (q = valcopy; *p != '\0';)
+ switch (*p)
+ {
+ case '\\':
+ p++;
+
+ if (*p == 'e' || *p == 'E')
+ {
+ p++;
+ *q++ = ESC_CHAR;
+ }
+ break;
+
+ case '^':
+ p++;
+ if (*p == '^')
+ *q++ = *p++;
+ else
+ {
+ char c;
+
+ c = *p | 0x20;
+ if (c >= 'a' && c <= 'z')
+ {
+ *q++ = c - 'a' + 1;
+ p++;
+ }
+ else if (*p != '\0')
+ p++;
+ }
+ break;
+
+ default:
+ *q++ = *p++;
+ }
+
+ *q = '\0';
+ return valcopy;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/terminal.h b/lib/terminal.h
new file mode 100644
index 000000000..bd0f79cb7
--- /dev/null
+++ b/lib/terminal.h
@@ -0,0 +1,41 @@
+/** \file terminal.h
+ * \brief Header: terminal emulation logic.
+ */
+
+#ifndef MC_TERMINAL_H
+#define MC_TERMINAL_H
+
+#include
+#include // uint32_t
+
+#include "lib/global.h" // include
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct csi_command_t
+{
+ char private_mode;
+ uint32_t params[16][4];
+ size_t param_count;
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+gboolean parse_csi (struct csi_command_t *out, const char **sptr, const char *end);
+
+char *strip_ctrl_codes (char *s);
+
+/* Replaces "\\E" and "\\e" with "\033". Replaces "^" + [a-z] with
+ * ((char) 1 + (c - 'a')). The same goes for "^" + [A-Z].
+ * Returns a newly allocated string. */
+char *convert_controls (const char *s);
+
+/*** inline functions ****************************************************************************/
+
+#endif
diff --git a/lib/util.c b/lib/util.c
index 88fde2afe..76e4638bc 100644
--- a/lib/util.c
+++ b/lib/util.c
@@ -741,105 +741,6 @@ skip_numbers (const char *s)
return su;
}
-/* --------------------------------------------------------------------------------------------- */
-/**
- * Remove all control sequences from the argument string. We define
- * "control sequence", in a sort of pidgin BNF, as follows:
- *
- * control-seq = Esc non-'['
- * | Esc '[' (parameter-byte)* (intermediate-byte)* final-byte
- * parameter-byte = [\x30-\x3F] # one of "0-9;:<=>?"
- * intermediate-byte = [\x20–\x2F] # one of " !\"#$%&'()*+,-./"
- * final-byte = [\x40-\x7e] # one of "@A–Z[\]^_`a–z{|}~"
- *
- * The 256-color and true-color escape sequences should allow either ';' or ':' inside as separator,
- * actually, ':' is the more correct according to ECMA-48.
- * Some terminal emulators (e.g. xterm, gnome-terminal) support this.
- *
- * Non-printable characters are also removed.
- */
-
-char *
-strip_ctrl_codes (char *s)
-{
- char *w; // Current position where the stripped data is written
- char *r; // Current position where the original data is read
-
- if (s == NULL)
- return NULL;
-
- for (w = s, r = s; *r != '\0';)
- {
- if (*r == ESC_CHAR)
- {
- // Skip the control sequence's arguments
- // '(' need to avoid strange 'B' letter in *Suse (if mc runs under root user)
- if (*(++r) == '[' || *r == '(')
- {
- // strchr() matches trailing binary 0
- while (*(++r) != '\0' && strchr ("0123456789;:<=>?", *r) != NULL)
- ;
- while (*r != '\0' && (*r < 0x40 || *r > 0x7E))
- ++r;
- }
- else if (*r == ']')
- {
- /*
- * Skip xterm's OSC (Operating System Command)
- * https://www.xfree86.org/current/ctlseqs.html
- * OSC P s ; P t ST
- * OSC P s ; P t BEL
- */
- char *new_r;
-
- for (new_r = r; *new_r != '\0'; new_r++)
- {
- switch (*new_r)
- {
- // BEL
- case '\a':
- r = new_r;
- goto osc_out;
- case ESC_CHAR:
- // ST
- if (new_r[1] == '\\')
- {
- r = new_r + 1;
- goto osc_out;
- }
- break;
- default:
- break;
- }
- }
- osc_out:;
- }
-
- /*
- * Now we are at the last character of the sequence.
- * Skip it unless it's binary 0.
- */
- if (*r != '\0')
- r++;
- }
- else
- {
- char *n;
-
- n = str_get_next_char (r);
- if (str_isprint (r))
- {
- memmove (w, r, n - r);
- w += n - r;
- }
- r = n;
- }
- }
-
- *w = '\0';
- return s;
-}
-
/* --------------------------------------------------------------------------------------------- */
enum compression_type
@@ -977,64 +878,6 @@ wipe_password (char *passwd)
}
}
-/* --------------------------------------------------------------------------------------------- */
-/**
- * Convert "\E" -> esc character and ^x to control-x key and ^^ to ^ key
- *
- * @param p pointer to string
- *
- * @return newly allocated string
- */
-
-char *
-convert_controls (const char *p)
-{
- char *valcopy;
- char *q;
-
- valcopy = g_strdup (p);
-
- // Parse the escape special character
- for (q = valcopy; *p != '\0';)
- switch (*p)
- {
- case '\\':
- p++;
-
- if (*p == 'e' || *p == 'E')
- {
- p++;
- *q++ = ESC_CHAR;
- }
- break;
-
- case '^':
- p++;
- if (*p == '^')
- *q++ = *p++;
- else
- {
- char c;
-
- c = *p | 0x20;
- if (c >= 'a' && c <= 'z')
- {
- *q++ = c - 'a' + 1;
- p++;
- }
- else if (*p != '\0')
- p++;
- }
- break;
-
- default:
- *q++ = *p++;
- }
-
- *q = '\0';
- return valcopy;
-}
-
/* --------------------------------------------------------------------------------------------- */
/**
* Finds out a relative path from first to second, i.e. goes as many ..
diff --git a/lib/util.h b/lib/util.h
index 1df2070fb..87505f3f1 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -191,12 +191,6 @@ const char *extension (const char *);
const char *unix_error_string (int error_num);
const char *skip_separators (const char *s);
const char *skip_numbers (const char *s);
-char *strip_ctrl_codes (char *s);
-
-/* Replaces "\\E" and "\\e" with "\033". Replaces "^" + [a-z] with
- * ((char) 1 + (c - 'a')). The same goes for "^" + [A-Z].
- * Returns a newly allocated string. */
-char *convert_controls (const char *s);
/* overwrites passwd with '\0's and frees it. */
void wipe_password (char *passwd);
diff --git a/src/filemanager/layout.c b/src/filemanager/layout.c
index 21db1420a..40aeed79b 100644
--- a/src/filemanager/layout.c
+++ b/src/filemanager/layout.c
@@ -41,6 +41,7 @@
#include
#include "lib/global.h"
+#include "lib/terminal.h" // strip_ctrl_codes()
#include "lib/tty/tty.h"
#include "lib/skin.h"
#include "lib/tty/key.h"
diff --git a/src/learn.c b/src/learn.c
index cc06ddf85..d19e7bc46 100644
--- a/src/learn.c
+++ b/src/learn.c
@@ -38,7 +38,8 @@
#include "lib/tty/key.h"
#include "lib/mcconfig.h"
#include "lib/strutil.h"
-#include "lib/util.h" // convert_controls()
+#include "lib/terminal.h" // convert_controls()
+#include "lib/util.h" // MC_PTR_FREE
#include "lib/widget.h"
#include "setup.h"
diff --git a/src/setup.c b/src/setup.c
index 2c8ed4866..ec4b4c24a 100644
--- a/src/setup.c
+++ b/src/setup.c
@@ -38,6 +38,7 @@
#include "lib/tty/key.h"
#include "lib/mcconfig.h" // num_history_items_recorded
#include "lib/fileloc.h"
+#include "lib/terminal.h" // convert_controls()
#include "lib/timefmt.h"
#include "lib/util.h"
diff --git a/src/subshell/common.c b/src/subshell/common.c
index f4a4a9c7b..994e490bd 100644
--- a/src/subshell/common.c
+++ b/src/subshell/common.c
@@ -98,6 +98,7 @@
#include "lib/global.h"
#include "lib/fileloc.h"
+#include "lib/terminal.h"
#include "lib/unixcompat.h"
#include "lib/tty/tty.h" // LINES
#include "lib/tty/key.h" // XCTRL
@@ -117,7 +118,7 @@
/* State of the subshell:
* INACTIVE: the default state; awaiting a command
- * ACTIVE: remain in the shell until the user hits 'subshell_switch_key'
+ * ACTIVE: remain in the shell until the user hits the subshell switch key
* RUNNING_COMMAND: return to MC when the current command finishes */
enum subshell_state_enum subshell_state;
@@ -177,12 +178,6 @@ static char tcsh_fifo[BUF_SMALL];
static int subshell_pty_slave = -1;
-/* The key for switching back to MC from the subshell */
-static const char subshell_switch_key = XCTRL ('o') & 255;
-
-static const char subshell_switch_key_csi_u[] = "\x1b[111;5u";
-static const size_t subshell_switch_key_csi_u_len = sizeof (subshell_switch_key_csi_u) - 1;
-
/* For reading/writing on the subshell's pty */
static char pty_buffer[PTY_BUFFER_SIZE] = "\0";
@@ -466,7 +461,7 @@ init_subshell_child (const char *pty_name)
case SHELL_FISH:
execl (mc_global.shell->path, mc_global.shell->path, "--init-command",
- "set --global __mc_csi_u 1", (char *) NULL);
+ "set --global __mc_kitty_keyboard 1", (char *) NULL);
break;
case SHELL_ASH_BUSYBOX:
@@ -778,6 +773,57 @@ set_prompt_string (void)
setup_cmdline ();
}
+/* --------------------------------------------------------------------------------------------- */
+
+enum kitty_keyboard_protocol_t
+{
+ MODIFIER_SHIFT = 0x01,
+ MODIFIER_CTRL = 0x04,
+ MODIFIER_CAPS_LOCK = 0x40,
+ MODIFIER_NUM_LOCK = 0x80,
+
+ EVENT_TYPE_PRESS = 1,
+ EVENT_TYPE_REPEAT = 2
+};
+
+static gboolean
+peek_subshell_switch_key (const char *buffer, size_t len)
+{
+ if (len == 0)
+ return FALSE;
+ if (buffer[0] == (XCTRL ('o') & 255))
+ return TRUE;
+
+ // Also check if ctrl-o is encoded as per the kitty keyboard protocol.
+ if (len == 1)
+ return FALSE;
+ if (buffer[0] != ESC_CHAR || buffer[1] != '[') // CSI
+ return FALSE;
+ buffer += 2;
+ len -= 2;
+
+ struct csi_command_t csi;
+
+ if (!parse_csi (&csi, &buffer, buffer + len))
+ return FALSE;
+ if (csi.private_mode != '\0' || buffer[-1] != 'u')
+ return FALSE;
+ if (csi.param_count != 2) // ctrl-o must have the modifier field
+ return FALSE;
+ if (csi.params[1][0] == 0) // Bad modifier.
+ return FALSE;
+
+ const uint32_t codepoint = csi.params[0][0];
+ const uint32_t modifiers = csi.params[1][0] - 1;
+ const uint32_t event = csi.params[1][1];
+
+ if (event != 0 && event != EVENT_TYPE_PRESS && event != EVENT_TYPE_REPEAT)
+ return FALSE;
+
+ return codepoint == 'o'
+ && (modifiers & ~(MODIFIER_CAPS_LOCK | MODIFIER_NUM_LOCK)) == MODIFIER_CTRL;
+}
+
/* --------------------------------------------------------------------------------------------- */
/** Feed the subshell our keyboard input until it says it's finished */
@@ -907,11 +953,7 @@ feed_subshell (int how, gboolean fail_on_error)
}
for (i = 0; i < bytes; ++i)
- if (pty_buffer[i] == subshell_switch_key
- || (subshell_switch_key_csi_u_len <= (size_t) bytes - i
- && memcmp (&pty_buffer[i], subshell_switch_key_csi_u,
- subshell_switch_key_csi_u_len)
- == 0))
+ if (peek_subshell_switch_key (pty_buffer + i, bytes - i))
{
write_all (mc_global.tty.subshell_pty, pty_buffer, i);
diff --git a/tests/.gitignore b/tests/.gitignore
index ddf11e3a7..d3b3fece1 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -53,6 +53,7 @@ lib/strutil/str_verscmp
lib/strutil/str_verscmp.log
lib/strutil/str_verscmp.trs
lib/strutil/test-suite.log
+lib/terminal
lib/test-suite.log
lib/utilinux__my_system-fork_child.log
lib/utilinux__my_system-fork_child_shell.log
diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am
index 256b2d18d..3ed6d1676 100644
--- a/tests/lib/Makefile.am
+++ b/tests/lib/Makefile.am
@@ -19,6 +19,7 @@ TESTS = \
mc_build_filename \
name_quote \
serialize \
+ terminal \
tty \
utilunix__mc_pstream_get_string \
utilunix__my_system_fork_fail \
@@ -47,6 +48,9 @@ name_quote_SOURCES = \
serialize_SOURCES = \
serialize.c
+terminal_SOURCES = \
+ terminal.c
+
tty_SOURCES = \
tty.c
diff --git a/tests/lib/terminal.c b/tests/lib/terminal.c
new file mode 100644
index 000000000..4eed2821e
--- /dev/null
+++ b/tests/lib/terminal.c
@@ -0,0 +1,97 @@
+/*
+ lib/terminal - tests for terminal emulation functions
+
+ Copyright (C) 2013-2025
+ Free Software Foundation, Inc.
+
+ Written by:
+ Johannes Altmanninger, 2025
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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.
+
+ The Midnight Commander 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 .
+ */
+
+#define TEST_SUITE_NAME "/lib/terminal"
+
+#include "tests/mctest.h"
+
+#include
+
+#include "lib/global.h" // include
+#include "lib/terminal.h"
+#include "lib/strutil.h"
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+setup (void)
+{
+ str_init_strings (NULL);
+}
+
+static void
+teardown (void)
+{
+ str_uninit_strings ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+START_TEST (test_parse_csi)
+{
+ const char *s = &"\x1b[=5uRest"[2];
+ const char *end = s + strlen (s);
+ gboolean ok = parse_csi (NULL, &s, end);
+ ck_assert_msg (ok, "failed to parse CSI");
+ ck_assert_str_eq (s, "Rest");
+}
+END_TEST
+
+/* --------------------------------------------------------------------------------------------- */
+
+START_TEST (test_strip_ctrl_codes)
+{
+ char *s = strdup (
+ "\033]0;~\a\033[30m\033(B\033[m\033]133;A;special_key=1\a$ "
+ "\033[K\033[?2004h\033[>4;1m\033[=5u\033=\033[?2004l\033[>4;0m\033[=0u\033>\033[?2004h"
+ "\033[>4;1m\033[=5u\033=\033[?2004l\033[>4;0m\033[=0u\033>\033[?2004h\033[>4;1m\033[=5u"
+ "\033=");
+ char *actual = strip_ctrl_codes (s);
+ const char *expected = "$ ";
+ ck_assert_str_eq (actual, expected);
+ free (s);
+}
+END_TEST
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+main (void)
+{
+ TCase *tc_core;
+
+ tc_core = tcase_create ("Core");
+
+ tcase_add_checked_fixture (tc_core, setup, teardown);
+
+ // Add new tests here: ***************
+ tcase_add_test (tc_core, test_parse_csi);
+ tcase_add_test (tc_core, test_strip_ctrl_codes);
+ // ***********************************
+
+ return mctest_run_all (tc_core);
+}
+
+/* --------------------------------------------------------------------------------------------- */