Add support for BSD derivatives, fix macOS
This commit is contained in:
		
							parent
							
								
									3607757554
								
							
						
					
					
						commit
						52a28f01c8
					
				| @ -8,11 +8,13 @@ if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") | |||||||
| endif () | endif () | ||||||
| 
 | 
 | ||||||
| find_package (PkgConfig REQUIRED) | find_package (PkgConfig REQUIRED) | ||||||
| pkg_check_modules (NCURSESW QUIET ncursesw) | pkg_check_modules (NCURSESW REQUIRED ncursesw) | ||||||
|  | pkg_check_modules (ACL libacl) | ||||||
| 
 | 
 | ||||||
| add_executable (${PROJECT_NAME} ${PROJECT_NAME}.cpp) | add_executable (${PROJECT_NAME} ${PROJECT_NAME}.cpp) | ||||||
| target_include_directories (${PROJECT_NAME} PUBLIC ${NCURSESW_INCLUDE_DIRS}) | target_include_directories (${PROJECT_NAME} PUBLIC ${NCURSESW_INCLUDE_DIRS}) | ||||||
| target_link_libraries (${PROJECT_NAME} PUBLIC ${NCURSESW_LIBRARIES} acl) | target_link_libraries (${PROJECT_NAME} | ||||||
|  | 	PUBLIC ${NCURSESW_LIBRARIES} ${ACL_LIBRARIES}) | ||||||
| target_compile_features (${PROJECT_NAME} PUBLIC cxx_std_14) | target_compile_features (${PROJECT_NAME} PUBLIC cxx_std_14) | ||||||
| target_compile_definitions (${PROJECT_NAME} PUBLIC | target_compile_definitions (${PROJECT_NAME} PUBLIC | ||||||
| 	-DPROJECT_NAME=\"${PROJECT_NAME}\" -DPROJECT_VERSION=\"${PROJECT_VERSION}\") | 	-DPROJECT_NAME=\"${PROJECT_NAME}\" -DPROJECT_VERSION=\"${PROJECT_VERSION}\") | ||||||
|  | |||||||
| @ -11,8 +11,8 @@ commands.  It enables you to: | |||||||
|    can be simply forwarded if it is to be edited.  What's more, it will always |    can be simply forwarded if it is to be edited.  What's more, it will always | ||||||
|    be obvious whether the navigator is running. |    be obvious whether the navigator is running. | ||||||
| 
 | 
 | ||||||
| The only supported platform is Linux.  I wanted to try a different, simpler | 'sdn' runs on Linux and all BSD derivatives.  I wanted to try a different, | ||||||
| approach here, and the end result is very friendly to tinkering. | simpler approach here, and the end result is very friendly to tinkering. | ||||||
| 
 | 
 | ||||||
| image::sdn.png[align="center"] | image::sdn.png[align="center"] | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										93
									
								
								sdn.cpp
									
									
									
									
									
								
							
							
						
						
									
										93
									
								
								sdn.cpp
									
									
									
									
									
								
							| @ -45,9 +45,13 @@ | |||||||
| #include <time.h> | #include <time.h> | ||||||
| #include <unistd.h> | #include <unistd.h> | ||||||
| 
 | 
 | ||||||
|  | #ifdef __linux__ | ||||||
| #include <acl/libacl.h> | #include <acl/libacl.h> | ||||||
| #include <ncurses.h> |  | ||||||
| #include <sys/inotify.h> | #include <sys/inotify.h> | ||||||
|  | #else | ||||||
|  | #include <sys/event.h> | ||||||
|  | #endif | ||||||
|  | #include <ncurses.h> | ||||||
| #include <sys/types.h> | #include <sys/types.h> | ||||||
| #include <sys/wait.h> | #include <sys/wait.h> | ||||||
| #include <sys/xattr.h> | #include <sys/xattr.h> | ||||||
| @ -57,9 +61,10 @@ | |||||||
| #include <term.h> | #include <term.h> | ||||||
| #undef CTRL  // term.h -> termios.h -> sys/ttydefaults.h, too simplistic
 | #undef CTRL  // term.h -> termios.h -> sys/ttydefaults.h, too simplistic
 | ||||||
| 
 | 
 | ||||||
| // Unicode is complex enough already and we might make assumptions
 |  | ||||||
| #ifndef __STDC_ISO_10646__ | #ifndef __STDC_ISO_10646__ | ||||||
| #error Unicode required for wchar_t | // Unicode is complex enough already and we might make assumptions,
 | ||||||
|  | // though macOS doesn't define this despite using UCS-4,
 | ||||||
|  | // and we won't build on Windows that seems to be the only one to use UTF-16.
 | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| // Trailing return types make C++ syntax suck considerably less
 | // Trailing return types make C++ syntax suck considerably less
 | ||||||
| @ -303,7 +308,21 @@ fun xdg_config_write (const string &suffix) -> unique_ptr<fstream> { | |||||||
| 
 | 
 | ||||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||||
| 
 | 
 | ||||||
| using ncstring = basic_string<cchar_t>; | // This should be basic_string, however that crashes on macOS
 | ||||||
|  | using ncstring = vector<cchar_t>; | ||||||
|  | 
 | ||||||
|  | fun operator+ (const ncstring &lhs, const ncstring &rhs) -> ncstring { | ||||||
|  | 	ncstring result; | ||||||
|  | 	result.reserve (lhs.size () + rhs.size ()); | ||||||
|  | 	result.insert (result.end (), lhs.begin (), lhs.end ()); | ||||||
|  | 	result.insert (result.end (), rhs.begin (), rhs.end ()); | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fun operator+= (ncstring &lhs, const ncstring &rhs) -> ncstring & { | ||||||
|  | 	lhs.insert (lhs.end (), rhs.begin (), rhs.end ()); | ||||||
|  | 	return lhs; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| fun cchar (chtype attrs, wchar_t c) -> cchar_t { | fun cchar (chtype attrs, wchar_t c) -> cchar_t { | ||||||
| 	cchar_t ch {}; wchar_t ws[] = {c, 0}; | 	cchar_t ch {}; wchar_t ws[] = {c, 0}; | ||||||
| @ -538,7 +557,7 @@ static struct { | |||||||
| 	bool no_chdir;                      ///< Do not tell the shell to chdir
 | 	bool no_chdir;                      ///< Do not tell the shell to chdir
 | ||||||
| 	bool quitting;                      ///< Whether we should quit already
 | 	bool quitting;                      ///< Whether we should quit already
 | ||||||
| 
 | 
 | ||||||
| 	int inotify_fd, inotify_wd = -1;    ///< File watch
 | 	int watch_fd, watch_wd = -1;        ///< File watch (inotify/kqueue)
 | ||||||
| 	bool out_of_date;                   ///< Entries may be out of date
 | 	bool out_of_date;                   ///< Entries may be out of date
 | ||||||
| 
 | 
 | ||||||
| 	const wchar_t *editor;              ///< Prompt string for editing
 | 	const wchar_t *editor;              ///< Prompt string for editing
 | ||||||
| @ -597,8 +616,10 @@ fun ls_format (const entry &e, bool for_target) -> chtype { | |||||||
| 			set (LS_MULTIHARDLINK); | 			set (LS_MULTIHARDLINK); | ||||||
| 		if ((info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) | 		if ((info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) | ||||||
| 			set (LS_EXECUTABLE); | 			set (LS_EXECUTABLE); | ||||||
|  | #ifdef __linux__ | ||||||
| 		if (lgetxattr (name.c_str (), "security.capability", NULL, 0) >= 0) | 		if (lgetxattr (name.c_str (), "security.capability", NULL, 0) >= 0) | ||||||
| 			set (LS_CAPABILITY); | 			set (LS_CAPABILITY); | ||||||
|  | #endif | ||||||
| 		if ((info.st_mode & S_ISGID)) | 		if ((info.st_mode & S_ISGID)) | ||||||
| 			set (LS_SETGID); | 			set (LS_SETGID); | ||||||
| 		if ((info.st_mode & S_ISUID)) | 		if ((info.st_mode & S_ISUID)) | ||||||
| @ -692,11 +713,13 @@ fun make_entry (const struct dirent *f) -> entry { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	auto mode = decode_mode (info.st_mode); | 	auto mode = decode_mode (info.st_mode); | ||||||
|  | #ifdef __linux__ | ||||||
| 	// We're using a laughably small subset of libacl: this translates to
 | 	// We're using a laughably small subset of libacl: this translates to
 | ||||||
| 	// two lgetxattr() calls, the results of which are compared with
 | 	// two lgetxattr() calls, the results of which are compared with
 | ||||||
| 	// specific architecture-dependent constants.  Linux-only.
 | 	// specific architecture-dependent constants.  Linux-only.
 | ||||||
| 	if (acl_extended_file_nofollow (f->d_name) > 0) | 	if (acl_extended_file_nofollow (f->d_name) > 0) | ||||||
| 		mode += L"+"; | 		mode += L"+"; | ||||||
|  | #endif | ||||||
| 	e.cols[entry::MODES] = apply_attrs (mode, 0); | 	e.cols[entry::MODES] = apply_attrs (mode, 0); | ||||||
| 
 | 
 | ||||||
| 	auto usr = g.unames.find (info.st_uid); | 	auto usr = g.unames.find (info.st_uid); | ||||||
| @ -726,8 +749,8 @@ fun make_entry (const struct dirent *f) -> entry { | |||||||
| 	auto &fn = e.cols[entry::FILENAME] = | 	auto &fn = e.cols[entry::FILENAME] = | ||||||
| 		apply_attrs (to_wide (e.filename), ls_format (e, false)); | 		apply_attrs (to_wide (e.filename), ls_format (e, false)); | ||||||
| 	if (!e.target_path.empty ()) { | 	if (!e.target_path.empty ()) { | ||||||
| 		fn.append (apply_attrs (L" -> ", 0)); | 		fn += apply_attrs (L" -> ", 0); | ||||||
| 		fn.append (apply_attrs (to_wide (e.target_path), ls_format (e, true))); | 		fn += apply_attrs (to_wide (e.target_path), ls_format (e, true)); | ||||||
| 	} | 	} | ||||||
| 	return e; | 	return e; | ||||||
| } | } | ||||||
| @ -797,8 +820,8 @@ fun update () { | |||||||
| 			print (info, info_width); | 			print (info, info_width); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		auto start = sanitize (prompt + line.substr (0, g.editor_cursor)); | 		line.resize (g.editor_cursor); | ||||||
| 		move (LINES - 1, compute_width (start)); | 		move (LINES - 1, compute_width (sanitize (prompt + line))); | ||||||
| 		curs_set (1); | 		curs_set (1); | ||||||
| 	} else if (!g.message.empty ()) { | 	} else if (!g.message.empty ()) { | ||||||
| 		move (LINES - 1, 0); | 		move (LINES - 1, 0); | ||||||
| @ -921,12 +944,25 @@ readfail: | |||||||
| 	g.cursor = max (0, min (g.cursor, int (g.entries.size ()) - 1)); | 	g.cursor = max (0, min (g.cursor, int (g.entries.size ()) - 1)); | ||||||
| 	g.offset = max (0, min (g.offset, int (g.entries.size ()) - 1)); | 	g.offset = max (0, min (g.offset, int (g.entries.size ()) - 1)); | ||||||
| 
 | 
 | ||||||
| 	if (g.inotify_wd != -1) | #ifdef __linux__ | ||||||
| 		inotify_rm_watch (g.inotify_fd, g.inotify_wd); | 	if (g.watch_wd != -1) | ||||||
|  | 		inotify_rm_watch (g.watch_fd, g.watch_wd); | ||||||
| 
 | 
 | ||||||
| 	// We don't show atime, so access and open are merely spam
 | 	// We don't show atime, so access and open are merely spam
 | ||||||
| 	g.inotify_wd = inotify_add_watch (g.inotify_fd, ".", | 	g.watch_wd = inotify_add_watch (g.watch_fd, ".", | ||||||
| 		(IN_ALL_EVENTS | IN_ONLYDIR | IN_EXCL_UNLINK) & ~(IN_ACCESS | IN_OPEN)); | 		(IN_ALL_EVENTS | IN_ONLYDIR | IN_EXCL_UNLINK) & ~(IN_ACCESS | IN_OPEN)); | ||||||
|  | #else | ||||||
|  | 	if (g.watch_wd != -1) | ||||||
|  | 		close (g.watch_wd); | ||||||
|  | 
 | ||||||
|  | 	if ((g.watch_wd = open (".", O_RDONLY | O_DIRECTORY | O_CLOEXEC)) >= 0) { | ||||||
|  | 		// At least the macOS kqueue doesn't report anything too specific
 | ||||||
|  | 		struct kevent ev {}; | ||||||
|  | 		EV_SET (&ev, g.watch_wd, EVFILT_VNODE, EV_ADD | EV_CLEAR, | ||||||
|  | 			NOTE_WRITE | NOTE_LINK, 0, nullptr); | ||||||
|  | 		(void) kevent (g.watch_fd, &ev, 1, nullptr, 0, nullptr); | ||||||
|  | 	} | ||||||
|  | #endif | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fun run_program (initializer_list<const char *> list, const string &filename) { | fun run_program (initializer_list<const char *> list, const string &filename) { | ||||||
| @ -1550,19 +1586,27 @@ fun handle (wint_t c) -> bool { | |||||||
| 	return !g.quitting; | 	return !g.quitting; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fun inotify_check () { | fun watch_check () { | ||||||
| 	// Only provide simple indication that contents might have changed
 |  | ||||||
| 	char buf[4096]; ssize_t len; |  | ||||||
| 	bool changed = false; | 	bool changed = false; | ||||||
| 	while ((len = read (g.inotify_fd, buf, sizeof buf)) > 0) { | 	// Only provide simple indication that contents might have changed,
 | ||||||
|  | 	// if only because kqueue can't do any better
 | ||||||
|  | #ifdef __linux__ | ||||||
|  | 	char buf[4096]; ssize_t len; | ||||||
|  | 	while ((len = read (g.watch_fd, buf, sizeof buf)) > 0) { | ||||||
| 		const inotify_event *e; | 		const inotify_event *e; | ||||||
| 		for (char *ptr = buf; ptr < buf + len; ptr += sizeof *e + e->len) { | 		for (char *ptr = buf; ptr < buf + len; ptr += sizeof *e + e->len) { | ||||||
| 			e = (const inotify_event *) buf; | 			e = (const inotify_event *) buf; | ||||||
| 			if (e->wd == g.inotify_wd) | 			if (e->wd == g.watch_wd) | ||||||
| 				changed = g.out_of_date = true; | 				changed = true; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if (changed) | #else | ||||||
|  | 	struct kevent ev {}; | ||||||
|  | 	struct timespec timeout {}; | ||||||
|  | 	if (kevent (g.watch_fd, nullptr, 0, &ev, 1, &timeout) > 0) | ||||||
|  | 		changed = ev.filter == EVFILT_VNODE && (ev.fflags & NOTE_WRITE); | ||||||
|  | #endif | ||||||
|  | 	if ((g.out_of_date = changed)) | ||||||
| 		update (); | 		update (); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -1884,10 +1928,17 @@ int main (int argc, char *argv[]) { | |||||||
| 	// So that the neither us nor our children stop on tcsetpgrp()
 | 	// So that the neither us nor our children stop on tcsetpgrp()
 | ||||||
| 	signal (SIGTTOU, SIG_IGN); | 	signal (SIGTTOU, SIG_IGN); | ||||||
| 
 | 
 | ||||||
| 	if ((g.inotify_fd = inotify_init1 (IN_NONBLOCK)) < 0) { | #ifdef __linux__ | ||||||
|  | 	if ((g.watch_fd = inotify_init1 (IN_NONBLOCK)) < 0) { | ||||||
| 		cerr << "cannot initialize inotify" << endl; | 		cerr << "cannot initialize inotify" << endl; | ||||||
| 		return 1; | 		return 1; | ||||||
| 	} | 	} | ||||||
|  | #else | ||||||
|  | 	if ((g.watch_fd = kqueue ()) < 0) { | ||||||
|  | 		cerr << "cannot initialize kqueue" << endl; | ||||||
|  | 		return 1; | ||||||
|  | 	} | ||||||
|  | #endif | ||||||
| 
 | 
 | ||||||
| 	locale::global (locale ("")); | 	locale::global (locale ("")); | ||||||
| 	load_bindings (); | 	load_bindings (); | ||||||
| @ -1924,7 +1975,7 @@ int main (int argc, char *argv[]) { | |||||||
| 
 | 
 | ||||||
| 	wint_t c; | 	wint_t c; | ||||||
| 	while (!read_key (c) || handle (c)) { | 	while (!read_key (c) || handle (c)) { | ||||||
| 		inotify_check (); | 		watch_check (); | ||||||
| 		if (g.sort_flash_ttl && !--g.sort_flash_ttl) | 		if (g.sort_flash_ttl && !--g.sort_flash_ttl) | ||||||
| 			update (); | 			update (); | ||||||
| 		if (g.message_ttl && !--g.message_ttl) { | 		if (g.message_ttl && !--g.message_ttl) { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user