diff --git a/.gitignore b/.gitignore index d602b205..6b7a8ba6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,9 @@ lua/libimlib2.c *.a /config.h +# Compiler cache +.cache + # Ignore vscode stuff .vscode *.code-workspace diff --git a/cmake/ConkyBuildOptions.cmake b/cmake/ConkyBuildOptions.cmake index 783022f9..3f7ebf30 100644 --- a/cmake/ConkyBuildOptions.cmake +++ b/cmake/ConkyBuildOptions.cmake @@ -169,6 +169,7 @@ if(BUILD_X11) option(BUILD_XFT "Build Xft (freetype fonts) support" true) option(BUILD_IMLIB2 "Enable Imlib2 support" true) option(BUILD_XSHAPE "Enable Xshape support" true) + option(BUILD_MOUSE_EVENTS "Enable mouse event support" true) else(BUILD_X11) set(OWN_WINDOW false CACHE BOOL "Enable own_window support" FORCE) set(BUILD_XDAMAGE false CACHE BOOL "Build Xdamage support" FORCE) @@ -177,6 +178,7 @@ else(BUILD_X11) set(BUILD_XFT false CACHE BOOL "Build Xft (freetype fonts) support" FORCE) set(BUILD_IMLIB2 false CACHE BOOL "Enable Imlib2 support" FORCE) set(BUILD_XSHAPE false CACHE BOOL "Enable Xshape support" FORCE) + set(BUILD_MOUSE_EVENTS false CACHE BOOL "Enable mouse event support" FORCE) set(BUILD_NVIDIA false) endif(BUILD_X11) diff --git a/cmake/config.h.in b/cmake/config.h.in index 2d4f6ed4..098d3dc4 100644 --- a/cmake/config.h.in +++ b/cmake/config.h.in @@ -48,6 +48,8 @@ #cmakedefine OWN_WINDOW 1 +#cmakedefine MOUSE_EVENTS 1 + #cmakedefine BUILD_XDAMAGE 1 #cmakedefine BUILD_XINERAMA 1 diff --git a/doc/config_settings.yaml b/doc/config_settings.yaml index ca3ffa1e..d79760b5 100644 --- a/doc/config_settings.yaml +++ b/doc/config_settings.yaml @@ -218,6 +218,16 @@ values: Imlib2 image cache size, in bytes. Increase this value if you use $image lots. Set to 0 to disable the image cache. default: 4194304 + - name: lua_mouse_hook + desc: |- + This function, if defined, will be called by Conky upon receiving mouse + events from X. Requires X support. A table containing event information + will be passed to this function as the first argument. Use this hook for + detecting mouse input and acting on it. Conky puts 'conky_' in front of + function_name to prevent accidental calls to the wrong function unless + you place 'conky_' in front of it yourself. + args: + - function_name - name: lowercase desc: Boolean value, if true, text is rendered in lower case. - name: lua_draw_hook_post diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6db083e7..d8a6d45f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -236,6 +236,11 @@ if(BUILD_X11) find_package(Xinerama REQUIRED) set(conky_libs ${conky_libs} ${Xinerama_LIBRARIES}) endif(BUILD_XINERAMA) + + if(BUILD_MOUSE_EVENTS) + set(mouse_events mouse-events.cc mouse-events.h) + set(optional_sources ${optional_sources} ${mouse_events}) + endif(BUILD_MOUSE_EVENTS) endif(BUILD_X11) if(BUILD_GUI) diff --git a/src/display-x11.cc b/src/display-x11.cc index 175fbdaa..58fdc58c 100644 --- a/src/display-x11.cc +++ b/src/display-x11.cc @@ -38,9 +38,13 @@ #ifdef BUILD_XDAMAGE #include #endif +#include "fonts.h" #ifdef BUILD_IMLIB2 #include "imlib2.h" #endif /* BUILD_IMLIB2 */ +#ifdef BUILD_MOUSE_EVENTS +#include "mouse-events.h" +#endif #endif /* BUILD_X11 */ #include @@ -52,9 +56,6 @@ #include "gui.h" #include "llua.h" #include "x11.h" -#ifdef BUILD_X11 -#include "fonts.h" -#endif /* TODO: cleanup global namespace */ #ifdef BUILD_X11 @@ -62,7 +63,7 @@ // TODO: cleanup externs (move to conky.h ?) #ifdef OWN_WINDOW extern int fixed_size, fixed_pos; -#endif +#endif /* OWN_WINDOW */ extern int text_start_x, text_start_y; /* text start position in window */ extern int text_offset_x, text_offset_y; /* offset for start position */ extern int text_width, @@ -364,6 +365,8 @@ bool display_output_x11::main_loop_wait(double t) { /* handle X events */ while (XPending(display) != 0) { XEvent ev; + /* indicates whether processed event was consumed */ + bool consumed = false; XNextEvent(display, &ev); switch (ev.type) { @@ -449,6 +452,13 @@ bool display_output_x11::main_loop_wait(double t) { break; case ButtonPress: +#ifdef BUILD_MOUSE_EVENTS + if (ev.xbutton.button == 4 || ev.xbutton.button == 5) { + consumed = llua_mouse_hook(mouse_scroll_event(&ev.xbutton)); + } else { + consumed = llua_mouse_hook(mouse_press_event(&ev.xbutton)); + } +#endif /* BUILD_MOUSE_EVENTS */ if (own_window.get(*state)) { /* if an ordinary window with decorations */ if ((own_window_type.get(*state) == TYPE_NORMAL && @@ -457,18 +467,26 @@ bool display_output_x11::main_loop_wait(double t) { /* allow conky to hold input focus. */ break; } - /* forward the click to the desktop window */ XUngrabPointer(display, ev.xbutton.time); - ev.xbutton.window = window.desktop; - ev.xbutton.x = ev.xbutton.x_root; - ev.xbutton.y = ev.xbutton.y_root; - XSendEvent(display, ev.xbutton.window, False, ButtonPressMask, &ev); - XSetInputFocus(display, ev.xbutton.window, RevertToParent, - ev.xbutton.time); + if (!consumed) { + /* forward the click to the desktop window */ + ev.xbutton.window = window.desktop; + ev.xbutton.x = ev.xbutton.x_root; + ev.xbutton.y = ev.xbutton.y_root; + XSendEvent(display, ev.xbutton.window, False, ButtonPressMask, &ev); + XSetInputFocus(display, ev.xbutton.window, RevertToParent, + ev.xbutton.time); + } } break; case ButtonRelease: +#ifdef BUILD_MOUSE_EVENTS + /* don't report scrollwheel release events */ + if (ev.xbutton.button != Button4 && ev.xbutton.button != Button5) { + llua_mouse_hook(mouse_release_event(&ev.xbutton)); + } +#endif /* BUILD_MOUSE_EVENTS */ if (own_window.get(*state)) { /* if an ordinary window with decorations */ if ((own_window_type.get(*state) == TYPE_NORMAL) && @@ -483,7 +501,21 @@ bool display_output_x11::main_loop_wait(double t) { XSendEvent(display, ev.xbutton.window, False, ButtonReleaseMask, &ev); } break; - +#ifdef BUILD_MOUSE_EVENTS + /* + windows below are notified for the following events as well; + can't forward the event without filtering XQueryTree output. + */ + case MotionNotify: + llua_mouse_hook(mouse_move_event(&ev.xmotion)); + break; + case EnterNotify: + llua_mouse_hook(mouse_enter_event(&ev.xcrossing)); + break; + case LeaveNotify: + llua_mouse_hook(mouse_leave_event(&ev.xcrossing)); + break; +#endif /* BUILD_MOUSE_EVENTS */ #endif default: diff --git a/src/llua.cc b/src/llua.cc index 404ecf22..ea845cf7 100644 --- a/src/llua.cc +++ b/src/llua.cc @@ -95,6 +95,7 @@ class lua_load_setting : public conky::simple_config_setting { }; lua_load_setting lua_load; + conky::simple_config_setting lua_startup_hook("lua_startup_hook", std::string(), true); conky::simple_config_setting lua_shutdown_hook("lua_shutdown_hook", @@ -106,6 +107,12 @@ conky::simple_config_setting lua_draw_hook_pre("lua_draw_hook_pre", true); conky::simple_config_setting lua_draw_hook_post( "lua_draw_hook_post", std::string(), true); + +#ifdef BUILD_MOUSE_EVENTS +conky::simple_config_setting lua_mouse_hook("lua_mouse_hook", + std::string(), true); +#endif /* BUILD_MOUSE_EVENTS */ + #endif } // namespace @@ -483,6 +490,37 @@ void llua_draw_post_hook() { llua_do_call(lua_draw_hook_post.get(*state).c_str(), 0); } +#ifdef BUILD_MOUSE_EVENTS +template +bool llua_mouse_hook(const EventT &ev) { + if ((lua_L == nullptr) || lua_mouse_hook.get(*state).empty()) { + return false; + } + const std::string func = "conky_" + lua_mouse_hook.get(*state); + lua_getglobal(lua_L, func.c_str()); + + ev.push_lua_table(lua_L); + + bool result = false; + if (lua_pcall(lua_L, 1, 1, 0) != 0) { + NORM_ERR("llua_mouse_hook: function %s execution failed: %s", func.c_str(), + lua_tostring(lua_L, -1)); + lua_pop(lua_L, 1); + } else { + result = lua_toboolean(lua_L, -1); + lua_pop(lua_L, 1); + } + + return result; +} + +template bool llua_mouse_hook(const mouse_scroll_event &ev); +template bool llua_mouse_hook(const mouse_button_event &ev); +template bool llua_mouse_hook(const mouse_move_event &ev); +template bool llua_mouse_hook( + const mouse_crossing_event &ev); +#endif /* BUILD_MOUSE_EVENTS */ + void llua_set_userdata(const char *key, const char *type, void *value) { tolua_pushusertype(lua_L, value, type); lua_setfield(lua_L, -2, key); diff --git a/src/llua.h b/src/llua.h index fa8d1bd7..150ad8d4 100644 --- a/src/llua.h +++ b/src/llua.h @@ -32,6 +32,13 @@ extern "C" { #include +#ifdef BUILD_X11 +#include "x11.h" +#ifdef BUILD_MOUSE_EVENTS +#include "mouse-events.h" +#endif /* BUILD_MOUSE_EVENTS */ +#endif /* BUILD_X11 */ + #define LUAPREFIX "conky_" #ifdef HAVE_SYS_INOTIFY_H @@ -46,6 +53,15 @@ void llua_shutdown_hook(void); void llua_draw_pre_hook(void); void llua_draw_post_hook(void); +#ifdef BUILD_MOUSE_EVENTS +/** + Takes a mouse_event as argument. + Returns true if event was properly consumed, false otherwise. + */ +template +bool llua_mouse_hook(const EventT &ev); +#endif /* BUILD_MOUSE_EVENTS */ + void llua_setup_window_table(int text_start_x, int text_start_y, int text_width, int text_height); void llua_update_window_table(int text_start_x, int text_start_y, diff --git a/src/main.cc b/src/main.cc index 29537f20..82458077 100644 --- a/src/main.cc +++ b/src/main.cc @@ -178,6 +178,9 @@ static void print_version() { #ifdef OWN_WINDOW << _(" * Own window\n") #endif +#ifdef BUILD_MOUSE_EVENTS + << _(" * Mouse evenets\n") +#endif #endif /* BUILD_X11 */ #if defined BUILD_AUDACIOUS || defined BUILD_CMUS || defined BUILD_MPD || \ defined BUILD_MOC || defined BUILD_XMMS2 diff --git a/src/mouse-events.cc b/src/mouse-events.cc new file mode 100644 index 00000000..7a647787 --- /dev/null +++ b/src/mouse-events.cc @@ -0,0 +1,180 @@ +/* + * mouse_events.cc: conky support for mouse events + * + * Copyright (C) 2020 Tin Svagelj tin.svagelj@live.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "mouse-events.h" + +#include +#include +#include "X11/Xlib.h" + +std::string event_type_to_str(int type) { + switch (type) { + case MOUSE_DOWN: + return "button_down"; + case MOUSE_UP: + return "button_up"; + case MOUSE_SCROLL: + return "mouse_scroll"; + case MOUSE_MOVE: + return "mouse_move"; + case AREA_ENTER: + return "mouse_enter"; + case AREA_LEAVE: + return "mouse_leave"; + default: + return "err"; + } +} + +/* Lua helper functions */ +template +void push_table_value(lua_State *L, std::string key, T value); + +void push_table_value(lua_State *L, std::string key, std::string value) { + lua_pushstring(L, key.c_str()); + lua_pushstring(L, value.c_str()); + lua_settable(L, -3); +} + +void push_table_value(lua_State *L, std::string key, int value) { + lua_pushstring(L, key.c_str()); + lua_pushinteger(L, value); + lua_settable(L, -3); +} + +void push_table_value(lua_State *L, std::string key, uint value) { + lua_pushstring(L, key.c_str()); + lua_pushinteger(L, value); + lua_settable(L, -3); +} + +void push_table_value(lua_State *L, std::string key, uint64_t value) { + lua_pushstring(L, key.c_str()); + lua_pushinteger(L, value); + lua_settable(L, -3); +} + +void push_table_value(lua_State *L, std::string key, bool value) { + lua_pushstring(L, key.c_str()); + lua_pushboolean(L, value); + lua_settable(L, -3); +} + +void push_table_value(lua_State *L, std::string key, float value) { + lua_pushstring(L, key.c_str()); + lua_pushnumber(L, value); + lua_settable(L, -3); +} + +void push_table_value(lua_State *L, std::string key, double value) { + lua_pushstring(L, key.c_str()); + lua_pushnumber(L, value); + lua_settable(L, -3); +} + +template +void push_bitset(lua_State *L, std::bitset it, + std::array labels) { + lua_newtable(L); + for (size_t i = 0; i < N; i++) push_table_value(L, labels[i], it.test(i)); +} + +const std::array mod_names = { + {"shift", "lock", "control", "mod1", "num_lock", "mod3", "mod4", "mod5", + "mouse_left", "mouse_right", "mouse_middle", "scroll_up", "scroll_down"}}; + +void push_mods(lua_State *L, std::bitset<13> mods) { + lua_pushstring(L, "mods"); + push_bitset(L, mods, mod_names); + lua_settable(L, -3); +} + +/* Class methods */ + +void mouse_event::push_lua_table(lua_State *L) const { + lua_newtable(L); + push_table_value(L, "type", event_type_to_str(this->type)); + push_lua_data(L); +} + +void mouse_positioned_event::push_lua_data(lua_State *L) const { + push_table_value(L, "x", this->x); + push_table_value(L, "y", this->y); + push_table_value(L, "x_abs", this->x_abs); + push_table_value(L, "y_abs", this->y_abs); + push_table_value(L, "time", this->time); +} + +mouse_move_event::mouse_move_event(XMotionEvent *ev) { + this->type = MOUSE_MOVE; + this->x = ev->x; + this->y = ev->y; + this->x_abs = ev->x_root; + this->y_abs = ev->y_root; + this->time = ev->time; +} + +void mouse_move_event::push_lua_data(lua_State *L) const { + mouse_positioned_event::push_lua_data(L); + push_mods(L, this->mods); +} + +mouse_scroll_event::mouse_scroll_event(XButtonEvent *ev) { + this->type = MOUSE_SCROLL; + this->x = ev->x; + this->y = ev->y; + this->x_abs = ev->x_root; + this->y_abs = ev->y_root; + this->time = ev->time; + this->mods = ev->state; + this->up = ev->button == 4; +} + +void mouse_scroll_event::push_lua_data(lua_State *L) const { + mouse_positioned_event::push_lua_data(L); + push_table_value(L, "direction", std::string(this->up ? "up" : "down")); + push_mods(L, this->mods); +} + +mouse_button_event::mouse_button_event(XButtonEvent *ev) { + this->type = ev->type == ButtonPress ? MOUSE_DOWN : MOUSE_UP; + this->x = ev->x; + this->y = ev->y; + this->x_abs = ev->x_root; + this->y_abs = ev->y_root; + this->time = ev->time; + this->mods = ev->state; + this->button = ev->button; +} + +void mouse_button_event::push_lua_data(lua_State *L) const { + mouse_positioned_event::push_lua_data(L); + push_table_value(L, "button", this->button); + push_mods(L, this->mods); +} + +mouse_crossing_event::mouse_crossing_event(XCrossingEvent *ev) { + this->type = ev->type == EnterNotify ? AREA_ENTER : AREA_LEAVE; + this->x = ev->x; + this->y = ev->y; + this->x_abs = ev->x_root; + this->y_abs = ev->y_root; + this->time = ev->time; +} diff --git a/src/mouse-events.h b/src/mouse-events.h new file mode 100644 index 00000000..76dc35bd --- /dev/null +++ b/src/mouse-events.h @@ -0,0 +1,97 @@ +/* + * mouse_events.h: conky support for mouse events + * + * Copyright (C) 2020 Tin Svagelj tin.svagelj@live.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef MOUSE_EVENTS_H +#define MOUSE_EVENTS_H + +#include +#include + +extern "C" { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wvariadic-macros" +#include +#pragma GCC diagnostic pop +#include +} + +enum mouse_event_type { + MOUSE_DOWN = 0, + MOUSE_UP = 1, + MOUSE_SCROLL = 2, + MOUSE_MOVE = 3, + AREA_ENTER = 4, + AREA_LEAVE = 5, + MOUSE_EVENT_COUNT = 6, +}; + +struct mouse_event { + mouse_event_type type; + uint64_t time = 0L; // event time + + void push_lua_table(lua_State *L) const; + + virtual void push_lua_data(lua_State *L) const = 0; +}; + +struct mouse_positioned_event : public mouse_event { + int x = 0, y = 0; // positions relative to window + int x_abs = 0, y_abs = 0; // positions relative to root + + void push_lua_data(lua_State *L) const; +}; + +struct mouse_move_event : public mouse_positioned_event { + std::bitset<13> mods; // held buttons and modifiers (ctrl, shift, ...) + + explicit mouse_move_event(XMotionEvent *ev); + + void push_lua_data(lua_State *L) const; +}; + +struct mouse_scroll_event : public mouse_positioned_event { + std::bitset<13> mods; // held buttons and modifiers (ctrl, shift, ...) + bool up = false; + + explicit mouse_scroll_event(XButtonEvent *ev); + + void push_lua_data(lua_State *L) const; +}; + +struct mouse_button_event : public mouse_positioned_event { + std::bitset<13> mods; // held buttons and modifiers (ctrl, shift, ...) + uint button = 0; + + explicit mouse_button_event(XButtonEvent *ev); + + void push_lua_data(lua_State *L) const; +}; + +typedef struct mouse_button_event mouse_press_event; +typedef struct mouse_button_event mouse_release_event; + +struct mouse_crossing_event : public mouse_positioned_event { + explicit mouse_crossing_event(XCrossingEvent *ev); +}; + +typedef struct mouse_crossing_event mouse_enter_event; +typedef struct mouse_crossing_event mouse_leave_event; + +#endif /* MOUSE_EVENTS_H */ diff --git a/src/x11.cc b/src/x11.cc index 83930d4d..34c82419 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -27,6 +27,7 @@ * */ +#include #include "common.h" #include "config.h" #include "conky.h" @@ -829,14 +830,19 @@ void x11_init_window(lua::state &l __attribute__((unused)), bool own) { XFlush(display); - XSelectInput(display, window.window, - ExposureMask | PropertyChangeMask + long input_mask = ExposureMask | PropertyChangeMask; #ifdef OWN_WINDOW - | (own_window.get(l) ? (StructureNotifyMask | - ButtonPressMask | ButtonReleaseMask) - : 0) -#endif - ); + if (own_window.get(l)) { + input_mask |= StructureNotifyMask | ButtonPressMask | ButtonReleaseMask; + } +#endif /* OWN_WINDOW */ +#ifdef BUILD_MOUSE_EVENTS + /* it's not recommended to add event masks to special windows in X; causes a crash */ + if (own_window_type.get(l) != TYPE_DESKTOP) { + input_mask |= ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask; + } +#endif /* BUILD_MOUSE_EVENTS */ + XSelectInput(display, window.window, input_mask); window_created = 1; DBGP("leave x11_init_window()");