Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Change cursors over clickable/scrollable areas #727

Merged
merged 9 commits into from
Sep 17, 2017
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ addons:
- libxcb-randr0-dev
- libxcb-util0-dev
- libxcb-xkb-dev
- libxcb-cursor-dev
- libxcb1-dev
- python-xcbgen
- xcb-proto
Expand Down
2 changes: 2 additions & 0 deletions cmake/02-opts.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions cmake/03-libs.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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)
1 change: 1 addition & 0 deletions cmake/05-summary.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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:")
Expand Down
4 changes: 2 additions & 2 deletions contrib/polybar-git.aur/PKGBUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
15 changes: 13 additions & 2 deletions include/components/bar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ class tray_manager;
// }}}

class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::property_notify, evt::enter_notify,
evt::leave_notify, evt::destroy_notify, evt::client_message>,
evt::leave_notify, evt::motion_notify, evt::destroy_notify, evt::client_message>,
public signal_receiver<SIGN_PRIORITY_BAR, signals::eventqueue::start, signals::ui::tick,
signals::ui::shade_window, signals::ui::unshade_window, signals::ui::dim_window> {
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<bar>;
static make_type make(bool only_initialize_values = false);
Expand Down Expand Up @@ -56,6 +60,7 @@ class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::propert
void handle(const evt::destroy_notify& evt);
void handle(const evt::enter_notify& evt);
void handle(const evt::leave_notify& evt);
void handle(const evt::motion_notify& evt);
void handle(const evt::button_press& evt);
void handle(const evt::expose& evt);
void handle(const evt::property_notify& evt);
Expand All @@ -65,6 +70,9 @@ class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::propert
bool on(const signals::ui::shade_window&);
bool on(const signals::ui::tick&);
bool on(const signals::ui::dim_window&);
#if WITH_XCURSOR
bool on(const signals::ui::cursor_change&);
#endif

private:
connection& m_connection;
Expand All @@ -85,6 +93,9 @@ class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::propert

mousebtn m_buttonpress_btn{mousebtn::NONE};
int m_buttonpress_pos{0};
#if WITH_XCURSOR
int m_motion_pos{0};
#endif

event_timer m_buttonpress{0L, 5L};
event_timer m_doubleclick{0L, 150L};
Expand Down
4 changes: 4 additions & 0 deletions include/components/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ struct bar_settings {

bool override_redirect{false};

string cursor{};
string cursor_click{};
string cursor_scroll{};

vector<action> actions{};

bool dimmed{false};
Expand Down
3 changes: 3 additions & 0 deletions include/events/signal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ namespace signals {
struct button_press : public detail::value_signal<button_press, string> {
using base_type::base_type;
};
struct cursor_change : public detail::value_signal<cursor_change, string> {
using base_type::base_type;
};
struct visibility_change : public detail::value_signal<visibility_change, bool> {
using base_type::base_type;
};
Expand Down
1 change: 1 addition & 0 deletions include/events/signal_fwd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions include/settings.hpp.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#cmakedefine01 WITH_XCOMPOSITE
#cmakedefine01 WITH_XKB
#cmakedefine01 WITH_XRM
#cmakedefine01 WITH_XCURSOR

#if WITH_XRANDR
#cmakedefine01 WITH_XRANDR_MONITORS
Expand Down Expand Up @@ -106,15 +107,16 @@ 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 ? '+' : '-'),
(WITH_XDAMAGE ? '+' : '-'),
(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");
Expand Down
27 changes: 27 additions & 0 deletions include/x11/cursor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include "settings.hpp"

#if not WITH_XCURSOR
#error "Not built with support for xcb-cursor..."
#endif

#include <xcb/xcb_cursor.h>

#include "common.hpp"
#include "x11/connection.hpp"
#include "utils/string.hpp"

POLYBAR_NS

namespace cursor_util {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I understand this right that there are three types of cursors pointer, ns-resize and default and these vectors are just a list of fallback names if the xcb doesn't recognize a certain cursor name?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I have chosen three relevant cursors to be used (we could add more but IMO those are the ones which are most likely to be used). Their names are not standardized so we have to cycle through them until we find one that exists in the current theme.

static const map<string, vector<string>> 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
3 changes: 3 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()

# }}}

Expand Down
95 changes: 93 additions & 2 deletions src/components/bar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having pointer and ns-resize as default values would be a good idea

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also it would be useful to the user if polybar showed a warning, if the config uses an invalid cursor name.
We could maybe have a map that maps cursor names to name vectors. Like this we have all valid cursor names as well as their xcb names in one spot and set_cursor in cursor.cpp could just access the map instead of using a long if-elseif chain. This would also make it easier to, in the future, add new cursor names.

#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, " ", "-");
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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
*
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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
29 changes: 29 additions & 0 deletions src/x11/cursor.cpp
Original file line number Diff line number Diff line change
@@ -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