diff --git a/src/cpp/project/blank-screens/src/blind.cpp b/src/cpp/project/blank-screens/src/blind.cpp index 34705c3e..9bf38f3c 100644 --- a/src/cpp/project/blank-screens/src/blind.cpp +++ b/src/cpp/project/blank-screens/src/blind.cpp @@ -1,108 +1,337 @@ #include "blind.hpp" #include +#include +#include +#include + +#include #include +#include + +#include +#include + +// // PUBLIC // // -#include - -bs::blind::blind(std::shared_ptr cli, - std::string_view monitor, - Display* display, - int default_screen, - Window root_window, - XRRCrtcInfo* crtc_info) - : m_monitor(monitor) - , m_cli(cli) - , m_display(display) +bs::blinds::blinds(const bs::cli& cli) + : m_cli(cli) + , m_display(XOpenDisplay(nullptr)) + , m_screen(DefaultScreen(m_display)) + , m_root_window(RootWindow(m_display, m_screen)) { - m_window = XCreateSimpleWindow(display, - root_window, - crtc_info->x, - crtc_info->y, - crtc_info->width, - crtc_info->height, - 0, - 0, - 0); - - auto* class_hint = XAllocClassHint(); - static std::string class_name(xph::exec_name); - class_hint->res_name = class_name.data(); - class_hint->res_class = class_name.data(); - XSetClassHint(display, m_window, class_hint); - - XSetWindowAttributes window_attributes; - window_attributes.override_redirect = True; - - const auto wm_state = XInternAtom(display, "_NET_WM_STATE", False); - const auto wm_state_above = XInternAtom(display, "_NET_WM_STATE_ABOVE", False); - const auto wm_window_type = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False); - const auto wm_window_type_dock = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DOCK", False); - XChangeProperty(display, - m_window, - wm_state, - XA_ATOM, - 32, - PropModeAppend, - reinterpret_cast(&wm_state_above), - 1); - XChangeProperty(display, - m_window, - wm_window_type, - XA_ATOM, - 32, - PropModeReplace, - reinterpret_cast(&wm_window_type), - 1); - XChangeProperty(display, - m_window, - wm_window_type_dock, - XA_ATOM, - 32, - PropModeReplace, - reinterpret_cast(&wm_window_type_dock), - 1); + xph::die_if(!m_display, "unable to open display"); + + const auto& monitors = m_cli.monitors(); + for (const auto& monitor : monitors) + this->add_monitor(monitor); +} + +bs::blinds::~blinds(void) +{ + // TODO: create xph::assert for DEBUG + xph::die_if(!m_display, "we shouldn't be able to reach here if m_display is null"); + + destroy_windows(); + + XCloseDisplay(m_display); +} + +bool bs::blinds::add_monitor(const std::string& monitor_expr, bool commit_changes) +{ + const auto& monitor = eval_monitor_expr(monitor_expr); + + if (std::all_of(monitor.begin(), monitor.end(), ::isspace)) + return false; + + if (std::find(m_monitors.begin(), m_monitors.end(), monitor) != m_monitors.end()) + return false; + + m_monitors.push_back(monitor); + m_alphas.push_back(0.0); + if (commit_changes) + update_windows(); + + return true; +} + +bool bs::blinds::remove_monitor(const std::string& monitor_expr, bool commit_changes) +{ + const auto& monitor = eval_monitor_expr(monitor_expr); + + auto monitor_it = std::find(m_monitors.begin(), m_monitors.end(), monitor); + if (monitor_it == m_monitors.end()) + return false; + + m_alphas.erase(m_alphas.begin() + (monitor_it - m_monitors.begin())); + m_monitors.erase(monitor_it); + if (commit_changes) + update_windows(); + + return true; +} + +void bs::blinds::toggle_monitor(const std::string& monitor_expr, bool commit_changes) +{ + if (!add_monitor(monitor_expr, commit_changes)) + remove_monitor(monitor_expr, commit_changes); +} + +void bs::blinds::commit_monitor_changes(void) +{ + update_windows(); + + std::cerr << "\nmonitors:"; + for (const auto& monitor : m_monitors) + std::cerr << ' ' << monitor; + std::cerr << "\nwindows:"; + for (const auto& window : m_windows) + std::cerr << ' ' << window; + std::cerr << "\nalphas:"; + for (const auto& alpha : m_alphas) + std::cerr << ' ' << alpha; + std::cerr << '\n'; +} + +void bs::blinds::lerp_alpha(double alpha) +{ + const constexpr double epsilon = 0.0001; + + static std::vector m_lerp_idx; + + m_lerp_idx.reserve(m_windows.size()); + for (std::size_t i = 0; i < m_windows.size(); ++i) + m_lerp_idx.push_back(i); + + while (m_lerp_idx.size()) { + for (auto it = m_lerp_idx.end() - 1; it >= m_lerp_idx.begin(); --it) { + const auto& window = m_windows[*it]; + const auto& last_alpha = m_alphas[*it]; - XChangeWindowAttributes(display, m_window, CWOverrideRedirect, &window_attributes); + if (xph::approx_eq(last_alpha, alpha, epsilon)) { + m_lerp_idx.erase(it); + continue; + } - XMapWindow(display, m_window); - XRaiseWindow(display, m_window); - XFlush(display); + const auto& new_alpha = last_alpha + (alpha - last_alpha) * m_cli.lerp_factor(); + m_alphas[*it] = set_window_alpha(window, new_alpha); + } - set_alpha(cli->alpha()); + XFlush(m_display); - XSetWindowBackground(display, m_window, BlackPixel(display, default_screen)); - XClearWindow(display, m_window); - XFlush(display); + std::this_thread::sleep_for(m_cli.frame_time()); + } } -bs::blind::~blind(void) +// // PRIVATE // // + +std::string bs::blinds::eval_monitor_expr(const std::string& monitor_expr) +{ + return monitor_expr == "@cursor" ? get_cursor_monitor() : monitor_expr; +} + +void bs::blinds::update_windows(void) +{ + destroy_windows(); + create_windows(); +} + +void bs::blinds::create_windows(void) +{ + const auto screen_resources = XRRGetScreenResources(m_display, m_root_window); + xph::die_if(!screen_resources, "unable to get screen resources"); + + auto primary_output = XRRGetOutputPrimary(m_display, m_root_window); + + for (decltype(screen_resources->noutput) i = 0; i < screen_resources->noutput; ++i) { + if (m_cli.ignore_primary() && screen_resources->outputs[i] == primary_output) + continue; + + const auto output_info = + XRRGetOutputInfo(m_display, screen_resources, screen_resources->outputs[i]); + if (!output_info) { + std::cerr << xph::exec_name << ": unable to get information for monitor " << i << '\n'; + continue; + } + + decltype(m_monitors.begin()) idx; + + if (output_info->connection) + goto next; + + idx = std::find(m_monitors.begin(), m_monitors.end(), output_info->name); + if (idx == m_monitors.end()) + goto next; + + XRRCrtcInfo* crtc_info; + crtc_info = XRRGetCrtcInfo(m_display, screen_resources, output_info->crtc); + if (!crtc_info) { + std::cerr << xph::exec_name << ": unable to get information for monitor " << i << '\n'; + goto next; + } + + { + const auto window = XCreateSimpleWindow(m_display, + m_root_window, + crtc_info->x, + crtc_info->y, + crtc_info->width, + crtc_info->height, + 0, + 0, + 0); + + auto* class_hint = XAllocClassHint(); + static std::string class_name(xph::exec_name); + class_hint->res_name = class_name.data(); + class_hint->res_class = class_name.data(); + XSetClassHint(m_display, window, class_hint); + + auto window_idx = m_windows.begin() + (idx - m_monitors.begin()); + if (window_idx > m_windows.end()) + m_windows.push_back(window); + else + m_windows.insert(window_idx, window); + } + + XRRFreeCrtcInfo(crtc_info); +next: + XRRFreeOutputInfo(output_info); + } + XRRFreeScreenResources(screen_resources); + + auto begin = m_windows.begin(); + auto end = m_windows.end(); + for (auto it = begin; it != end; ++it) { + XSetWindowAttributes window_attributes; + window_attributes.override_redirect = True; + + const auto wm_state = XInternAtom(m_display, "_NET_WM_STATE", False); + const auto wm_state_above = XInternAtom(m_display, "_NET_WM_STATE_ABOVE", False); + const auto wm_window_type = XInternAtom(m_display, "_NET_WM_WINDOW_TYPE", False); + const auto wm_window_type_dock = XInternAtom(m_display, "_NET_WM_WINDOW_TYPE_DOCK", False); + XChangeProperty(m_display, + *it, + wm_state, + XA_ATOM, + 32, + PropModeAppend, + reinterpret_cast(&wm_state_above), + 1); + XChangeProperty(m_display, + *it, + wm_window_type, + XA_ATOM, + 32, + PropModeReplace, + reinterpret_cast(&wm_window_type), + 1); + XChangeProperty(m_display, + *it, + wm_window_type_dock, + XA_ATOM, + 32, + PropModeReplace, + reinterpret_cast(&wm_window_type_dock), + 1); + + XChangeWindowAttributes(m_display, *it, CWOverrideRedirect, &window_attributes); + + XMapWindow(m_display, *it); + XRaiseWindow(m_display, *it); + XFlush(m_display); + + m_alphas[it - begin] = set_window_alpha(*it, m_alphas[it - begin]); + XSetWindowBackground(m_display, *it, BlackPixel(m_display, m_screen)); + XClearWindow(m_display, *it); + XFlush(m_display); + } + + lerp_alpha(m_cli.alpha()); +} + +void bs::blinds::destroy_windows(void) +{ + for (auto& window : m_windows) + XDestroyWindow(m_display, window); + m_windows.clear(); +} + +std::string bs::blinds::get_cursor_monitor(void) { - XDestroyWindow(m_display, m_window); - XFlush(m_display); + + int root_x, root_y, win_x, win_y; + unsigned int mask; + Window root_return, child_return; + + XQueryPointer(m_display, + m_root_window, + &root_return, + &child_return, + &root_x, + &root_y, + &win_x, + &win_y, + &mask); + + XRRScreenResources* screen_resources = XRRGetScreenResources(m_display, m_root_window); + xph::die_if(!screen_resources, "unable to get screen resources"); + + for (int i = 0; i < screen_resources->noutput; ++i) { + XRROutputInfo* output_info = + XRRGetOutputInfo(m_display, screen_resources, screen_resources->outputs[i]); + if (!output_info || output_info->connection != RR_Connected) { + if (output_info) + XRRFreeOutputInfo(output_info); + continue; + } + + XRRCrtcInfo* crtc_info = XRRGetCrtcInfo(m_display, screen_resources, output_info->crtc); + if (!crtc_info) { + XRRFreeOutputInfo(output_info); + continue; + } + + if (root_x >= crtc_info->x && root_x < static_cast(crtc_info->x + crtc_info->width) && + root_y >= crtc_info->y && root_y < static_cast(crtc_info->y + crtc_info->height)) { + std::string monitor_name = output_info->name; + XRRFreeCrtcInfo(crtc_info); + XRRFreeOutputInfo(output_info); + XRRFreeScreenResources(screen_resources); + return monitor_name; + } + + XRRFreeCrtcInfo(crtc_info); + XRRFreeOutputInfo(output_info); + } + + XRRFreeScreenResources(screen_resources); + + return {}; } -void bs::blind::set_alpha(double alpha) +double bs::blinds::set_window_alpha(Window window, double alpha) { - alpha = std::clamp(alpha, m_cli->min_alpha(), m_cli->max_alpha()); + alpha = std::clamp(alpha, m_cli.min_alpha(), m_cli.max_alpha()); unsigned long opacity; - if (alpha > 1 - m_cli->snap_threshold()) + if (alpha > 1 - m_cli.snap_threshold()) opacity = 0xFFFFFFFFul; - else if (alpha < m_cli->snap_threshold()) + else if (alpha < m_cli.snap_threshold()) opacity = 0x00000000ul; else opacity = 0xFFFFFFFFul * alpha; const auto opacity_atom = XInternAtom(m_display, "_NET_WM_WINDOW_OPACITY", False); XChangeProperty(m_display, - m_window, + window, opacity_atom, XA_CARDINAL, 32, PropModeReplace, reinterpret_cast(&opacity), 1L); - XFlush(m_display); + + return alpha; } diff --git a/src/cpp/project/blank-screens/src/blind.hpp b/src/cpp/project/blank-screens/src/blind.hpp index cf104dc6..b6a8a21c 100644 --- a/src/cpp/project/blank-screens/src/blind.hpp +++ b/src/cpp/project/blank-screens/src/blind.hpp @@ -6,26 +6,34 @@ #include "cli.hpp" namespace bs { - class blind { - public: - std::string_view m_monitor; - + class blinds { private: - std::shared_ptr m_cli; - Window m_window; - Display* m_display; + const cli& m_cli; + Display* const m_display; + const int m_screen; + const Window m_root_window; + std::vector m_monitors; + std::vector m_windows; + std::vector m_alphas; public: - blind(void) = delete; - ~blind(void); - blind(std::shared_ptr cli, - std::string_view monitor, - Display* display, - int default_screen, - Window root_window, - XRRCrtcInfo* crtc_info); + blinds(void) = delete; + ~blinds(void); + blinds(const cli& cli); - void set_alpha(double alpha); + bool add_monitor(const std::string& monitor_expr, bool commit_changes = true); + bool remove_monitor(const std::string& monitor_expr, bool commit_changes = true); + void toggle_monitor(const std::string& monitor_expr, bool commit_changes = true); + void commit_monitor_changes(void); + void lerp_alpha(double alpha); + + private: + std::string eval_monitor_expr(const std::string& monitor_expr); + void update_windows(void); + void create_windows(void); + void destroy_windows(void); + std::string get_cursor_monitor(void); + double set_window_alpha(Window window, double alpha); }; } // namespace bs diff --git a/src/cpp/project/blank-screens/src/daemon.cpp b/src/cpp/project/blank-screens/src/daemon.cpp index 6dde5430..032a3733 100644 --- a/src/cpp/project/blank-screens/src/daemon.cpp +++ b/src/cpp/project/blank-screens/src/daemon.cpp @@ -18,18 +18,7 @@ namespace fs = std::filesystem; -bs::daemon::daemon(const cli& cli) : m_cli(cli) -{ - m_display = XOpenDisplay(nullptr); - xph::die_if(!m_display, "unable to open display"); - - const auto& monitors = m_cli.monitors(); - for (const auto& monitor : monitors) - add_monitor(monitor); - - m_last_alpha = 0.0; - lerp_alpha(cli.alpha()); -} +bs::daemon::daemon(const cli& cli) : m_cli(cli), m_blinds({ cli }) {} [[noreturn]] void bs::daemon::run() { @@ -47,59 +36,6 @@ bs::daemon::daemon(const cli& cli) : m_cli(cli) std::exit(EXIT_FAILURE); } -static std::string get_cursor_monitor() -{ - Display* display = XOpenDisplay(nullptr); - xph::die_if(!display, "unable to open display"); - - int screen = DefaultScreen(display); - Window root_window = RootWindow(display, screen); - - int root_x, root_y, win_x, win_y; - unsigned int mask; - Window root_return, child_return; - - XQueryPointer( - display, root_window, &root_return, &child_return, &root_x, &root_y, &win_x, &win_y, &mask); - - XRRScreenResources* screen_resources = XRRGetScreenResources(display, root_window); - xph::die_if(!screen_resources, "unable to get screen resources"); - - for (int i = 0; i < screen_resources->noutput; ++i) { - XRROutputInfo* output_info = - XRRGetOutputInfo(display, screen_resources, screen_resources->outputs[i]); - if (!output_info || output_info->connection != RR_Connected) { - if (output_info) - XRRFreeOutputInfo(output_info); - continue; - } - - XRRCrtcInfo* crtc_info = XRRGetCrtcInfo(display, screen_resources, output_info->crtc); - if (!crtc_info) { - XRRFreeOutputInfo(output_info); - continue; - } - - if (root_x >= crtc_info->x && root_x < static_cast(crtc_info->x + crtc_info->width) && - root_y >= crtc_info->y && root_y < static_cast(crtc_info->y + crtc_info->height)) { - std::string monitor_name = output_info->name; - XRRFreeCrtcInfo(crtc_info); - XRRFreeOutputInfo(output_info); - XRRFreeScreenResources(screen_resources); - XCloseDisplay(display); - return monitor_name; - } - - XRRFreeCrtcInfo(crtc_info); - XRRFreeOutputInfo(output_info); - } - - XRRFreeScreenResources(screen_resources); - XCloseDisplay(display); - - return {}; -} - void bs::daemon::dispatch(const std::string& command_line) { static std::vector argv{}; @@ -115,138 +51,38 @@ void bs::daemon::dispatch(const std::string& command_line) if (argv.empty()) return; + if (argv[0] == "exit") + std::exit(EXIT_SUCCESS); + + if (argv.size() < 2) + goto err; + if (argv[0] == "alpha") { std::cerr << xph::exec_name << ": setting alpha to " << argv[1] << '\n'; const auto alpha = argv.size() < 2 ? m_cli.alpha() : std::stod(argv[1]); - lerp_alpha(alpha); + m_blinds.lerp_alpha(alpha); } else if (argv[0] == "add") { - for (const auto& monitor : argv | std::views::drop(1)) { - const auto& target_monitor = monitor == "@cursor" ? get_cursor_monitor() : monitor; - std::cerr << xph::exec_name << ": adding monitor " << target_monitor << '\n'; - add_monitor(target_monitor); - } + for (const auto& monitor : argv | std::views::drop(1)) + m_blinds.add_monitor(monitor, false); + m_blinds.commit_monitor_changes(); } else if (argv[0] == "remove") { - for (const auto& monitor : argv | std::views::drop(1)) { - const auto& target_monitor = monitor == "@cursor" ? get_cursor_monitor() : monitor; - std::cerr << xph::exec_name << ": removing monitor " << target_monitor << '\n'; - remove_monitor(target_monitor); - } + for (const auto& monitor : argv | std::views::drop(1)) + m_blinds.remove_monitor(monitor, false); + m_blinds.commit_monitor_changes(); } else if (argv[0] == "toggle") { - for (const auto& monitor : argv | std::views::drop(1)) { - const auto& target_monitor = monitor == "@cursor" ? get_cursor_monitor() : monitor; - std::cerr << xph::exec_name << ": toggling monitor " << target_monitor << '\n'; - toggle_monitor(target_monitor); - } - } else if (argv[0] == "exit") { - std::exit(EXIT_SUCCESS); + for (const auto& monitor : argv | std::views::drop(1)) + m_blinds.toggle_monitor(monitor, false); + m_blinds.commit_monitor_changes(); } else { +err: std::cerr << xph::exec_name << ": unknown command [" << argv[0] << "]\n"; } } -void bs::daemon::lerp_alpha(double alpha) -{ - auto current_alpha = m_last_alpha; - - while (!xph::approx_eq(current_alpha, alpha, km_epsilon)) { - current_alpha = - std::clamp(current_alpha + (alpha - current_alpha) * m_cli.lerp_factor(), 0.0, 1.0); - - for (auto& blind : m_blinds) - blind.set_alpha(current_alpha); - - std::this_thread::sleep_for(m_cli.frame_time()); - } - - m_last_alpha = alpha; -} - -void bs::daemon::add_monitor(const std::string& target_monitor) -{ - for (const auto& blind : m_blinds) { - if (blind.m_monitor == target_monitor) { - std::cerr << xph::exec_name << ": monitor " << target_monitor << " already exists\n"; - return; - } - } - - const auto default_screen = DefaultScreen(m_display); - const auto root_window = RootWindow(m_display, default_screen); - - const auto screen_resources = XRRGetScreenResources(m_display, root_window); - xph::die_if(!screen_resources, "unable to get screen resources"); - - auto primary_output = XRRGetOutputPrimary(m_display, root_window); - - for (decltype(screen_resources->noutput) i = 0; i < screen_resources->noutput; ++i) { - if (m_cli.ignore_primary() && screen_resources->outputs[i] == primary_output) - continue; - - const auto output_info = - XRRGetOutputInfo(m_display, screen_resources, screen_resources->outputs[i]); - if (!output_info) { - std::cerr << xph::exec_name << ": unable to get information for monitor " << i << '\n'; - continue; - } - - if (output_info->connection) - goto next; - if (output_info->connection || output_info->name != target_monitor) - goto next; - - XRRCrtcInfo* crtc_info; - crtc_info = XRRGetCrtcInfo(m_display, screen_resources, output_info->crtc); - if (!crtc_info) { - std::cerr << xph::exec_name << ": unable to get information for monitor " << i << '\n'; - goto next; - } - - m_blinds.emplace_back(std::make_shared(m_cli), - target_monitor, - m_display, - default_screen, - root_window, - crtc_info); - - XRRFreeCrtcInfo(crtc_info); -next: - XRRFreeOutputInfo(output_info); - } - XRRFreeScreenResources(screen_resources); -} - -void bs::daemon::remove_monitor(const std::string& target_monitor) -{ - for (auto it = m_blinds.begin(); it != m_blinds.end(); ++it) { - if (it->m_monitor == target_monitor) { - m_blinds.erase(it); - break; - } - } -} - -void bs::daemon::toggle_monitor(const std::string& target_monitor) -{ - for (auto it = m_blinds.begin(); it != m_blinds.end(); ++it) { - if (it->m_monitor == target_monitor) { - std::cerr << xph::exec_name << ": removing monitor " << target_monitor << '\n'; - m_blinds.erase(it); - return; - } - } - - std::cerr << xph::exec_name << ": adding monitor " << target_monitor << '\n'; - add_monitor(target_monitor); -} - void bs::daemon::handle_signals([[maybe_unused]] int signal) { fs::remove(m_cli.fifo_path()); fs::remove_all(m_cli.lock_path()); - lerp_alpha(0.0); - - m_blinds.clear(); - - XCloseDisplay(m_display); + m_blinds.lerp_alpha(0.0); } diff --git a/src/cpp/project/blank-screens/src/daemon.hpp b/src/cpp/project/blank-screens/src/daemon.hpp index 458aa6f2..45b08c6b 100644 --- a/src/cpp/project/blank-screens/src/daemon.hpp +++ b/src/cpp/project/blank-screens/src/daemon.hpp @@ -8,11 +8,8 @@ namespace bs { class daemon { private: - const constexpr static double km_epsilon = 0.0001; const cli& m_cli; - std::vector m_blinds; - Display* m_display; - double m_last_alpha; + blinds m_blinds; public: daemon(void) = delete; @@ -22,10 +19,6 @@ namespace bs { private: void dispatch(const std::string& command_line); - void lerp_alpha(double alpha); - void add_monitor(const std::string& target_monitor); - void remove_monitor(const std::string& target_monitor); - void toggle_monitor(const std::string& target_monitor); }; } // namespace bs