Fellow tmuxers,
at LAAAST! It is completed! ^^
There might be a few touch ups here and there that could be done but all
in all, my mouse overhauling effort has concluded with attached patch.
Changes:
- For panes not in xterm mouse tracking mode, 'emulate' wheel scrolling
by sending cursor up/down sequences like some other terminal emulators
do. Allows scrolling in less, man, bash history...
- Add bitmasks for mouse + SHIFT, ALT, CTRL
- Store timestamp for each mouse event
- Replace the last_mouse entry in the client structure with a tail queue
of last 10 mouse events. Allows implementing complex mouse click
'gestures' like 'kill window on CTRL + triple middle click within
800ms'. Propositions for syntax needed!
- Fix layout not being recalculated when switching windows via mouse wheel
- Improved debug logging of mouse events
Please review.
Oh and there's a bug with parsing (x,y) mouse coordinates with x>160,
probably platform specific types... For reference:
http://david.woodhou.se/mouse.c minimal implementation works, current
code gives bogus coords on linux/x86_64..
This is probably not what was intended, so make mouse-select-pane ignore
clicks in the status line.
Good catch. Fix is incorporated.
#regards|marcel C:
--- input-keys.c.orig 2011-12-21 21:56:08.901056926 +0100
+++ input-keys.c 2011-12-21 21:57:07.553245366 +0100
@@ -203,7 +203,7 @@ input_mouse(struct window_pane *wp, stru
{
char buf[10];
size_t len;
- int value;
+ int mode, i, lines;
if (wp->screen->mode & ALL_MOUSE_MODES) {
if (wp->screen->mode & MODE_MOUSE_UTF8) {
@@ -220,13 +220,29 @@ input_mouse(struct window_pane *wp, stru
buf[len++] = m->y + 33;
}
bufferevent_write(wp->event, buf, len);
- } else if ((m->b & MOUSE_BUTTON) != MOUSE_2) {
- value = options_get_number(&wp->window->options, "mode-mouse");
- if (value == 1 &&
+ return;
+ }
+ if ((m->b & MOUSE_BUTTON) != MOUSE_2) {
+ mode = options_get_number(&wp->window->options, "mode-mouse");
+ if (mode == 1 &&
window_pane_set_mode(wp, &window_copy_mode) == 0) {
window_copy_init_from_pane(wp);
if (wp->mode->mouse != NULL)
wp->mode->mouse(wp, NULL, m);
+ return;
}
}
+ /* Emulate mouse wheel scrolling. */
+ if (m->b & MOUSE_45) {
+ /*
+ * Wheel + shift: scroll 1 line
+ * Wheel + ctrl: scroll at triple speed
+ * */
+ lines = (m->b & MOUSE_SHIFT) ? 1 : MOUSE_SCROLL_EMU_LINES;
+ if (m->b & MOUSE_CTRL)
+ lines *= 3;
+
+ for (i=0; i < lines; i++)
+ input_key(wp, (m->b & MOUSE_BUTTON) == MOUSE_1 ? KEYC_UP : KEYC_DOWN);
+ }
}
--- server-client.c.orig 2011-12-21 21:56:08.979059839 +0100
+++ server-client.c 2011-12-21 21:57:02.135043206 +0100
@@ -26,6 +26,10 @@
#include "tmux.h"
+u_int server_client_mouse_time_diff(struct mouse_event *, struct mouse_event *);
+char* server_client_mouse_button_string(int);
+void server_client_prune_mouse_stack(struct client *);
+void server_client_handle_mouse_event(struct client *, struct mouse_event *);
void server_client_handle_key(int, struct mouse_event *, void *);
void server_client_repeat_timer(int, short, void *);
void server_client_check_exit(struct client *);
@@ -88,8 +92,7 @@ server_client_create(int fd)
c->prompt_buffer = NULL;
c->prompt_index = 0;
- c->last_mouse.b = MOUSE_UP;
- c->last_mouse.x = c->last_mouse.y = -1;
+ TAILQ_INIT(&c->mouse_events);
evtimer_set(&c->repeat_timer, server_client_repeat_timer, c);
@@ -261,6 +264,124 @@ server_client_status_timer(void)
}
}
+u_int
+server_client_mouse_time_diff(struct mouse_event *m_first, struct mouse_event *m_second)
+{
+ u_int diff_usec;
+
+ diff_usec = m_second->time.tv_usec - m_first->time.tv_usec;
+ diff_usec += (m_second->time.tv_sec - m_first->time.tv_sec) * 1000000;
+ return diff_usec;
+}
+
+char *
+server_client_mouse_button_string(int b) {
+ static char buf[200];
+ buf[0] = '\0';
+ if (b & MOUSE_SHIFT) strcat(buf, "shift ");
+ if (b & MOUSE_ALT) strcat(buf, "alt ");
+ if (b & MOUSE_CTRL) strcat(buf, "ctrl ");
+ if (!(b & MOUSE_45) && ((b & MOUSE_BUTTON) == MOUSE_1)) strcat(buf, "left ");
+ if (!(b & MOUSE_45) && ((b & MOUSE_BUTTON) == MOUSE_2)) strcat(buf, "middle ");
+ if ((b & MOUSE_BUTTON) == MOUSE_3) strcat(buf, "right ");
+ if (!(b & MOUSE_45) && ((b & MOUSE_BUTTON) == MOUSE_BUTTON)) strcat(buf, "buttonup ");
+ if (b & MOUSE_DRAG) strcat(buf, "drag ");
+ if ((b & MOUSE_45) && (b & MOUSE_BUTTON) == MOUSE_1) strcat(buf, "wheelup ");
+ if ((b & MOUSE_45) && (b & MOUSE_BUTTON) == MOUSE_2) strcat(buf, "wheeldown ");
+
+ return buf;
+}
+
+#define MAXMOUSEEVENTS 10
+void
+server_client_prune_mouse_stack(struct client *c)
+{
+ struct mouse_event *m = TAILQ_FIRST(&c->mouse_events);
+ int i = 0;
+
+ while (m) {
+ if(++i == MAXMOUSEEVENTS) {
+ TAILQ_REMOVE(&c->mouse_events, m, entry);
+ xfree(m);
+ break;
+ } else
+ m = TAILQ_NEXT(m, entry);
+ }
+ log_debug("mouse: %u events in history", i);
+
+}
+
+void
+server_client_handle_mouse_event(struct client *c, struct mouse_event *mouse)
+{
+ struct mouse_event *m[MAXMOUSEEVENTS];
+ struct session *s;
+ struct window *w;
+ struct window_pane *wp;
+ struct options *oo;
+ char *btn_str;
+ u_int tdiff, i = 0;
+
+ s = c->session;
+ w = s->curw->window;
+ wp = w->active;
+ oo = &s->options;
+
+ btn_str = server_client_mouse_button_string(mouse->b);
+ tdiff = TAILQ_EMPTY(&c->mouse_events) ? 0 : server_client_mouse_time_diff(TAILQ_FIRST(&c->mouse_events), mouse);
+ log_debug("mouse event: %s(button=%u), (x,y)=(%u,%u) (+%.2fsec)",
+ btn_str, mouse->b, mouse->x, mouse->y, tdiff/1000000.0);
+
+ memset(m, 0, MAXMOUSEEVENTS * sizeof *m);
+ m[0] = xmalloc(sizeof(struct mouse_event));
+ memcpy(m[0], mouse, sizeof(struct mouse_event));
+
+ TAILQ_INSERT_HEAD(&c->mouse_events, m[0], entry);
+ server_client_prune_mouse_stack(c);
+ /* Map the mouse event history tail queue to an array for laziness benefit. */
+ TAILQ_FOREACH(mouse, &c->mouse_events, entry) {
+ m[i] = mouse;
+ i++;
+ }
+
+ /* Allow pane switching in copy mode only by mouse down (click). */
+ if (options_get_number(oo, "mouse-select-pane") &&
+ !(options_get_number(oo, "status") && m[0]->y + 1 == c->tty.sy) &&
+ (wp->mode != &window_copy_mode ||
+ (!(m[0]->b & MOUSE_DRAG) && m[0]->b != MOUSE_UP))) { /* neither mouse drag nor button up */
+ window_set_active_at(w, m[0]->x, m[0]->y);
+ server_redraw_window_borders(w);
+ wp = w->active;
+ }
+ if (m[0]->y + 1 == c->tty.sy &&
+ options_get_number(oo, "mouse-select-window") &&
+ options_get_number(oo, "status")) {
+ if (m[0]->b == MOUSE_UP && m[1] && m[1]->b != MOUSE_UP) {
+ status_set_window_at(c, m[0]->x);
+ return;
+ }
+ if (m[0]->b & MOUSE_45) {
+ if ((m[0]->b & MOUSE_BUTTON) == MOUSE_1) {
+ session_previous(c->session, 0);
+ }
+ if ((m[0]->b & MOUSE_BUTTON) == MOUSE_2) {
+ session_next(c->session, 0);
+ }
+ recalculate_sizes();
+ server_redraw_session(s);
+ return;
+ }
+ }
+ if (options_get_number(oo, "mouse-resize-pane"))
+ layout_resize_pane_mouse(c, m[0]);
+ /*
+ * TODO: Elaborate mouse click gesture matching on
+ * event history array goes here. Config syntax?
+ */
+ window_pane_mouse(wp, c->session, m[0]);
+ return;
+}
+
/* Handle data key input from client. */
void
server_client_handle_key(int key, struct mouse_event *mouse, void *data)
@@ -287,6 +408,7 @@ server_client_handle_key(int key, struct
if (gettimeofday(&c->activity_time, NULL) != 0)
fatal("gettimeofday failed");
memcpy(&s->activity_time, &c->activity_time, sizeof s->activity_time);
+ memcpy(&mouse->time, &c->activity_time, sizeof mouse->time);
w = c->session->curw->window;
wp = w->active;
@@ -318,42 +440,8 @@ server_client_handle_key(int key, struct
if (key == KEYC_MOUSE) {
if (c->flags & CLIENT_READONLY)
return;
- if (options_get_number(oo, "mouse-select-pane") &&
- ((!(mouse->b & MOUSE_DRAG) && mouse->b != MOUSE_UP) ||
- wp->mode != &window_copy_mode)) {
- /*
- * Allow pane switching in copy mode only by mouse down
- * (click).
- */
- window_set_active_at(w, mouse->x, mouse->y);
- server_redraw_window_borders(w);
- wp = w->active;
- }
- if (mouse->y + 1 == c->tty.sy &&
- options_get_number(oo, "mouse-select-window") &&
- options_get_number(oo, "status")) {
- if (mouse->b == MOUSE_UP &&
- c->last_mouse.b != MOUSE_UP) {
- status_set_window_at(c, mouse->x);
- return;
- }
- if (mouse->b & MOUSE_45) {
- if ((mouse->b & MOUSE_BUTTON) == MOUSE_1) {
- session_previous(c->session, 0);
- server_redraw_session(s);
- }
- if ((mouse->b & MOUSE_BUTTON) == MOUSE_2) {
- session_next(c->session, 0);
- server_redraw_session(s);
- }
- return;
- }
- }
- if (options_get_number(oo, "mouse-resize-pane"))
- layout_resize_pane_mouse(c, mouse);
- memcpy(&c->last_mouse, mouse, sizeof c->last_mouse);
- window_pane_mouse(wp, c->session, mouse);
- return;
+ else
+ server_client_handle_mouse_event(c, mouse);
}
/* Is this a prefix key? */
@@ -473,6 +561,7 @@ server_client_reset_state(struct client
struct screen *s = wp->screen;
struct options *oo = &c->session->options;
struct options *wo = &w->options;
+ struct mouse_event *lm;
int status, mode;
if (c->flags & CLIENT_SUSPENDED)
@@ -491,7 +580,9 @@ server_client_reset_state(struct client
* a smooth appearance.
*/
mode = s->mode;
- if ((c->last_mouse.b & MOUSE_RESIZE_PANE) &&
+ if (!TAILQ_EMPTY(&c->mouse_events) &&
+ (lm = TAILQ_NEXT(TAILQ_FIRST(&c->mouse_events), entry)) != NULL &&
+ lm->b & MOUSE_RESIZE_PANE &&
!(mode & (MODE_MOUSE_BUTTON|MODE_MOUSE_ANY)))
mode |= MODE_MOUSE_BUTTON;
--- layout.c.orig 2011-12-21 21:56:08.929057970 +0100
+++ layout.c 2011-12-21 21:57:02.135043206 +0100
@@ -490,26 +490,27 @@ layout_resize_pane_mouse(struct client *
{
struct window *w;
struct window_pane *wp;
+ struct mouse_event *last_mouse = TAILQ_NEXT(mouse, entry);
int pane_border;
w = c->session->curw->window;
-
pane_border = 0;
- if ((c->last_mouse.b & MOUSE_BUTTON) != MOUSE_UP &&
- (c->last_mouse.b & MOUSE_RESIZE_PANE)) {
+
+ if (last_mouse != NULL && (last_mouse->b & MOUSE_BUTTON) != MOUSE_UP &&
+ (last_mouse->b & MOUSE_RESIZE_PANE)) {
TAILQ_FOREACH(wp, &w->panes, entry) {
- if (wp->xoff + wp->sx == c->last_mouse.x &&
- wp->yoff <= 1 + c->last_mouse.y &&
- wp->yoff + wp->sy >= c->last_mouse.y) {
+ if (wp->xoff + wp->sx == last_mouse->x &&
+ wp->yoff <= 1 + last_mouse->y &&
+ wp->yoff + wp->sy >= last_mouse->y) {
layout_resize_pane(wp, LAYOUT_LEFTRIGHT,
- mouse->x - c->last_mouse.x);
+ mouse->x - last_mouse->x);
pane_border = 1;
}
- if (wp->yoff + wp->sy == c->last_mouse.y &&
- wp->xoff <= 1 + c->last_mouse.x &&
- wp->xoff + wp->sx >= c->last_mouse.x) {
+ if (wp->yoff + wp->sy == last_mouse->y &&
+ wp->xoff <= 1 + last_mouse->x &&
+ wp->xoff + wp->sx >= last_mouse->x) {
layout_resize_pane(wp, LAYOUT_TOPBOTTOM,
- mouse->y - c->last_mouse.y);
+ mouse->y - last_mouse->y);
pane_border = 1;
}
}
--- tty-keys.c.orig 2011-12-21 21:56:09.019061330 +0100
+++ tty-keys.c 2011-12-21 21:57:02.142043467 +0100
@@ -633,6 +633,7 @@ tty_keys_mouse(struct tty *tty,
value = (unsigned char)buf[*size];
(*size)++;
} else {
+ /* FIXME: x > 160 = pointeger magic casting FAIL? */
value = (unsigned char)buf[*size];
(*size)++;
}
@@ -644,14 +645,12 @@ tty_keys_mouse(struct tty *tty,
else
m->y = value;
}
- log_debug("mouse input: %.*s", (int) *size, buf);
-
- /* Check and return the mouse input. */
+ /* Fail on invalid mouse input. */
if (m->b < 32 || m->x < 33 || m->y < 33)
return (-1);
m->b -= 32;
m->x -= 33;
m->y -= 33;
- log_debug("mouse position: x=%u y=%u b=%u", m->x, m->y, m->b);
+ memset(&m->entry, 0, sizeof(m->entry));
return (0);
}
--- tmux.h.orig 2011-12-21 21:56:09.018061293 +0100
+++ tmux.h 2011-12-21 21:57:02.142043467 +0100
@@ -569,6 +569,8 @@ struct mode_key_table {
#define ALL_MOUSE_MODES (MODE_MOUSE_STANDARD|MODE_MOUSE_BUTTON|MODE_MOUSE_ANY)
+#define MOUSE_SCROLL_EMU_LINES 3
+
/*
* A single UTF-8 character.
*
@@ -1099,12 +1101,18 @@ struct mouse_event {
#define MOUSE_3 2
#define MOUSE_UP 3
#define MOUSE_BUTTON 3
+#define MOUSE_SHIFT 4
+#define MOUSE_ALT 8
+#define MOUSE_CTRL 16
#define MOUSE_DRAG 32
#define MOUSE_45 64
#define MOUSE_RESIZE_PANE 128 /* marker for resizing */
u_int x;
u_int y;
+ struct timeval time;
+ TAILQ_ENTRY(mouse_event) entry;
};
+TAILQ_HEAD(mouse_event_stack, mouse_event);
/* Saved message entry. */
struct message_entry {
@@ -1193,7 +1201,7 @@ struct client {
struct session *session;
struct session *last_session;
- struct mouse_event last_mouse;
+ struct mouse_event_stack mouse_events;
int references;
};
------------------------------------------------------------------------------
Write once. Port to many.
Get the SDK and tools to simplify cross-platform app development. Create
new or port existing apps to sell to consumers worldwide. Explore the
Intel AppUpSM program developer opportunity. appdeveloper.intel.com/join
http://p.sf.net/sfu/intel-appdev
_______________________________________________
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users