diff --git a/.travis.yml b/.travis.yml index 61045757..d5a31130 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,7 @@ addons: - libxcb-randr0-dev - libxcb-util0-dev - libxcb-xkb-dev + - libxcb-cursor-dev - libxcb1-dev - python-xcbgen - xcb-proto diff --git a/cmake/02-opts.cmake b/cmake/02-opts.cmake index 905696f4..adefa582 100644 --- a/cmake/02-opts.cmake +++ b/cmake/02-opts.cmake @@ -9,6 +9,7 @@ checklib(ENABLE_MPD "pkg-config" libmpdclient) checklib(ENABLE_NETWORK "cmake" Libiw) checklib(WITH_XRM "pkg-config" xcb-xrm) checklib(WITH_XRANDR_MONITORS "pkg-config" "xcb-randr>=1.12") +checklib(WITH_XCURSOR "pkg-config" "xcb-cursor") if(NOT DEFINED ENABLE_CCACHE AND CMAKE_BUILD_TYPE_UPPER MATCHES DEBUG) set(ENABLE_CCACHE ON) @@ -36,6 +37,7 @@ option(WITH_XSYNC "xcb-sync support" OFF) option(WITH_XCOMPOSITE "xcb-composite support" OFF) option(WITH_XKB "xcb-xkb support" ON) option(WITH_XRM "xcb-xrm support" ON) +option(WITH_XCURSOR "xcb-cursor support" ON) if(CMAKE_BUILD_TYPE_UPPER MATCHES DEBUG) option(DEBUG_LOGGER "Debug logging" ON) diff --git a/cmake/03-libs.cmake b/cmake/03-libs.cmake index de176a91..44ddf098 100644 --- a/cmake/03-libs.cmake +++ b/cmake/03-libs.cmake @@ -20,3 +20,4 @@ querylib(WITH_XRANDR_MONITORS "pkg-config" "xcb-randr>=1.12" libs dirs) querylib(WITH_XRENDER "pkg-config" xcb-render libs dirs) querylib(WITH_XRM "pkg-config" xcb-xrm libs dirs) querylib(WITH_XSYNC "pkg-config" xcb-sync libs dirs) +querylib(WITH_XCURSOR "pkg-config" xcb-cursor libs dirs) diff --git a/cmake/05-summary.cmake b/cmake/05-summary.cmake index b5c3f58b..0e3924c9 100644 --- a/cmake/05-summary.cmake +++ b/cmake/05-summary.cmake @@ -28,6 +28,7 @@ colored_option(" xcb-sync" WITH_XSYNC) colored_option(" xcb-composite" WITH_XCOMPOSITE) colored_option(" xcb-xkb" WITH_XKB) colored_option(" xcb-xrm" WITH_XRM) +colored_option(" xcb-cursor" WITH_XCURSOR) if(CMAKE_BUILD_TYPE_UPPER MATCHES DEBUG) message(STATUS " Debug options:") diff --git a/contrib/polybar-git.aur/PKGBUILD b/contrib/polybar-git.aur/PKGBUILD index 02e004f8..7cc43c9f 100644 --- a/contrib/polybar-git.aur/PKGBUILD +++ b/contrib/polybar-git.aur/PKGBUILD @@ -3,12 +3,12 @@ _pkgname=polybar pkgname="${_pkgname}-git" pkgver=3.0.5 -pkgrel=3 +pkgrel=4 pkgdesc="A fast and easy-to-use status bar" arch=("i686" "x86_64") url="https://github.com/jaagr/polybar" license=("MIT") -depends=("cairo" "xcb-util-image" "xcb-util-wm" "xcb-util-xrm") +depends=("cairo" "xcb-util-image" "xcb-util-wm" "xcb-util-xrm" "xcb-util-cursor") optdepends=("alsa-lib: volume module support" "libmpdclient: mpd module support" "wireless_tools: network module support" diff --git a/doc/config.cmake b/doc/config.cmake index 37930853..462fc93f 100644 --- a/doc/config.cmake +++ b/doc/config.cmake @@ -66,6 +66,9 @@ tray-padding = 2 ;scroll-up = i3wm-wsnext ;scroll-down = i3wm-wsprev +cursor-click = pointer +cursor-scroll = ns-resize + [module/xwindow] type = internal/xwindow label = %title:0:30:...% diff --git a/include/components/bar.hpp b/include/components/bar.hpp index 616ecdcc..8e5d1762 100644 --- a/include/components/bar.hpp +++ b/include/components/bar.hpp @@ -26,9 +26,13 @@ class tray_manager; // }}} class bar : public xpp::event::sink, + evt::leave_notify, evt::motion_notify, evt::destroy_notify, evt::client_message>, public signal_receiver { + signals::ui::shade_window, signals::ui::unshade_window, signals::ui::dim_window +#if WITH_XCURSOR + , signals::ui::cursor_change +#endif + > { public: using make_type = unique_ptr; static make_type make(bool only_initialize_values = false); @@ -56,6 +60,7 @@ class bar : public xpp::event::sink actions{}; bool dimmed{false}; diff --git a/include/events/signal.hpp b/include/events/signal.hpp index b9fc4250..986a6125 100644 --- a/include/events/signal.hpp +++ b/include/events/signal.hpp @@ -102,6 +102,9 @@ namespace signals { struct button_press : public detail::value_signal { using base_type::base_type; }; + struct cursor_change : public detail::value_signal { + using base_type::base_type; + }; struct visibility_change : public detail::value_signal { using base_type::base_type; }; diff --git a/include/events/signal_fwd.hpp b/include/events/signal_fwd.hpp index a2ffb911..f7da2f89 100644 --- a/include/events/signal_fwd.hpp +++ b/include/events/signal_fwd.hpp @@ -32,6 +32,7 @@ namespace signals { struct changed; struct tick; struct button_press; + struct cursor_change; struct visibility_change; struct dim_window; struct shade_window; diff --git a/include/settings.hpp.cmake b/include/settings.hpp.cmake index 1f21b2b7..00d31897 100644 --- a/include/settings.hpp.cmake +++ b/include/settings.hpp.cmake @@ -30,6 +30,7 @@ #cmakedefine01 WITH_XCOMPOSITE #cmakedefine01 WITH_XKB #cmakedefine01 WITH_XRM +#cmakedefine01 WITH_XCURSOR #if WITH_XRANDR #cmakedefine01 WITH_XRANDR_MONITORS @@ -106,7 +107,7 @@ const auto print_build_info = [](bool extended = false) { (ENABLE_NETWORK ? '+' : '-')); if (extended) { printf("\n"); - printf("X extensions: %crandr (%cmonitors) %crender %cdamage %csync %ccomposite %cxkb %cxrm\n", + printf("X extensions: %crandr (%cmonitors) %crender %cdamage %csync %ccomposite %cxkb %cxrm %cxcursor\n", (WITH_XRANDR ? '+' : '-'), (WITH_XRANDR_MONITORS ? '+' : '-'), (WITH_XRENDER ? '+' : '-'), @@ -114,7 +115,8 @@ const auto print_build_info = [](bool extended = false) { (WITH_XSYNC ? '+' : '-'), (WITH_XCOMPOSITE ? '+' : '-'), (WITH_XKB ? '+' : '-'), - (WITH_XRM ? '+' : '-')); + (WITH_XRM ? '+' : '-'), + (WITH_XCURSOR ? '+' : '-')); printf("\n"); printf("Build type: @CMAKE_BUILD_TYPE@\n"); printf("Compiler: @CMAKE_CXX_COMPILER@\n"); diff --git a/include/x11/cursor.hpp b/include/x11/cursor.hpp new file mode 100644 index 00000000..514aa1eb --- /dev/null +++ b/include/x11/cursor.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "settings.hpp" + +#if not WITH_XCURSOR +#error "Not built with support for xcb-cursor..." +#endif + +#include + +#include "common.hpp" +#include "x11/connection.hpp" +#include "utils/string.hpp" + +POLYBAR_NS + +namespace cursor_util { + static const map> cursors = { + {"pointer", {"pointing_hand", "pointer", "hand", "hand1", "hand2", "e29285e634086352946a0e7090d73106", "9d800788f1b08800ae810202380a0822"}}, + {"default", {"left_ptr", "arrow", "dnd-none", "op_left_arrow"}}, + {"ns-resize", {"size_ver", "sb_v_double_arrow", "v_double_arrow", "n-resize", "s-resize", "col-resize", "top_side", "bottom_side", "base_arrow_up", "base_arrow_down", "based_arrow_down", "based_arrow_up", "00008160000006810000408080010102"}} + }; + bool valid(string name); + bool set_cursor(xcb_connection_t *c, xcb_screen_t *screen, xcb_window_t w, string name); +} + +POLYBAR_NS_END diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 04a8a490..d2ef77fd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -49,6 +49,9 @@ endif() if(NOT WITH_XRM) list(REMOVE_ITEM files x11/xresources.cpp) endif() +if(NOT WITH_XCURSOR) + list(REMOVE_ITEM files x11/cursor.cpp) +endif() # }}} diff --git a/src/components/bar.cpp b/src/components/bar.cpp index 4807f8f6..6e7dfbe0 100644 --- a/src/components/bar.cpp +++ b/src/components/bar.cpp @@ -21,6 +21,10 @@ #include "x11/icccm.hpp" #include "x11/tray_manager.hpp" +#if WITH_XCURSOR +#include "x11/cursor.hpp" +#endif + #if ENABLE_I3 #include "utils/i3.hpp" #endif @@ -126,6 +130,19 @@ bar::bar(connection& conn, signal_emitter& emitter, const config& config, const m_opts.dimvalue = m_conf.get(bs, "dim-value", 1.0); m_opts.dimvalue = math_util::cap(m_opts.dimvalue, 0.0, 1.0); + m_opts.cursor_click = m_conf.get(bs, "cursor-click", ""s); + m_opts.cursor_scroll = m_conf.get(bs, "cursor-scroll", ""s); +#if WITH_XCURSOR + if (!m_opts.cursor_click.empty() && !cursor_util::valid(m_opts.cursor_click)) { + m_log.warn("Ignoring unsupported cursor-click option '%s'", m_opts.cursor_click); + m_opts.cursor_click.clear(); + } + if (!m_opts.cursor_scroll.empty() && !cursor_util::valid(m_opts.cursor_scroll)) { + m_log.warn("Ignoring unsupported cursor-scroll option '%s'", m_opts.cursor_scroll); + m_opts.cursor_scroll.clear(); + } +#endif + // Build WM_NAME m_opts.wmname = m_conf.get(bs, "wm-name", "polybar-" + bs.substr(4) + "_" + m_opts.monitor->name); m_opts.wmname = string_util::replace(m_opts.wmname, " ", "-"); @@ -554,7 +571,6 @@ void bar::handle(const evt::enter_notify&) { } #endif #endif - if (m_opts.dimmed) { m_taskqueue->defer_unique("window-dim", 25ms, [&](size_t) { m_opts.dimmed = false; @@ -580,7 +596,6 @@ void bar::handle(const evt::leave_notify&) { } #endif #endif - if (!m_opts.dimmed) { m_taskqueue->defer_unique("window-dim", 3s, [&](size_t) { m_opts.dimmed = true; @@ -589,6 +604,69 @@ void bar::handle(const evt::leave_notify&) { } } +/** + * Event handler for XCB_MOTION_NOTIFY events + * + * Used to change the cursor depending on the module + */ +void bar::handle(const evt::motion_notify& evt) { + m_log.trace("bar: Detected motion: %i at pos(%i, %i)", evt->detail, evt->event_x, evt->event_y); +#if WITH_XCURSOR + m_motion_pos = evt->event_x; + // scroll cursor is less important than click cursor, so we shouldn't return until we are sure there is no click action + bool found_scroll = false; + const auto find_click_area = [&](const action& action) { + if (!m_opts.cursor_click.empty() && !(action.button == mousebtn::SCROLL_UP || action.button == mousebtn::SCROLL_DOWN || action.button == mousebtn::NONE)) { + if (!string_util::compare(m_opts.cursor, m_opts.cursor_click)) { + m_opts.cursor = m_opts.cursor_click; + m_sig.emit(cursor_change{string{m_opts.cursor}}); + } + return true; + } else if (!m_opts.cursor_scroll.empty() && (action.button == mousebtn::SCROLL_UP || action.button == mousebtn::SCROLL_DOWN)) { + if (!found_scroll) { + found_scroll = true; + } + } + return false; + }; + + for (auto&& action : m_renderer->actions()) { + if (action.test(m_motion_pos)) { + m_log.trace("Found matching input area"); + if(find_click_area(action)) + return; + } + } + if(found_scroll) { + if (!string_util::compare(m_opts.cursor, m_opts.cursor_scroll)) { + m_opts.cursor = m_opts.cursor_scroll; + m_sig.emit(cursor_change{string{m_opts.cursor}}); + } + return; + } + for (auto&& action : m_opts.actions) { + if (!action.command.empty()) { + m_log.trace("Found matching fallback handler"); + if(find_click_area(action)) + return; + } + } + if(found_scroll) { + if (!string_util::compare(m_opts.cursor, m_opts.cursor_scroll)) { + m_opts.cursor = m_opts.cursor_scroll; + m_sig.emit(cursor_change{string{m_opts.cursor}}); + } + return; + } + if (!string_util::compare(m_opts.cursor, "default")) { + m_log.trace("No matching cursor area found"); + m_opts.cursor = "default"; + m_sig.emit(cursor_change{string{m_opts.cursor}}); + return; + } +#endif +} + /** * Event handler for XCB_BUTTON_PRESS events * @@ -703,6 +781,9 @@ bool bar::on(const signals::eventqueue::start&) { if (m_opts.dimvalue != 1.0) { m_connection.ensure_event_mask(m_opts.window, XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW); } + if (!m_opts.cursor_click.empty() || !m_opts.cursor_scroll.empty() ) { + m_connection.ensure_event_mask(m_opts.window, XCB_EVENT_MASK_POINTER_MOTION); + } m_log.info("Bar window: %s", m_connection.id(m_opts.window)); restack_window(); @@ -841,4 +922,14 @@ bool bar::on(const signals::ui::dim_window& sig) { return false; } +#if WITH_XCURSOR +bool bar::on(const signals::ui::cursor_change& sig) { + if(!cursor_util::set_cursor(m_connection, m_connection.screen(), m_opts.window, sig.cast())) { + m_log.warn("Failed to create cursor context"); + } + m_connection.flush(); + return false; +} +#endif + POLYBAR_NS_END diff --git a/src/x11/cursor.cpp b/src/x11/cursor.cpp new file mode 100644 index 00000000..8ecdcb1d --- /dev/null +++ b/src/x11/cursor.cpp @@ -0,0 +1,29 @@ +#include "x11/cursor.hpp" + +POLYBAR_NS + +namespace cursor_util { + bool valid(string name) { + if (cursors.find(name) != cursors.end()) + return true; + return false; + } + + bool set_cursor(xcb_connection_t *c, xcb_screen_t *screen, xcb_window_t w, string name) { + xcb_cursor_t cursor = XCB_CURSOR_NONE; + xcb_cursor_context_t *ctx; + + if (xcb_cursor_context_new(c, screen, &ctx) < 0) { + return false; + } + for (auto&& cursor_name : cursors.at(name)) { + cursor = xcb_cursor_load_cursor(ctx, cursor_name.c_str()); + if (cursor != XCB_CURSOR_NONE) + break; + } + xcb_change_window_attributes(c, w, XCB_CW_CURSOR, &cursor); + xcb_cursor_context_free(ctx); + return true; + } +} +POLYBAR_NS_END