Added mouse events to conky (#955)

* Add mouse events.

Signed-off-by: Tin Svagelj <tin.svagelj@live.com>

* Rename MOUSE_EVENTS flag to BUILD_MOUSE_EVENTS.

Signed-off-by: Tin Svagelj <tin.svagelj@live.com>

* Update NORM_ERR func argument from std::string to char*

Because func was previously char* I forgot to update NORM_ERR function
argument to `func.c_str()` not that it's std::string.

Previously func was pointing to std::string memory that was freed at
assignment.

Signed-off-by: Tin Svagelj <tin.svagelj@live.com>

Signed-off-by: Tin Svagelj <tin.svagelj@live.com>
This commit is contained in:
Tin Švagelj 2023-01-01 18:39:57 +01:00 committed by GitHub
parent 31d247a80d
commit 7fbdfbd4d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 413 additions and 19 deletions

3
.gitignore vendored
View File

@ -20,6 +20,9 @@ lua/libimlib2.c
*.a
/config.h
# Compiler cache
.cache
# Ignore vscode stuff
.vscode
*.code-workspace

View File

@ -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)

View File

@ -48,6 +48,8 @@
#cmakedefine OWN_WINDOW 1
#cmakedefine MOUSE_EVENTS 1
#cmakedefine BUILD_XDAMAGE 1
#cmakedefine BUILD_XINERAMA 1

View File

@ -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

View File

@ -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)

View File

@ -38,9 +38,13 @@
#ifdef BUILD_XDAMAGE
#include <X11/extensions/Xdamage.h>
#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 <iostream>
@ -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:

View File

@ -95,6 +95,7 @@ class lua_load_setting : public conky::simple_config_setting<std::string> {
};
lua_load_setting lua_load;
conky::simple_config_setting<std::string> lua_startup_hook("lua_startup_hook",
std::string(), true);
conky::simple_config_setting<std::string> lua_shutdown_hook("lua_shutdown_hook",
@ -106,6 +107,12 @@ conky::simple_config_setting<std::string> lua_draw_hook_pre("lua_draw_hook_pre",
true);
conky::simple_config_setting<std::string> lua_draw_hook_post(
"lua_draw_hook_post", std::string(), true);
#ifdef BUILD_MOUSE_EVENTS
conky::simple_config_setting<std::string> 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 <typename EventT>
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<mouse_scroll_event>(const mouse_scroll_event &ev);
template bool llua_mouse_hook<mouse_button_event>(const mouse_button_event &ev);
template bool llua_mouse_hook<mouse_move_event>(const mouse_move_event &ev);
template bool llua_mouse_hook<mouse_crossing_event>(
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);

View File

@ -32,6 +32,13 @@ extern "C" {
#include <config.h>
#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 <typename EventT>
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,

View File

@ -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

180
src/mouse-events.cc Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
*/
#include "mouse-events.h"
#include <array>
#include <string>
#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 <typename T>
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 <size_t N>
void push_bitset(lua_State *L, std::bitset<N> it,
std::array<std::string, N> labels) {
lua_newtable(L);
for (size_t i = 0; i < N; i++) push_table_value(L, labels[i], it.test(i));
}
const std::array<std::string, 13> 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;
}

97
src/mouse-events.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
*/
#ifndef MOUSE_EVENTS_H
#define MOUSE_EVENTS_H
#include <bitset>
#include <cstdint>
extern "C" {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wvariadic-macros"
#include <X11/Xlib.h>
#pragma GCC diagnostic pop
#include <lua.h>
}
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 */

View File

@ -27,6 +27,7 @@
*
*/
#include <X11/X.h>
#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()");