diff --git a/.bash_aliases b/.bash_aliases index 398fbffa..2110a4b9 100644 --- a/.bash_aliases +++ b/.bash_aliases @@ -131,8 +131,8 @@ command grep -Fiq microsoft /proc/version && . $HOME/.bash_aliases_wsl.bash # executed. _before_command() { - [ -n "${__begin_window+.}" ] && return - __begin_window=$(custom-bash-prompt) + [ -n "${__begin_ts+.}" ] && return + __begin_ts=$(custom-bash-prompt) } # Post-command for command timing. It will be called just before the prompt is @@ -140,10 +140,10 @@ _before_command() _after_command() { local exit_code=$? - [ -z "${__begin_window+.}" ] && return + [ -z "${__begin_ts+.}" ] && return local last_command=$(history 1) - PS1=$(custom-bash-prompt "$last_command" $exit_code $__begin_window $COLUMNS "$PWD" $SHLVL) - unset __begin_window + PS1=$(custom-bash-prompt "$last_command" $exit_code $__begin_ts $COLUMNS "$PWD" $SHLVL) + unset __begin_ts } trap _before_command DEBUG diff --git a/.zshrc b/.zshrc index 1266296a..2d78c314 100644 --- a/.zshrc +++ b/.zshrc @@ -4,16 +4,16 @@ _after_command() { local exit_code=$? - [ -z "${__begin_window+.}" ] && return + [ -z "${__begin_ts+.}" ] && return local last_command=$(history -n -1 2>/dev/null) - PS1=$(custom-zsh-prompt "$last_command" $exit_code $=__begin_window $COLUMNS $PWD $SHLVL) - unset __begin_window + PS1=$(custom-zsh-prompt "$last_command" $exit_code $__begin_ts $COLUMNS $PWD $SHLVL) + unset __begin_ts } _before_command() { - [ -n "${__begin_window+.}" ] && return - __begin_window=$(custom-zsh-prompt) + [ -n "${__begin_ts+.}" ] && return + __begin_ts=$(custom-zsh-prompt) } cfs() diff --git a/custom-prompt/Makefile b/custom-prompt/Makefile index 38c5872d..b3c8c45f 100644 --- a/custom-prompt/Makefile +++ b/custom-prompt/Makefile @@ -1,6 +1,4 @@ -CC = gcc CPPFLAGS = $(shell pkg-config --cflags libgit2) -CFLAGS = -fstrict-aliasing -std=c11 -Wall -Wextra -Wno-unused-parameter CXXFLAGS = -fstrict-aliasing -std=c++17 -Wall -Wextra -Wno-unused-parameter LDLIBS = -lstdc++ $(shell pkg-config --libs libgit2) @@ -9,17 +7,12 @@ MainBashObject = custom-bash-prompt.o MainBashExecutable = bin/$(MainBashObject:.o=) MainZshObject = custom-zsh-prompt.o MainZshExecutable = bin/$(MainZshObject:.o=) -OtherObjects = get_active_wid_linux.o get_active_wid_windows.o json_logger.o - -ifneq "$(OS)" "Windows_NT" - UNAME = $(shell uname) - ifeq "$(UNAME)" "Linux" - CPPFLAGS += $(shell pkg-config --cflags glib-2.0 libnotify x11) - LDLIBS += $(shell pkg-config --libs glib-2.0 libnotify x11) - else - LDLIBS += -framework Cocoa -framework CoreGraphics - OtherObjects += get_active_wid_macos.o - endif +OtherObjects = focus_utils.o json_logger.o + +UNAME = $(shell uname) +ifeq "$(UNAME)" "Linux" + CPPFLAGS += $(shell pkg-config --cflags libnotify) + LDLIBS += $(shell pkg-config --libs libnotify) endif .PHONY: debug release @@ -27,7 +20,6 @@ endif debug: $(MainBashExecutable) $(MainZshExecutable) release: CPPFLAGS += -DNDEBUG -release: CFLAGS += -flto -O2 release: CXXFLAGS += -flto -O2 release: LDFLAGS += -flto -O2 release: debug diff --git a/custom-prompt/custom-prompt.cc b/custom-prompt/custom-prompt.cc index d4e6cac7..81356d28 100644 --- a/custom-prompt/custom-prompt.cc +++ b/custom-prompt/custom-prompt.cc @@ -14,7 +14,7 @@ #include #include -#include "get_active_wid.hh" +#include "focus_utils.hh" #include "json_logger.hh" namespace C @@ -550,8 +550,10 @@ void notify_desktop(std::string_view const& last_command, int exit_code, Interva #else // Xfce Terminal (the best terminal) does not support OSC 777. Do it the // hard way. - C::notify_init(__FILE__); - C::NotifyNotification* notif = C::notify_notification_new(last_command.data(), description.data(), "terminal"); + C::notify_init("Terminal"); + C::NotifyNotification* notif = C::notify_notification_new( + last_command.data(), description.data(), exit_code == 0 ? "dialog-information" : "dialog-error" + ); C::notify_notification_show(notif, nullptr); // C::notify_uninit(); #endif @@ -630,12 +632,10 @@ void write_report(std::string_view const& last_command, int exit_code, Interval * @param last_command Most-recently run command. * @param exit_code Code with which the command exited. * @param delay Running time of the command in nanoseconds. - * @param prev_active_wid ID of the focused window when the command started. * @param columns Width of the terminal window. */ void report_command_status( - std::string_view& last_command, int exit_code, long long unsigned delay, long long unsigned prev_active_wid, - std::size_t columns + std::string_view& last_command, int exit_code, long long unsigned delay, std::size_t columns ) { LOG_DEBUG( @@ -662,12 +662,9 @@ void report_command_status( { return; } - - long long unsigned curr_active_wid = get_active_wid(); - LOG_DEBUG( - logger, "Obtained focused window details", { { "previous", prev_active_wid }, { "current", curr_active_wid } } - ); - if (prev_active_wid != curr_active_wid) + bool terminal_focused = terminal_has_focus(); + LOG_DEBUG(logger, "Obtained focus details", { { "terminal_focused", terminal_focused } }); + if (!terminal_focused) { notify_desktop(last_command, exit_code, interval); } @@ -759,7 +756,7 @@ int main_internal(int const argc, char const* argv[]) long long unsigned ts = get_timestamp(); if (argc <= 1) { - std::cout << ts << ' ' << get_active_wid() << '\n'; + std::cout << ts << '\n'; return EXIT_SUCCESS; } @@ -781,12 +778,11 @@ int main_internal(int const argc, char const* argv[]) std::string_view last_command(argv[1]); int exit_code = std::stoi(argv[2]); long long unsigned delay = ts - std::stoull(argv[3]); - long long unsigned prev_active_wid = std::stoull(argv[4]); - std::size_t columns = std::stoull(argv[5]); - report_command_status(last_command, exit_code, delay, prev_active_wid, columns); + std::size_t columns = std::stoull(argv[4]); + report_command_status(last_command, exit_code, delay, columns); - std::string_view pwd(argv[6]); - int shlvl = std::stoi(argv[7]); + std::string_view pwd(argv[5]); + int shlvl = std::stoi(argv[6]); std::string_view venv_view; char const* venv; if ((venv = getenv("VIRTUAL_ENV_PROMPT")) != nullptr) @@ -814,7 +810,7 @@ int main(int const argc, char const* argv[]) // null-terminated. if (argc == 2) { - char const* argv[] = { "custom-prompt", "[] last_command", "0", "0", "0", "79", "/", "1", nullptr }; + char const* argv[] = { "custom-prompt", "[] last_command", "0", "0", "79", "/", "1", nullptr }; int constexpr argc = sizeof argv / sizeof *argv - 1; return main_internal(argc, argv); } diff --git a/custom-prompt/focus_utils.cc b/custom-prompt/focus_utils.cc new file mode 100644 index 00000000..30058eaa --- /dev/null +++ b/custom-prompt/focus_utils.cc @@ -0,0 +1,114 @@ +#include "focus_utils.hh" +#include "json_logger.hh" + +static JSONLogger logger; + +#ifdef _WIN32 + +#include +#include + +bool terminal_has_focus(void) +{ + HWND foreground_window = GetForegroundWindow(); + TCHAR class_name[64]; + int class_name_len = GetClassName(foreground_window, class_name, sizeof class_name / sizeof *class_name); + LOG_DEBUG(logger, "Read active window", { { "class_name_len", class_name_len } }); + if (class_name_len == 0) + { + return false; + } + // I use WezTerm on Windows because it supports OSC 777; I never open more + // than one window. Hence, checking whether a Wezterm window is active is + // sufficient for me. + return _tcscmp(class_name, _T("org.wezfurlong.wezterm")) == 0; +} + +#else + +#include +#include + +#include +#include +#include + +#define BUFSIZE 255 + +/** + * Temporarily modify standard input to allow non-blocking reads. + */ +class NonBlockingStandardInputGuard +{ +private: + bool error_occurred; + termios prev_termios, curr_termios; + +public: + NonBlockingStandardInputGuard(void); + ~NonBlockingStandardInputGuard(); +}; + +/** + * Enable non-canonical mode. + */ +NonBlockingStandardInputGuard::NonBlockingStandardInputGuard(void) : error_occurred(false) +{ + if (tcgetattr(STDIN_FILENO, &this->prev_termios) == -1) + { + this->error_occurred = true; + return; + } + this->curr_termios = this->prev_termios; + this->curr_termios.c_lflag &= ~(ECHO | ICANON); + // Block until sufficiently many bytes are available from standard input. + this->curr_termios.c_cc[VMIN] = BUFSIZE; + // But not for too long. + this->curr_termios.c_cc[VTIME] = 1; + if (tcsetattr(STDIN_FILENO, TCSANOW, &this->curr_termios) == -1) + { + this->error_occurred = true; + } +} + +/** + * Disable non-canonical mode. + */ +NonBlockingStandardInputGuard::~NonBlockingStandardInputGuard() +{ + if (!this->error_occurred) + { + tcsetattr(STDIN_FILENO, TCSANOW, &this->prev_termios); + } +} + +bool terminal_has_focus(void) +{ + char buf[BUFSIZE]; + ssize_t count; + { + NonBlockingStandardInputGuard _; + // Enable and immediately disable focus reporting. + std::clog << "\x1b\x5b?1004h\x1b\x5b?1004l"; + count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf); + } + LOG_DEBUG(logger, "Read non-blocking standard input", { { "count", count } }); + if (count <= 0) + { + return false; + } + std::string_view buf_view(buf, count); + size_t focus_out_seq_pos = buf_view.rfind("\x1b\x5bO"); + if (focus_out_seq_pos == std::string_view::npos) + { + return true; + } + size_t focus_in_seq_pos = buf_view.rfind("\x1b\x5bI"); + if (focus_in_seq_pos == std::string_view::npos) + { + return false; + } + return focus_out_seq_pos < focus_in_seq_pos; +} + +#endif diff --git a/custom-prompt/focus_utils.hh b/custom-prompt/focus_utils.hh new file mode 100644 index 00000000..9bf23c63 --- /dev/null +++ b/custom-prompt/focus_utils.hh @@ -0,0 +1,11 @@ +#ifndef FOCUS_UTILS_HH_ +#define FOCUS_UTILS_HH_ + +/** + * Check whether the terminal has GUI focus. + * + * @return Focus state. + */ +bool terminal_has_focus(void); + +#endif diff --git a/custom-prompt/get_active_wid.hh b/custom-prompt/get_active_wid.hh deleted file mode 100644 index 3f1a6863..00000000 --- a/custom-prompt/get_active_wid.hh +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef GET_ACTIVE_WID_HH_ -#define GET_ACTIVE_WID_HH_ - -// clang-format off - -#ifdef __APPLE__ -extern "C" { -#endif - -/* - * On Linux, return the ID of the active window if X is running, else 0. - * - * On macOS, return the ID of the topmost window if one is found, else 0. - * - * On Windows, return the ID of the foreground window. - * - * @return Active window ID. - */ -long long unsigned get_active_wid(void); - -#ifdef __APPLE__ -} -#endif - -#endif diff --git a/custom-prompt/get_active_wid_linux.cc b/custom-prompt/get_active_wid_linux.cc deleted file mode 100644 index e46e9599..00000000 --- a/custom-prompt/get_active_wid_linux.cc +++ /dev/null @@ -1,31 +0,0 @@ -#ifdef __linux__ -#include -#include - -long long unsigned get_active_wid(void) -{ - Display* display = XOpenDisplay(nullptr); - if (display == nullptr) - { - return 0; - } - - Window root_window = DefaultRootWindow(display); - Atom property = XInternAtom(display, "_NET_ACTIVE_WINDOW", False); - - Atom actual_type_return; - int actual_format_return; - long unsigned nitems_return; - long unsigned bytes_after_return; - char unsigned* prop_return; - XGetWindowProperty( - display, root_window, property, 0, 1, False, XA_WINDOW, &actual_type_return, &actual_format_return, - &nitems_return, &bytes_after_return, &prop_return - ); - - Window active_wid = *(Window*)prop_return; - // XFree(prop_return); - // XCloseDisplay(display); - return active_wid; -} -#endif diff --git a/custom-prompt/get_active_wid_macos.m b/custom-prompt/get_active_wid_macos.m deleted file mode 100644 index b236f99f..00000000 --- a/custom-prompt/get_active_wid_macos.m +++ /dev/null @@ -1,19 +0,0 @@ -#ifdef __APPLE__ -#include -#include - -long long unsigned get_active_wid(void) -{ - NSArray *windows = (NSArray *)CGWindowListCopyWindowInfo( - kCGWindowListExcludeDesktopElements | kCGWindowListOptionOnScreenOnly, kCGNullWindowID); - for (NSDictionary *window in windows) - { - int windowLayer = [[window objectForKey: (NSString *)kCGWindowLayer] intValue]; - if (windowLayer == 0) - { - return [[window objectForKey: (NSString *)kCGWindowNumber] intValue]; - } - } - return 0; -} -#endif diff --git a/custom-prompt/get_active_wid_windows.cc b/custom-prompt/get_active_wid_windows.cc deleted file mode 100644 index 4f7a1130..00000000 --- a/custom-prompt/get_active_wid_windows.cc +++ /dev/null @@ -1,8 +0,0 @@ -#ifdef _WIN32 -#include - -long long unsigned get_active_wid(void) -{ - return (long long unsigned)GetForegroundWindow(); -} -#endif