/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #include "mouse.h" #include "globals.h" #include "clientlist.h" #include "layout.h" #include "key.h" #include "ipc-protocol.h" #include "utils.h" #include "settings.h" #include "command.h" #include #include #include #include "glib-backports.h" // gui #include #include #include #include #include static Point2D g_button_drag_start; static Rectangle g_win_drag_start; static HSClient* g_win_drag_client = NULL; static HSMonitor* g_drag_monitor = NULL; static MouseDragFunction g_drag_function = NULL; static Cursor g_cursor; static GList* g_mouse_binds = NULL; static unsigned int* g_numlockmask_ptr; static int* g_snap_distance; static int* g_snap_gap; #define CLEANMASK(mask) ((mask) & ~(*g_numlockmask_ptr|LockMask)) #define REMOVEBUTTONMASK(mask) ((mask) & \ ~( Button1Mask \ | Button2Mask \ | Button3Mask \ | Button4Mask \ | Button5Mask )) void mouse_init() { g_numlockmask_ptr = get_numlockmask_ptr(); g_snap_distance = &(settings_find("snap_distance")->value.i); g_snap_gap = &(settings_find("snap_gap")->value.i); /* set cursor theme */ g_cursor = XCreateFontCursor(g_display, XC_left_ptr); XDefineCursor(g_display, g_root, g_cursor); } void mouse_destroy() { mouse_unbind_all(); XFreeCursor(g_display, g_cursor); } void mouse_handle_event(XEvent* ev) { XButtonEvent* be = &(ev->xbutton); MouseBinding* b = mouse_binding_find(be->state, be->button); HSClient* client = get_client_from_window(ev->xbutton.window); if (!b || !client) { // there is no valid bind for this type of mouse event return; } b->action(client, b->argc, b->argv); } void mouse_initiate_move(HSClient* client, int argc, char** argv) { (void) argc; (void) argv; mouse_initiate_drag(client, mouse_function_move); } void mouse_initiate_zoom(HSClient* client, int argc, char** argv) { (void) argc; (void) argv; mouse_initiate_drag(client, mouse_function_zoom); } void mouse_initiate_resize(HSClient* client, int argc, char** argv) { (void) argc; (void) argv; mouse_initiate_drag(client, mouse_function_resize); } void mouse_call_command(struct HSClient* client, int argc, char** argv) { // TODO: add completion client_set_dragged(client, true); call_command_no_output(argc, argv); client_set_dragged(client, false); } void mouse_initiate_drag(HSClient* client, MouseDragFunction function) { g_drag_function = function; g_win_drag_client = client; g_drag_monitor = find_monitor_with_tag(client->tag); if (!g_drag_monitor || client->tag->floating == false) { // only can drag wins in floating mode g_win_drag_client = NULL; g_drag_function = NULL; return; } client_set_dragged(g_win_drag_client, true); g_win_drag_start = g_win_drag_client->float_size; g_button_drag_start = get_cursor_position(); XGrabPointer(g_display, client->window, True, PointerMotionMask|ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); } void mouse_stop_drag() { if (g_win_drag_client) { client_set_dragged(g_win_drag_client, false); // resend last size monitor_apply_layout(g_drag_monitor); } g_win_drag_client = NULL; g_drag_function = NULL; XUngrabPointer(g_display, CurrentTime); // remove all enternotify-events from the event queue that were // generated by the XUngrabPointer XEvent ev; XSync(g_display, False); while(XCheckMaskEvent(g_display, EnterWindowMask, &ev)); } void handle_motion_event(XEvent* ev) { if (!g_drag_monitor) { return; } if (!g_win_drag_client) return; if (!g_drag_function) return; if (ev->type != MotionNotify) return; // get newest motion notification while (XCheckMaskEvent(g_display, ButtonMotionMask, ev)); // call function that handles it g_drag_function(&(ev->xmotion)); } bool mouse_is_dragging() { return g_drag_function != NULL; } static void mouse_binding_free(void* voidmb) { MouseBinding* mb = (MouseBinding*)voidmb; if (!mb) return; argv_free(mb->argc, mb->argv); g_free(mb); } int mouse_unbind_all() { g_list_free_full(g_mouse_binds, mouse_binding_free); g_mouse_binds = NULL; HSClient* client = get_current_client(); if (client) { grab_client_buttons(client, true); } return 0; } int mouse_binding_equals(MouseBinding* a, MouseBinding* b) { if((REMOVEBUTTONMASK(CLEANMASK(a->modifiers)) == REMOVEBUTTONMASK(CLEANMASK(b->modifiers))) && (a->button == b->button)) { return 0; } else { return -1; } } int mouse_bind_command(int argc, char** argv, GString* output) { if (argc < 3) { return HERBST_NEED_MORE_ARGS; } unsigned int modifiers = 0; char* string = argv[1]; if (!string2modifiers(string, &modifiers)) { g_string_append_printf(output, "%s: Modifier \"%s\" does not exist\n", argv[0], string); return HERBST_INVALID_ARGUMENT; } // last one is the mouse button const char* last_token = strlasttoken(string, KEY_COMBI_SEPARATORS); unsigned int button = string2button(last_token); if (button == 0) { g_string_append_printf(output, "%s: Unknown mouse button \"%s\"\n", argv[0], last_token); return HERBST_INVALID_ARGUMENT; } MouseFunction function = string2mousefunction(argv[2]); if (!function) { g_string_append_printf(output, "%s: Unknown mouse action \"%s\"\n", argv[0], argv[2]); return HERBST_INVALID_ARGUMENT; } // actually create a binding MouseBinding* mb = g_new(MouseBinding, 1); mb->button = button; mb->modifiers = modifiers; mb->action = function; mb->argc = argc - 3; mb->argv = argv_duplicate(argc - 3, argv + 3);; g_mouse_binds = g_list_prepend(g_mouse_binds, mb); HSClient* client = get_current_client(); if (client) { grab_client_buttons(client, true); } return 0; } MouseFunction string2mousefunction(char* name) { static struct { const char* name; MouseFunction function; } table[] = { { "move", mouse_initiate_move }, { "zoom", mouse_initiate_zoom }, { "resize", mouse_initiate_resize }, { "call", mouse_call_command }, }; int i; for (i = 0; i < LENGTH(table); i++) { if (!strcmp(table[i].name, name)) { return table[i].function; } } return NULL; } static struct { const char* name; unsigned int button; } string2button_table[] = { { "Button1", Button1 }, { "Button2", Button2 }, { "Button3", Button3 }, { "Button4", Button4 }, { "Button5", Button5 }, { "B1", Button1 }, { "B2", Button2 }, { "B3", Button3 }, { "B4", Button4 }, { "B5", Button5 }, }; unsigned int string2button(const char* name) { for (int i = 0; i < LENGTH(string2button_table); i++) { if (!strcmp(string2button_table[i].name, name)) { return string2button_table[i].button; } } return 0; } void complete_against_mouse_buttons(const char* needle, char* prefix, GString* output) { for (int i = 0; i < LENGTH(string2button_table); i++) { const char* buttonname = string2button_table[i].name; try_complete_prefix(needle, buttonname, prefix, output); } } MouseBinding* mouse_binding_find(unsigned int modifiers, unsigned int button) { MouseBinding mb = { 0 }; mb.modifiers = modifiers; mb.button = button; GList* elem = g_list_find_custom(g_mouse_binds, &mb, (GCompareFunc)mouse_binding_equals); return elem ? ((MouseBinding*)elem->data) : NULL; } static void grab_client_button(MouseBinding* bind, HSClient* client) { unsigned int modifiers[] = { 0, LockMask, *g_numlockmask_ptr, *g_numlockmask_ptr|LockMask }; for(int j = 0; j < LENGTH(modifiers); j++) { XGrabButton(g_display, bind->button, bind->modifiers | modifiers[j], client->window, False, ButtonPressMask | ButtonReleaseMask, GrabModeAsync, GrabModeSync, None, None); } } void grab_client_buttons(HSClient* client, bool focused) { update_numlockmask(); XUngrabButton(g_display, AnyButton, AnyModifier, client->window); if (focused) { g_list_foreach(g_mouse_binds, (GFunc)grab_client_button, client); } unsigned int btns[] = { Button1, Button2, Button3 }; for (int i = 0; i < LENGTH(btns); i++) { XGrabButton(g_display, btns[i], AnyModifier, client->window, False, ButtonPressMask|ButtonReleaseMask, GrabModeSync, GrabModeSync, None, None); } } void mouse_function_move(XMotionEvent* me) { int x_diff = me->x_root - g_button_drag_start.x; int y_diff = me->y_root - g_button_drag_start.y; g_win_drag_client->float_size = g_win_drag_start; g_win_drag_client->float_size.x += x_diff; g_win_drag_client->float_size.y += y_diff; // snap it to other windows int dx, dy; client_snap_vector(g_win_drag_client, g_drag_monitor, SNAP_EDGE_ALL, &dx, &dy); g_win_drag_client->float_size.x += dx; g_win_drag_client->float_size.y += dy; client_resize_floating(g_win_drag_client, g_drag_monitor); } void mouse_function_resize(XMotionEvent* me) { int x_diff = me->x_root - g_button_drag_start.x; int y_diff = me->y_root - g_button_drag_start.y; g_win_drag_client->float_size = g_win_drag_start; // relative x/y coords in drag window HSMonitor* m = g_drag_monitor; int rel_x = monitor_get_relative_x(m, g_button_drag_start.x) - g_win_drag_start.x; int rel_y = monitor_get_relative_y(m, g_button_drag_start.y) - g_win_drag_start.y; bool top = false; bool left = false; if (rel_y < g_win_drag_start.height/2) { top = true; y_diff *= -1; } if (rel_x < g_win_drag_start.width/2) { left = true; x_diff *= -1; } // avoid an overflow int new_width = g_win_drag_client->float_size.width + x_diff; int new_height = g_win_drag_client->float_size.height + y_diff; int min_width = WINDOW_MIN_WIDTH; int min_height = WINDOW_MIN_HEIGHT; HSClient* client = g_win_drag_client; if (client->sizehints_floating) { min_width = MAX(WINDOW_MIN_WIDTH, client->minw); min_height = MAX(WINDOW_MIN_HEIGHT, client->minh); } if (new_width < min_width) { new_width = min_width; x_diff = new_width - g_win_drag_client->float_size.width; } if (new_height < min_height) { new_height = min_height; y_diff = new_height - g_win_drag_client->float_size.height; } if (left) g_win_drag_client->float_size.x -= x_diff; if (top) g_win_drag_client->float_size.y -= y_diff; g_win_drag_client->float_size.width = new_width; g_win_drag_client->float_size.height = new_height; // snap it to other windows int dx, dy; int snap_flags = 0; if (left) snap_flags |= SNAP_EDGE_LEFT; else snap_flags |= SNAP_EDGE_RIGHT; if (top) snap_flags |= SNAP_EDGE_TOP; else snap_flags |= SNAP_EDGE_BOTTOM; client_snap_vector(g_win_drag_client, g_drag_monitor, (SnapFlags)snap_flags, &dx, &dy); if (left) { g_win_drag_client->float_size.x += dx; dx *= -1; } if (top) { g_win_drag_client->float_size.y += dy; dy *= -1; } g_win_drag_client->float_size.width += dx; g_win_drag_client->float_size.height += dy; client_resize_floating(g_win_drag_client, g_drag_monitor); } void mouse_function_zoom(XMotionEvent* me) { // stretch, where center stays at the same position int x_diff = me->x_root - g_button_drag_start.x; int y_diff = me->y_root - g_button_drag_start.y; // relative x/y coords in drag window HSMonitor* m = g_drag_monitor; int rel_x = monitor_get_relative_x(m, g_button_drag_start.x) - g_win_drag_start.x; int rel_y = monitor_get_relative_y(m, g_button_drag_start.y) - g_win_drag_start.y; int cent_x = g_win_drag_start.x + g_win_drag_start.width / 2; int cent_y = g_win_drag_start.y + g_win_drag_start.height / 2; if (rel_x < g_win_drag_start.width/2) { x_diff *= -1; } if (rel_y < g_win_drag_start.height/2) { y_diff *= -1; } HSClient* client = g_win_drag_client; // avoid an overflow int new_width = g_win_drag_start.width + 2 * x_diff; int new_height = g_win_drag_start.height + 2 * y_diff; // apply new rect client->float_size = g_win_drag_start; client->float_size.x = cent_x - new_width / 2; client->float_size.y = cent_y - new_height / 2; client->float_size.width = new_width; client->float_size.height = new_height; // snap it to other windows int right_dx, bottom_dy; int left_dx, top_dy; // we have to distinguish the direction in which we zoom client_snap_vector(g_win_drag_client, m, (SnapFlags)(SNAP_EDGE_BOTTOM | SNAP_EDGE_RIGHT), &right_dx, &bottom_dy); client_snap_vector(g_win_drag_client, m, (SnapFlags)(SNAP_EDGE_TOP | SNAP_EDGE_LEFT), &left_dx, &top_dy); // e.g. if window snaps by vector (3,3) at topleft, window has to be shrinked // but if the window snaps by vector (3,3) at bottomright, window has to grow if (abs(right_dx) < abs(left_dx)) { right_dx = -left_dx; } if (abs(bottom_dy) < abs(top_dy)) { bottom_dy = -top_dy; } new_width += 2 * right_dx; new_height += 2 * bottom_dy; applysizehints(client, &new_width, &new_height); // center window again client->float_size.width = new_width; client->float_size.height = new_height; client->float_size.x = cent_x - new_width / 2; client->float_size.y = cent_y - new_height / 2; client_resize_floating(g_win_drag_client, g_drag_monitor); } struct SnapData { HSClient* client; Rectangle rect; enum SnapFlags flags; int dx, dy; // the vector from client to other to make them snap }; bool is_point_between(int point, int left, int right) { return (point < right && point >= left); } // tells if the intervals [a_left, a_right) [b_left, b_right) intersect bool intervals_intersect(int a_left, int a_right, int b_left, int b_right) { return (b_left < a_right) && (a_left < b_right); } // compute vector to snap a point to an edge static void snap_1d(int x, int edge, int* delta) { // whats the vector from subject to edge? int cur_delta = edge - x; // if distance is smaller then all other deltas if (abs(cur_delta) < abs(*delta)) { // then snap it, i.e. save vector *delta = cur_delta; } } static int client_snap_helper(HSClient* candidate, struct SnapData* d) { if (candidate == d->client) { return 0; } Rectangle subject = d->rect; Rectangle other = candidate->dec.last_outer_rect; // increase other by snap gap other.x -= *g_snap_gap; other.y -= *g_snap_gap; other.width += *g_snap_gap * 2; other.height += *g_snap_gap * 2; if (intervals_intersect(other.y, other.y + other.height, subject.y, subject.y + subject.height)) { // check if x can snap to the right if (d->flags & SNAP_EDGE_RIGHT) { snap_1d(subject.x + subject.width, other.x, &d->dx); } // or to the left if (d->flags & SNAP_EDGE_LEFT) { snap_1d(subject.x, other.x + other.width, &d->dx); } } if (intervals_intersect(other.x, other.x + other.width, subject.x, subject.x + subject.width)) { // if we can snap to the top if (d->flags & SNAP_EDGE_TOP) { snap_1d(subject.y, other.y + other.height, &d->dy); } // or to the bottom if (d->flags & SNAP_EDGE_BOTTOM) { snap_1d(subject.y + subject.height, other.y, &d->dy); } } return 0; } // get the vector to snap a client to it's neighbour void client_snap_vector(struct HSClient* client, struct HSMonitor* monitor, enum SnapFlags flags, int* return_dx, int* return_dy) { struct SnapData d; HSTag* tag = monitor->tag; int distance = (*g_snap_distance > 0) ? *g_snap_distance : 0; // init delta *return_dx = 0; *return_dy = 0; if (!distance) { // nothing to do return; } d.client = client; // translate client rectangle to global coordinates d.rect = client_outer_floating_rect(client); d.rect.x += monitor->rect.x + monitor->pad_left; d.rect.y += monitor->rect.y + monitor->pad_up; d.flags = flags; d.dx = distance; d.dy = distance; // snap to monitor edges HSMonitor* m = g_drag_monitor; if (flags & SNAP_EDGE_TOP) { snap_1d(d.rect.y, m->rect.y + m->pad_up + *g_snap_gap, &d.dy); } if (flags & SNAP_EDGE_LEFT) { snap_1d(d.rect.x, m->rect.x + m->pad_left + *g_snap_gap, &d.dx); } if (flags & SNAP_EDGE_RIGHT) { snap_1d(d.rect.x + d.rect.width, m->rect.x + m->rect.width - m->pad_right - *g_snap_gap, &d.dx); } if (flags & SNAP_EDGE_BOTTOM) { snap_1d(d.rect.y + d.rect.height, m->rect.y + m->rect.height - m->pad_down - *g_snap_gap, &d.dy); } // snap to other clients frame_foreach_client(tag->frame, (ClientAction)client_snap_helper, &d); // write back results if (abs(d.dx) < abs(distance)) { *return_dx = d.dx; } if (abs(d.dy) < abs(distance)) { *return_dy = d.dy; } }