Add support for MSYS2
All checks were successful
Alpine 3.22 Success
Arch Linux AUR Success
OpenBSD 7.8 Success

This commit is contained in:
2026-01-03 03:47:38 +01:00
parent 16ef3f9e47
commit 10b6c04c97
5 changed files with 75 additions and 51 deletions

View File

@@ -24,7 +24,10 @@ target_link_libraries (${PROJECT_NAME}
PUBLIC ${NCURSESW_LIBRARIES} ${ACL_LIBRARIES})
target_compile_features (${PROJECT_NAME} PUBLIC cxx_std_14)
target_compile_definitions (${PROJECT_NAME} PUBLIC
-DPROJECT_NAME=\"${PROJECT_NAME}\" -DPROJECT_VERSION=\"${PROJECT_VERSION}\")
PROJECT_NAME=\"${PROJECT_NAME}\" PROJECT_VERSION=\"${PROJECT_VERSION}\")
if (MSYS)
target_compile_definitions (${PROJECT_NAME} PUBLIC _GNU_SOURCE)
endif ()
add_executable (${PROJECT_NAME}-mc-ext ${PROJECT_NAME}-mc-ext.cpp)
target_compile_features (${PROJECT_NAME}-mc-ext PUBLIC cxx_std_17)

View File

@@ -1,4 +1,4 @@
Copyright (c) 2017 - 2025, Přemysl Eric Janouch <p@janouch.name>
Copyright (c) 2017 - 2026, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

2
NEWS
View File

@@ -16,6 +16,8 @@ Unreleased
* Added an sdn-open script which does the same kind of processing as above
on top of xdg-open. This is what is now executed by M-Enter.
* Added support for Cygwin, in particular the MSYS2 flavour.
1.0.0 (2024-12-21)

View File

@@ -12,8 +12,9 @@ commands. It enables you to:
can be simply forwarded if it is to be edited. What's more, it will always
be obvious whether the navigator is running.
'sdn' runs on Linux and all BSD derivatives. I wanted to try a different,
simpler approach here, and the end result is very friendly to tinkering.
'sdn' runs on Linux, all BSD derivatives, and within MSYS2. I wanted to try
a different, simpler approach with this project, and the end result is
very friendly to tinkering.
image::sdn.png[align="center"]

112
sdn.cpp
View File

@@ -1,7 +1,7 @@
//
// sdn: simple directory navigator
//
// Copyright (c) 2017 - 2025, Přemysl Eric Janouch <p@janouch.name>
// Copyright (c) 2017 - 2026, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
@@ -53,7 +53,7 @@
#include <acl/libacl.h>
#include <sys/acl.h>
#include <sys/xattr.h>
#else
#elif !defined __CYGWIN__
#include <sys/event.h>
#endif
#include <ncurses.h>
@@ -425,6 +425,15 @@ fun decode_attrs (const vector<string> &attrs) -> chtype {
// --- Application -------------------------------------------------------------
using Key =
#if WINT_MAX >= INT32_MAX
wint_t;
#elif WINT_MIN < 0
int32_t;
#else
uint32_t;
#endif
enum { ALT = 1 << 24, SYM = 1 << 25 }; // Outside the range of Unicode
#define KEY(name) (SYM | KEY_ ## name)
#define CTRL(char) ((char) == '?' ? 0x7f : (char) & 0x1f)
@@ -451,7 +460,7 @@ enum action { ACTIONS(XX) ACTION_COUNT };
static const char *g_action_names[] = {ACTIONS(XX)};
#undef XX
static map<wint_t, action> g_normal_actions {
static map<Key, action> g_normal_actions {
{'\r', ACTION_ENTER}, {KEY (ENTER), ACTION_ENTER},
{ALT | '\r', ACTION_OPEN}, {ALT | KEY (ENTER), ACTION_OPEN},
{'t', ACTION_CHOOSE}, {'T', ACTION_CHOOSE_FULL},
@@ -481,7 +490,7 @@ static map<wint_t, action> g_normal_actions {
{'R', ACTION_REVERSE_SORT}, {ALT | '.', ACTION_SHOW_HIDDEN},
{CTRL ('L'), ACTION_REDRAW}, {'r', ACTION_RELOAD},
};
static map<wint_t, action> g_input_actions {
static map<Key, action> g_input_actions {
{27, ACTION_INPUT_ABORT}, {CTRL ('G'), ACTION_INPUT_ABORT},
{L'\r', ACTION_INPUT_CONFIRM}, {KEY (ENTER), ACTION_INPUT_CONFIRM},
// Sometimes terminfo is wrong, we need to accept both of these
@@ -496,12 +505,12 @@ static map<wint_t, action> g_input_actions {
{CTRL ('A'), ACTION_INPUT_BEGINNING}, {KEY (HOME), ACTION_INPUT_BEGINNING},
{CTRL ('E'), ACTION_INPUT_END}, {KEY (END), ACTION_INPUT_END},
};
static map<wint_t, action> g_search_actions {
static map<Key, action> g_search_actions {
{CTRL ('P'), ACTION_UP}, {KEY (UP), ACTION_UP},
{CTRL ('N'), ACTION_DOWN}, {KEY (DOWN), ACTION_DOWN},
{'/', ACTION_ENTER},
};
static const map<string, map<wint_t, action>*> g_binding_contexts {
static const map<string, map<Key, action>*> g_binding_contexts {
{"normal", &g_normal_actions}, {"input", &g_input_actions},
{"search", &g_search_actions},
};
@@ -589,9 +598,9 @@ static struct {
map<string, chtype> ls_exts; ///< LS_COLORS file extensions
bool ls_symlink_as_target; ///< ln=target in dircolors
map<string, wint_t, stringcaseless> name_to_key;
map<wint_t, string> key_to_name;
map<string, wint_t> custom_keys;
map<string, Key, stringcaseless> name_to_key;
map<Key, string> key_to_name;
map<string, Key> custom_keys;
string action_names[ACTION_COUNT]; ///< Stylized action names
// Refreshed by reload():
@@ -992,7 +1001,7 @@ readfail:
// We don't show atime, so access and open are merely spam
g.watch_wd = inotify_add_watch (g.watch_fd, ".",
(IN_ALL_EVENTS | IN_ONLYDIR | IN_EXCL_UNLINK) & ~(IN_ACCESS | IN_OPEN));
#else
#elif !defined __CYGWIN__
if (g.watch_wd != -1)
close (g.watch_wd);
@@ -1107,17 +1116,17 @@ fun run_pager (FILE *contents) {
update ();
}
fun encode_key (wint_t key) -> string {
fun encode_key (Key k) -> string {
string encoded;
if (key & ALT)
if (k & ALT)
encoded.append ("M-");
wchar_t bare = key & ~ALT;
Key bare = k & ~ALT;
if (g.key_to_name.count (bare))
encoded.append (capitalize (g.key_to_name.at (bare)));
else if (bare < 32 || bare == 0x7f)
encoded.append ("C-").append ({char (tolower ((bare + 64) & 0x7f))});
else
encoded.append (to_mb ({bare}));
encoded.append (to_mb ({wchar_t (bare)}));
return encoded;
}
@@ -1405,19 +1414,19 @@ fun move_towards_spacing (int diff) -> bool {
wcwidth (g.editor_line.at (g.editor_cursor));
}
fun handle_editor (wint_t c) {
fun handle_editor (Key k) {
auto action = ACTION_NONE;
if (g.editor_inserting) {
(void) halfdelay (1);
g.editor_inserting = false;
} else {
auto i = g_input_actions.find (c);
auto i = g_input_actions.find (k);
if (i != g_input_actions.end ())
action = i->second;
auto m = g_binding_contexts.find (to_mb (g.editor));
if (m != g_binding_contexts.end () &&
(i = m->second->find (c)) != m->second->end ())
(i = m->second->find (k)) != m->second->end ())
action = i->second;
}
@@ -1492,11 +1501,11 @@ fun handle_editor (wint_t c) {
default:
if (auto handler = g.editor_on[action]) {
handler ();
} else if (c & (ALT | SYM)) {
if (c != KEY (RESIZE))
} else if (k & (ALT | SYM)) {
if (k != KEY (RESIZE))
beep ();
} else {
g.editor_line.insert (g.editor_cursor, 1, c);
g.editor_line.insert (g.editor_cursor, 1, k);
g.editor_cursor++;
}
}
@@ -1504,14 +1513,14 @@ fun handle_editor (wint_t c) {
g.editor_on_change ();
}
fun handle (wint_t c) -> bool {
if (c == WEOF)
fun handle (Key k) -> bool {
if (k == WEOF)
return false;
// If an editor is active, let it handle the key instead and eat it
if (g.editor) {
handle_editor (c);
c = WEOF;
handle_editor (k);
k = WEOF;
}
const auto &current = at_cursor ();
@@ -1519,7 +1528,7 @@ fun handle (wint_t c) -> bool {
S_ISDIR (current.info.st_mode) ||
S_ISDIR (current.target_info.st_mode);
auto i = g_normal_actions.find (c);
auto i = g_normal_actions.find (k);
switch (i == g_normal_actions.end () ? ACTION_NONE : i->second) {
case ACTION_CHOOSE_FULL:
choose (current, true);
@@ -1701,7 +1710,7 @@ fun handle (wint_t c) -> bool {
reload (true);
break;
default:
if (c != KEY (RESIZE) && c != WEOF)
if (k != KEY (RESIZE) && k != WEOF)
beep ();
}
fix_cursor_and_offset ();
@@ -1723,7 +1732,7 @@ fun watch_check () {
changed = true;
}
}
#else
#elif !defined __CYGWIN__
struct kevent ev {};
struct timespec timeout {};
if (kevent (g.watch_fd, nullptr, 0, &ev, 1, &timeout) > 0)
@@ -1836,7 +1845,7 @@ fun monotonic_ts_ms () -> int64_t {
return ts.tv_sec * 1e3 + ts.tv_nsec / 1e6;
}
fun read_key (wint_t &c) -> bool {
fun read_key (Key &k) -> bool {
// XXX: on at least some systems, when run over ssh in a bind handler,
// after closing the terminal emulator we receive no fatal signal but our
// parent shell gets reparented under init and our stdin gets closed,
@@ -1845,37 +1854,40 @@ fun read_key (wint_t &c) -> bool {
// situation appears to be via timing. Checking errno doesn't work and
// resetting signal dispositions or the signal mask has no effect.
auto start = monotonic_ts_ms ();
wint_t c{};
int res = get_wch (&c);
if (res == ERR) {
c = WEOF;
if (monotonic_ts_ms () - start >= 50)
return false;
}
k = c;
wint_t metafied{};
if (c == 27 && (res = get_wch (&metafied)) != ERR)
c = ALT | metafied;
if (k == 27 && (res = get_wch (&metafied)) != ERR)
k = ALT | metafied;
if (res == KEY_CODE_YES)
c |= SYM;
k |= SYM;
return true;
}
fun parse_key (const string &key_name) -> wint_t {
wint_t c{};
fun parse_key (const string &key_name) -> Key {
Key k{};
auto p = key_name.c_str ();
if (!strncmp (p, "M-", 2)) {
c |= ALT;
k |= ALT;
p += 2;
}
if (g.name_to_key.count (p)) {
return c | g.name_to_key.at (p);
return k | g.name_to_key.at (p);
} else if (!strncmp (p, "C-", 2)) {
p += 2;
if (*p < '?' || *p > '~') {
cerr << "bindings: invalid combination: " << key_name << endl;
return WEOF;
}
c |= CTRL (*p);
k |= CTRL (*p);
p += 1;
} else {
wchar_t w; mbstate_t mb {};
@@ -1888,18 +1900,18 @@ fun parse_key (const string &key_name) -> wint_t {
cerr << "bindings: invalid encoding: " << key_name << endl;
return WEOF;
}
c |= w;
k |= w;
p += res;
}
if (*p) {
cerr << "key name has unparsable trailing part: " << key_name << endl;
return WEOF;
}
return c;
return k;
}
fun learn_named_key (const string &name, wint_t key) {
g.name_to_key[g.key_to_name[key] = name] = key;
fun learn_named_key (const string &name, Key k) {
g.name_to_key[g.key_to_name[k] = name] = k;
}
fun load_bindings () {
@@ -1958,15 +1970,15 @@ fun load_bindings () {
cerr << "bindings: invalid context: " << context << endl;
continue;
}
wint_t c = parse_key (key_name);
if (c == WEOF)
Key k = parse_key (key_name);
if (k == WEOF)
continue;
auto i = actions.find (action);
if (i == actions.end ()) {
cerr << "bindings: invalid action: " << action << endl;
continue;
}
(*m->second)[c] = i->second;
(*m->second)[k] = i->second;
}
}
@@ -2061,14 +2073,20 @@ int main (int argc, char *argv[]) {
cerr << "cannot initialize inotify" << endl;
return 1;
}
#else
#elif !defined __CYGWIN__
if ((g.watch_fd = kqueue ()) < 0) {
cerr << "cannot initialize kqueue" << endl;
return 1;
}
#endif
locale::global (locale (""));
try {
// Under MSYS2, the C++ locale mechanism cannot load UTF-8 this way.
locale::global (locale (""));
} catch (const runtime_error &) {
setlocale (LC_CTYPE, "");
}
load_bindings ();
load_config ();
@@ -2101,8 +2119,8 @@ int main (int argc, char *argv[]) {
return 1;
}
wint_t c;
while (!read_key (c) || handle (c)) {
Key k;
while (!read_key (k) || handle (k)) {
watch_check ();
if (g.sort_flash_ttl && !--g.sort_flash_ttl)
update ();