diff --git a/assets/config.conf b/assets/config.conf index 0881e5ad..28ed6136 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -261,3 +261,22 @@ axisbind=SUPER,DOWN,viewtoright_have_client # layer rule layerrule=animation_type_open:zoom,layer_name:rofi layerrule=animation_type_close:zoom,layer_name:rofi + +# Touch gesture settings +touch_distance_threshold = 100 +touch_degrees_leniency = 30 +touch_timeoutms = 1000 +touch_edge_size_left = 50 +touch_edge_size_top = 50 +touch_edge_size_right = 50 +touch_edge_size_bottom = 50 + +# Canvas touch gestures +touchgesturebind=up,any,any,2,canvas_zoom_resize,1.3 +touchgesturebind=down,any,any,2,canvas_zoom_resize,0.7 + +# 3-finger swipe: pan +touchgesturebind=right,any,any,3,canvas_pan,300,0 +touchgesturebind=left,any,any,3,canvas_pan,-300,0 +touchgesturebind=up,any,any,3,canvas_pan,0,300 +touchgesturebind=down,any,any,3,canvas_pan,0,-300 diff --git a/src/config/parse_config.h b/src/config/parse_config.h index c8170f52..600ded21 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -128,18 +128,8 @@ typedef struct { // 默认按键绑定数组 KeyBinding default_key_bindings[] = { - CHVT(1), - CHVT(2), - CHVT(3), - CHVT(4), - CHVT(5), - CHVT(6), - CHVT(7), - CHVT(8), - CHVT(9), - CHVT(10), - CHVT(11), - CHVT(12), + CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), }; typedef struct { @@ -170,6 +160,15 @@ typedef struct { Arg arg; } GestureBinding; +typedef struct { + uint32_t swipe; + uint32_t edge; + uint32_t distance; + uint32_t fingers_count; + int32_t (*func)(const Arg *); + Arg arg; +} TouchGestureBinding; + typedef struct { int32_t id; char *layout_name; @@ -296,6 +295,13 @@ typedef struct { char *tablet_map_to_mon; + double touch_distance_threshold; + double touch_degrees_leniency; + uint32_t touch_timeoutms; + double touch_edge_size_left; + double touch_edge_size_top; + double touch_edge_size_right; + double touch_edge_size_bottom; int32_t blur; int32_t blur_layer; int32_t blur_optimized; @@ -361,6 +367,9 @@ typedef struct { GestureBinding *gesture_bindings; int32_t gesture_bindings_count; + TouchGestureBinding *touch_gesture_bindings; + int32_t touch_gesture_bindings_count; + ConfigEnv **env; int32_t env_count; @@ -549,6 +558,92 @@ int32_t parse_direction(const char *str) { } } +int32_t parse_touch_direction(const char *str) { + char lowerStr[11]; + int32_t i = 0; + while (str[i] && i < 10) { + lowerStr[i] = tolower(str[i]); + i++; + } + lowerStr[i] = '\0'; + + if (strcmp(lowerStr, "up") == 0) { + return TOUCH_SWIPE_UP; + } else if (strcmp(lowerStr, "down") == 0) { + return TOUCH_SWIPE_DOWN; + } else if (strcmp(lowerStr, "left") == 0) { + return TOUCH_SWIPE_LEFT; + } else if (strcmp(lowerStr, "right") == 0) { + return TOUCH_SWIPE_RIGHT; + } else if (strcmp(lowerStr, "up_left") == 0) { + return TOUCH_SWIPE_UP_LEFT; + } else if (strcmp(lowerStr, "up_right") == 0) { + return TOUCH_SWIPE_UP_RIGHT; + } else if (strcmp(lowerStr, "down_left") == 0) { + return TOUCH_SWIPE_DOWN_LEFT; + } else if (strcmp(lowerStr, "down_right") == 0) { + return TOUCH_SWIPE_DOWN_RIGHT; + } else { + return TOUCH_SWIPE_NONE; + } +} + +int32_t parse_touch_edge(const char *str) { + char lowerStr[13]; + int32_t i = 0; + while (str[i] && i < 12) { + lowerStr[i] = tolower(str[i]); + i++; + } + lowerStr[i] = '\0'; + + if (strcmp(lowerStr, "any") == 0) { + return EDGE_ANY; + } else if (strcmp(lowerStr, "none") == 0) { + return EDGE_NONE; + } else if (strcmp(lowerStr, "left") == 0) { + return EDGE_LEFT; + } else if (strcmp(lowerStr, "right") == 0) { + return EDGE_RIGHT; + } else if (strcmp(lowerStr, "top") == 0) { + return EDGE_TOP; + } else if (strcmp(lowerStr, "bottom") == 0) { + return EDGE_BOTTOM; + } else if (strcmp(lowerStr, "top_left") == 0) { + return CORNER_TOP_LEFT; + } else if (strcmp(lowerStr, "top_right") == 0) { + return CORNER_TOP_RIGHT; + } else if (strcmp(lowerStr, "bottom_left") == 0) { + return CORNER_BOTTOM_LEFT; + } else if (strcmp(lowerStr, "bottom_right") == 0) { + return CORNER_BOTTOM_RIGHT; + } else { + return EDGE_ANY; + } +} + +int32_t parse_distance(const char *str) { + char lowerStr[7]; + int32_t i = 0; + while (str[i] && i < 6) { + lowerStr[i] = tolower(str[i]); + i++; + } + lowerStr[i] = '\0'; + + if (strcmp(lowerStr, "any") == 0) { + return DISTANCE_ANY; + } else if (strcmp(lowerStr, "short") == 0) { + return DISTANCE_SHORT; + } else if (strcmp(lowerStr, "medium") == 0) { + return DISTANCE_MEDIUM; + } else if (strcmp(lowerStr, "long") == 0) { + return DISTANCE_LONG; + } else { + return DISTANCE_ANY; + } +} + int32_t parse_fold_state(const char *str) { // 将输入字符串转换为小写 char lowerStr[10]; @@ -1226,6 +1321,10 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, } else if (strcmp(func_name, "canvas_zoom_resize") == 0) { func = canvas_zoom_resize; (*arg).f = atof(arg_value); + } else if (strcmp(func_name, "canvas_pan") == 0) { + func = canvas_pan; + (*arg).f = atof(arg_value); + (*arg).f2 = atof(arg_value2); } else if (strcmp(func_name, "canvas_overview_toggle") == 0) { func = canvas_overview_toggle; } else if (strcmp(func_name, "canvas_fill_viewport") == 0) { @@ -1709,6 +1808,20 @@ bool parse_option(Config *config, char *key, char *value) { if (config->tablet_map_to_mon) free(config->tablet_map_to_mon); config->tablet_map_to_mon = strdup(value); + } else if (strcmp(key, "touch_distance_threshold") == 0) { + config->touch_distance_threshold = atof(value); + } else if (strcmp(key, "touch_degrees_leniency") == 0) { + config->touch_degrees_leniency = atof(value); + } else if (strcmp(key, "touch_timeoutms") == 0) { + config->touch_timeoutms = atoi(value); + } else if (strcmp(key, "touch_edge_size_left") == 0) { + config->touch_edge_size_left = atof(value); + } else if (strcmp(key, "touch_edge_size_top") == 0) { + config->touch_edge_size_top = atof(value); + } else if (strcmp(key, "touch_edge_size_right") == 0) { + config->touch_edge_size_right = atof(value); + } else if (strcmp(key, "touch_edge_size_bottom") == 0) { + config->touch_edge_size_bottom = atof(value); } else if (strcmp(key, "gappih") == 0) { config->gappih = atoi(value); } else if (strcmp(key, "gappiv") == 0) { @@ -2740,6 +2853,91 @@ bool parse_option(Config *config, char *key, char *value) { config->gesture_bindings_count++; } + } else if (strncmp(key, "touchgesturebind", 16) == 0) { + config->touch_gesture_bindings = + realloc(config->touch_gesture_bindings, + (config->touch_gesture_bindings_count + 1) * + sizeof(TouchGestureBinding)); + if (!config->touch_gesture_bindings) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for axis touchgesturebind\n"); + return false; + } + + TouchGestureBinding *binding = + &config + ->touch_gesture_bindings[config->touch_gesture_bindings_count]; + memset(binding, 0, sizeof(TouchGestureBinding)); + + char swipe_str[256], edge_str[256], distance_str[256], + fingers_count_str[256], func_name[256], + arg_value[256] = "0\0", arg_value2[256] = "0\0", + arg_value3[256] = "0\0", arg_value4[256] = "0\0", + arg_value5[256] = "0\0"; + if (sscanf(value, + "%255[^,],%255[^,],%255[^,],%255[^,],%255[" + "^,],%255[^,],%255[^,],%255[^,],%255[^,],%255[^\n]", + swipe_str, edge_str, distance_str, fingers_count_str, + func_name, arg_value, arg_value2, arg_value3, arg_value4, + arg_value5) < 4) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid touchgesturebind " + "format: %s\n", + value); + return false; + } + + trim_whitespace(swipe_str); + trim_whitespace(edge_str); + trim_whitespace(distance_str); + trim_whitespace(fingers_count_str); + trim_whitespace(func_name); + trim_whitespace(arg_value); + trim_whitespace(arg_value2); + trim_whitespace(arg_value3); + trim_whitespace(arg_value4); + trim_whitespace(arg_value5); + + binding->swipe = parse_touch_direction(swipe_str); + binding->edge = parse_touch_edge(edge_str); + binding->distance = parse_distance(distance_str); + binding->fingers_count = atoi(fingers_count_str); + binding->arg.i = 0; + binding->arg.i2 = 0; + binding->arg.f = 0.0f; + binding->arg.f2 = 0.0f; + binding->arg.ui = 0; + binding->arg.ui2 = 0; + binding->arg.v = NULL; + binding->arg.v2 = NULL; + binding->arg.v3 = NULL; + binding->func = + parse_func_name(func_name, &binding->arg, arg_value, arg_value2, + arg_value3, arg_value4, arg_value5); + + if (!binding->func) { + if (binding->arg.v) { + free(binding->arg.v); + binding->arg.v = NULL; + } + if (binding->arg.v2) { + free(binding->arg.v2); + binding->arg.v2 = NULL; + } + if (binding->arg.v3) { + free(binding->arg.v3); + binding->arg.v3 = NULL; + } + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown " + "dispatch in " + "touchgesturebind: \033[1m\033[31m%s\n", + func_name); + return false; + } else { + config->touch_gesture_bindings_count++; + } } else if (strncmp(key, "source-optional", 15) == 0) { parse_config_file(config, value, false); } else if (strncmp(key, "source", 6) == 0) { @@ -3036,6 +3234,26 @@ void free_config(void) { config.gesture_bindings_count = 0; } + if (config.touch_gesture_bindings) { + for (i = 0; i < config.touch_gesture_bindings_count; i++) { + if (config.touch_gesture_bindings[i].arg.v) { + free((void *)config.touch_gesture_bindings[i].arg.v); + config.touch_gesture_bindings[i].arg.v = NULL; + } + if (config.touch_gesture_bindings[i].arg.v2) { + free((void *)config.touch_gesture_bindings[i].arg.v2); + config.touch_gesture_bindings[i].arg.v2 = NULL; + } + if (config.touch_gesture_bindings[i].arg.v3) { + free((void *)config.touch_gesture_bindings[i].arg.v3); + config.touch_gesture_bindings[i].arg.v3 = NULL; + } + } + free(config.touch_gesture_bindings); + config.touch_gesture_bindings = NULL; + config.touch_gesture_bindings_count = 0; + } + // 释放 tag_rules if (config.tag_rules) { for (int32_t i = 0; i < config.tag_rules_count; i++) { @@ -3265,6 +3483,19 @@ void override_config(void) { config.button_map = CLAMP_INT(config.button_map, 0, 1); config.axis_scroll_factor = CLAMP_FLOAT(config.axis_scroll_factor, 0.1f, 10.0f); + config.touch_distance_threshold = + CLAMP_FLOAT(config.touch_distance_threshold, 1.0f, 10000.0f); + config.touch_degrees_leniency = + CLAMP_FLOAT(config.touch_degrees_leniency, 0.0f, 45.0f); + config.touch_timeoutms = CLAMP_INT(config.touch_timeoutms, 1, 60000); + config.touch_edge_size_left = + CLAMP_FLOAT(config.touch_edge_size_left, 1.0f, 10000.0f); + config.touch_edge_size_top = + CLAMP_FLOAT(config.touch_edge_size_top, 1.0f, 10000.0f); + config.touch_edge_size_right = + CLAMP_FLOAT(config.touch_edge_size_right, 1.0f, 10000.0f); + config.touch_edge_size_bottom = + CLAMP_FLOAT(config.touch_edge_size_bottom, 1.0f, 10000.0f); config.gappih = CLAMP_INT(config.gappih, 0, 1000); config.gappiv = CLAMP_INT(config.gappiv, 0, 1000); config.gappoh = CLAMP_INT(config.gappoh, 0, 1000); @@ -3357,6 +3588,13 @@ void set_value_default() { config.scratchpad_cross_monitor = 0; config.focus_cross_tag = 0; config.axis_scroll_factor = 1.0; + config.touch_distance_threshold = 50.0; + config.touch_degrees_leniency = 15.0; + config.touch_timeoutms = 800; + config.touch_edge_size_left = 50.0; + config.touch_edge_size_top = 50.0; + config.touch_edge_size_right = 50.0; + config.touch_edge_size_bottom = 50.0; config.view_current_to_back = 0; config.single_scratchpad = 1; config.xwayland_persistence = 1; @@ -3554,6 +3792,8 @@ bool parse_config(void) { config.switch_bindings_count = 0; config.gesture_bindings = NULL; config.gesture_bindings_count = 0; + config.touch_gesture_bindings = NULL; + config.touch_gesture_bindings_count = 0; config.env = NULL; config.env_count = 0; config.exec = NULL; diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index 6b8d2e9d..8117f558 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -72,6 +72,7 @@ int32_t enable_monitor(const Arg *arg); int32_t toggle_monitor(const Arg *arg); int32_t scroller_stack(const Arg *arg); int32_t canvas_zoom_resize(const Arg *arg); +int32_t canvas_pan(const Arg *arg); int32_t canvas_overview_toggle(const Arg *arg); int32_t canvas_fill_viewport(const Arg *arg); int32_t canvas_centerview(const Arg *arg); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index d870e7e2..4461aa77 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1957,6 +1957,24 @@ int32_t canvas_zoom_resize(const Arg *arg) { return 0; } +int32_t canvas_pan(const Arg *arg) { + if (!selmon || !is_canvas_layout(selmon)) + return 0; + + Client *fs = focustop(selmon); + if (fs && fs->isfullscreen) + return 0; + + uint32_t tag = selmon->pertag->curtag; + float zoom = selmon->pertag->canvas_zoom[tag]; + + selmon->pertag->canvas_pan_x[tag] -= arg->f / zoom; + selmon->pertag->canvas_pan_y[tag] -= arg->f2 / zoom; + + canvas_reposition(selmon); + return 0; +} + int32_t canvas_overview_toggle(const Arg *arg) { if (!selmon || !is_canvas_layout(selmon)) return 0; diff --git a/src/dispatch/gesture.h b/src/dispatch/gesture.h new file mode 100644 index 00000000..1162ba29 --- /dev/null +++ b/src/dispatch/gesture.h @@ -0,0 +1,233 @@ +int32_t gesture_calculate_swipe_within_degrees(double gestdegrees, + double wantdegrees) { + return (gestdegrees >= wantdegrees - config.touch_degrees_leniency && + gestdegrees <= wantdegrees + config.touch_degrees_leniency); +} + +uint32_t gesture_calculate_swipe(double x0, double y0, double x1, double y1) { + double t, degrees, distance; + + t = atan2(x1 - x0, y0 - y1); + degrees = 57.2957795130823209 * (t < 0 ? t + 6.2831853071795865 : t); + distance = sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2)); + + wlr_log(WLR_DEBUG, "Swipe distance=[%.2f]; degrees=[%.2f]", distance, + degrees); + + if (distance < config.touch_distance_threshold) + return TOUCH_SWIPE_NONE; + else if (gesture_calculate_swipe_within_degrees(degrees, 0)) + return TOUCH_SWIPE_UP; + else if (gesture_calculate_swipe_within_degrees(degrees, 45)) + return TOUCH_SWIPE_UP_RIGHT; + else if (gesture_calculate_swipe_within_degrees(degrees, 90)) + return TOUCH_SWIPE_RIGHT; + else if (gesture_calculate_swipe_within_degrees(degrees, 135)) + return TOUCH_SWIPE_DOWN_RIGHT; + else if (gesture_calculate_swipe_within_degrees(degrees, 180)) + return TOUCH_SWIPE_DOWN; + else if (gesture_calculate_swipe_within_degrees(degrees, 225)) + return TOUCH_SWIPE_DOWN_LEFT; + else if (gesture_calculate_swipe_within_degrees(degrees, 270)) + return TOUCH_SWIPE_LEFT; + else if (gesture_calculate_swipe_within_degrees(degrees, 315)) + return TOUCH_SWIPE_UP_LEFT; + else if (gesture_calculate_swipe_within_degrees(degrees, 360)) + return TOUCH_SWIPE_UP; + + return TOUCH_SWIPE_NONE; +} + +uint32_t gesture_calculate_distance(Monitor *m, double x0, double y0, double x1, + double y1, int32_t swipe) { + double distance = sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2)); + double diag = sqrt(pow(m->m.width, 2) + pow(m->m.height, 2)); + switch (swipe) { + case TOUCH_SWIPE_UP: + case TOUCH_SWIPE_DOWN: + if (distance >= m->m.height * 0.66) { + return DISTANCE_LONG; + } else if (distance >= m->m.height * 0.33) { + return DISTANCE_MEDIUM; + } else { + return DISTANCE_SHORT; + } + break; + case TOUCH_SWIPE_RIGHT: + case TOUCH_SWIPE_LEFT: + if (distance >= m->m.width * 0.66) { + return DISTANCE_LONG; + } else if (distance >= m->m.width * 0.33) { + return DISTANCE_MEDIUM; + } else { + return DISTANCE_SHORT; + } + break; + case TOUCH_SWIPE_UP_RIGHT: + case TOUCH_SWIPE_UP_LEFT: + case TOUCH_SWIPE_DOWN_RIGHT: + case TOUCH_SWIPE_DOWN_LEFT: + if (distance >= diag * 0.66) { + return DISTANCE_LONG; + } else if (distance >= diag * 0.33) { + return DISTANCE_MEDIUM; + } else { + return DISTANCE_SHORT; + } + break; + } + + return 0; +} + +uint32_t gesture_calculate_edge(Monitor *m, double x0, double y0, double x1, + double y1) { + uint32_t horizontal = EDGE_NONE; + uint32_t vertical = EDGE_NONE; + + if (x0 <= config.touch_edge_size_left) { + horizontal = EDGE_LEFT; + } else if (x0 >= m->m.width - config.touch_edge_size_right) { + horizontal = EDGE_RIGHT; + } else if (x1 <= config.touch_edge_size_left) { + horizontal = EDGE_LEFT; + } else if (x1 >= m->m.width - config.touch_edge_size_right) { + horizontal = EDGE_RIGHT; + } + if (y0 <= config.touch_edge_size_top) { + vertical = EDGE_TOP; + } else if (y0 >= m->m.height - config.touch_edge_size_bottom) { + vertical = EDGE_BOTTOM; + } else if (y1 <= config.touch_edge_size_top) { + vertical = EDGE_TOP; + } else if (y1 >= m->m.height - config.touch_edge_size_bottom) { + vertical = EDGE_BOTTOM; + } + if (horizontal == EDGE_LEFT && vertical == EDGE_TOP) { + return CORNER_TOP_LEFT; + } else if (horizontal == EDGE_RIGHT && vertical == EDGE_TOP) { + return CORNER_TOP_RIGHT; + } else if (horizontal == EDGE_LEFT && vertical == EDGE_BOTTOM) { + return CORNER_BOTTOM_LEFT; + } else if (horizontal == EDGE_RIGHT && vertical == EDGE_BOTTOM) { + return CORNER_BOTTOM_RIGHT; + } else if (horizontal != EDGE_NONE) { + return horizontal; + } else { + return vertical; + } +} + +int32_t gesture_execute(int32_t nfingers, uint32_t swipe, uint32_t edge, + uint32_t distance) { + int32_t i; + int32_t handled = 0; + + wlr_log(WLR_DEBUG, "f:%d s:%d e:%d d:%d", nfingers, swipe, edge, distance); + + TouchGestureBinding *g; + for (i = 0; i < config.touch_gesture_bindings_count; i++) { + g = &config.touch_gesture_bindings[i]; + if (swipe == g->swipe && nfingers == g->fingers_count && + (distance == g->distance || g->distance == DISTANCE_ANY) && + (g->edge == EDGE_ANY || edge == g->edge || + ((edge == CORNER_TOP_LEFT || edge == CORNER_TOP_RIGHT) && + g->edge == EDGE_TOP) || + ((edge == CORNER_BOTTOM_LEFT || edge == CORNER_BOTTOM_RIGHT) && + g->edge == EDGE_BOTTOM) || + ((edge == CORNER_TOP_LEFT || edge == CORNER_BOTTOM_LEFT) && + g->edge == EDGE_LEFT) || + ((edge == CORNER_TOP_RIGHT || edge == CORNER_BOTTOM_RIGHT) && + g->edge == EDGE_RIGHT)) && + g->func) { + g->func(&g->arg); + handled = 1; + } + } + return handled; +} + +void gesture_consume(TouchGroup *tg, TouchPoint *t) { + if (t->consumed_by_gesture) + return; + + struct timespec now; + struct wlr_touch_cancel_event *event = + ecalloc(1, sizeof(struct wlr_touch_cancel_event)); + + clock_gettime(CLOCK_MONOTONIC_RAW, &now); + + event->touch_id = t->touch_id; + event->touch = tg->touch; + event->time_msec = now.tv_sec * 1000 + now.tv_nsec / 1000000; + + wlr_log(WLR_DEBUG, "gesture_consume id: %d", t->touch_id); + + t->consumed_by_gesture = true; + + handle_touchcancel(event); + + free(event); +} + +void gesture_touch_down(TouchGroup *tg, TouchPoint *t, double x, double y) { + wlr_log(WLR_DEBUG, "touch_down id: %d", t->touch_id); + + t->end_x = x; + t->end_y = y; + + if (wl_list_empty(&tg->touch_points)) + clock_gettime(CLOCK_MONOTONIC_RAW, &tg->time_down); +} + +void gesture_touch_motion(TouchGroup *tg, TouchPoint *t, double x, double y) { + t->end_x = x; + t->end_y = y; +} + +void gesture_touch_up(TouchGroup *tg, TouchPoint *t) { + struct timespec now; + + wlr_log(WLR_DEBUG, "touch_up id: %d", t->touch_id); + wlr_log(WLR_DEBUG, "len: %d", wl_list_length(&tg->touch_points)); + + clock_gettime(CLOCK_MONOTONIC_RAW, &now); + + uint32_t swipe = + gesture_calculate_swipe(t->start_x, t->start_y, t->end_x, t->end_y); + + if (swipe == TOUCH_SWIPE_NONE) { + goto cleanup; + } + + if (tg->touch_points_pending_swipe == 0) { + tg->pending_swipe = swipe; + } + if (tg->pending_swipe != swipe) { + goto cleanup; + } + + tg->touch_points_pending_swipe++; + + if (wl_list_length(&tg->touch_points) == 1) { + uint32_t edge = gesture_calculate_edge(tg->m, t->start_x, t->start_y, + t->end_x, t->end_y); + uint32_t distance = gesture_calculate_distance( + tg->m, t->start_x, t->start_y, t->end_x, t->end_y, swipe); + + if (config.touch_timeoutms > + ((now.tv_sec - tg->time_down.tv_sec) * 1000 + + (now.tv_nsec - tg->time_down.tv_nsec) / 1000000)) { + if (gesture_execute(tg->touch_points_pending_swipe, + tg->pending_swipe, edge, distance)) + gesture_consume(tg, t); + + tg->touch_points_pending_swipe = 0; + } + return; + } + +cleanup: + if (wl_list_length(&tg->touch_points) == 1) + tg->touch_points_pending_swipe = 0; +} diff --git a/src/mango.c b/src/mango.c index 93e8f2a3..961358bf 100644 --- a/src/mango.c +++ b/src/mango.c @@ -75,6 +75,7 @@ #include #include #include +#include #include #include #include @@ -156,6 +157,32 @@ enum { SWIPE_UP, SWIPE_DOWN, SWIPE_LEFT, SWIPE_RIGHT }; enum { CurNormal, CurPressed, CurMove, CurResize, CurPan }; /* cursor */ enum { XDGShell, LayerShell, X11 }; /* client types */ enum { AxisUp, AxisDown, AxisLeft, AxisRight }; // 滚轮滚动的方向 +enum { + TOUCH_SWIPE_UP, + TOUCH_SWIPE_DOWN, + TOUCH_SWIPE_RIGHT, + TOUCH_SWIPE_LEFT, + TOUCH_SWIPE_UP_RIGHT, + TOUCH_SWIPE_UP_LEFT, + TOUCH_SWIPE_DOWN_LEFT, + TOUCH_SWIPE_DOWN_RIGHT, + TOUCH_SWIPE_NONE +}; + +enum { + EDGE_ANY, + EDGE_NONE, + EDGE_LEFT, + EDGE_RIGHT, + EDGE_TOP, + EDGE_BOTTOM, + CORNER_TOP_LEFT, + CORNER_TOP_RIGHT, + CORNER_BOTTOM_LEFT, + CORNER_BOTTOM_RIGHT, +}; + +enum { DISTANCE_ANY, DISTANCE_SHORT, DISTANCE_MEDIUM, DISTANCE_LONG }; enum { LyrBg, LyrBlur, @@ -460,6 +487,23 @@ typedef struct { struct wl_listener destroy; } KeyboardGroup; +typedef struct { + struct wl_list link; + int32_t touch_id; + double start_x, start_y, end_x, end_y, start_surface_x, start_surface_y; + bool consumed_by_gesture; +} TouchPoint; + +typedef struct TouchGroup { + struct wl_list link; + struct wlr_touch *touch; + struct wl_list touch_points; + struct timespec time_down; + uint32_t pending_swipe; + uint32_t touch_points_pending_swipe; + Monitor *m; +} TouchGroup; + typedef struct { struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor; struct wl_listener destroy; @@ -624,6 +668,7 @@ static void createpointerconstraint(struct wl_listener *listener, void *data); static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); static void commitpopup(struct wl_listener *listener, void *data); static void createpopup(struct wl_listener *listener, void *data); +static void createtouch(struct wlr_touch *touch); static void cursorframe(struct wl_listener *listener, void *data); static void cursorwarptohint(void); static void destroydecoration(struct wl_listener *listener, void *data); @@ -700,6 +745,13 @@ static void setsel(struct wl_listener *listener, void *data); static void setup(void); static void startdrag(struct wl_listener *listener, void *data); +static void touchdown(struct wl_listener *listener, void *data); +static void touchup(struct wl_listener *listener, void *data); +static void touchframe(struct wl_listener *listener, void *data); +static void touchmotion(struct wl_listener *listener, void *data); +static void touchcancel(struct wl_listener *listener, void *data); +static void handle_touchcancel(struct wlr_touch_cancel_event *event); + static void unlocksession(struct wl_listener *listener, void *data); static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); static void unmapnotify(struct wl_listener *listener, void *data); @@ -913,6 +965,10 @@ static struct wlr_output_layout *output_layout; static struct wlr_box sgeom; static struct wl_list mons; static Monitor *selmon; +static struct wl_list touch_groups; + +static bool emulating_pointer_from_touch = false; +static int32_t emulated_pointer_touch_id; static int32_t enablegaps = 1; /* enables gaps, used by togglegaps */ static int32_t axis_apply_time = 0; @@ -1019,6 +1075,11 @@ static struct wl_listener request_set_sel = {.notify = setsel}; static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; static struct wl_listener request_start_drag = {.notify = requeststartdrag}; static struct wl_listener start_drag = {.notify = startdrag}; +static struct wl_listener touch_down = {.notify = touchdown}; +static struct wl_listener touch_frame = {.notify = touchframe}; +static struct wl_listener touch_motion = {.notify = touchmotion}; +static struct wl_listener touch_up = {.notify = touchup}; +static struct wl_listener touch_cancel = {.notify = touchcancel}; static struct wl_listener new_session_lock = {.notify = locksession}; static struct wl_listener drm_lease_request = {.notify = requestdrmlease}; static struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor = { @@ -1050,12 +1111,13 @@ static struct wl_event_source *sync_keymap; #include "animation/tag.h" static void canvas_reposition(Monitor *m); static void canvas_pan_to_client(Monitor *m, Client *c); -#include "layout/dwindle.h" #include "dispatch/bind_define.h" +#include "dispatch/gesture.h" #include "ext-protocol/all.h" #include "fetch/fetch.h" #include "layout/arrange.h" #include "layout/canvas.h" +#include "layout/dwindle.h" #include "layout/horizontal.h" #include "layout/vertical.h" @@ -2227,7 +2289,11 @@ bool handle_buttonpress(struct wlr_pointer_button_event *event) { struct wlr_surface *old_pointer_focus_surface = seat->pointer_state.focused_surface; - handlecursoractivity(); + if (!event->pointer || + event->pointer->base.type != WLR_INPUT_DEVICE_TOUCH) { + handlecursoractivity(); + } + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); if (event->pointer && check_trackpad_disabled(event->pointer)) { @@ -2501,6 +2567,11 @@ void cleanuplisteners(void) { wl_list_remove(&request_set_cursor_shape.link); wl_list_remove(&request_start_drag.link); wl_list_remove(&start_drag.link); + wl_list_remove(&touch_down.link); + wl_list_remove(&touch_frame.link); + wl_list_remove(&touch_motion.link); + wl_list_remove(&touch_up.link); + wl_list_remove(&touch_cancel.link); wl_list_remove(&new_session_lock.link); wl_list_remove(&tearing_new_object.link); wl_list_remove(&keyboard_shortcuts_inhibit_new_inhibitor.link); @@ -3596,6 +3667,16 @@ void createpointerconstraint(struct wl_listener *listener, void *data) { &pointer_constraint->destroy, destroypointerconstraint); } +void createtouch(struct wlr_touch *wlr_touch) { + TouchGroup *touch = ecalloc(1, sizeof(TouchGroup)); + + touch->touch = wlr_touch; + wl_list_init(&touch->touch_points); + wl_list_insert(&touch_groups, &touch->link); + wlr_touch->data = touch; + wlr_cursor_attach_input_device(cursor, &wlr_touch->base); +} + void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) { if (active_constraint == constraint) return; @@ -3962,6 +4043,9 @@ void inputdevice(struct wl_listener *listener, void *data) { case WLR_INPUT_DEVICE_SWITCH: createswitch(wlr_switch_from_input_device(device)); break; + case WLR_INPUT_DEVICE_TOUCH: + createtouch(wlr_touch_from_input_device(device)); + break; default: /* TODO handle other input device types */ break; @@ -3975,6 +4059,8 @@ void inputdevice(struct wl_listener *listener, void *data) { caps = WL_SEAT_CAPABILITY_POINTER; if (!wl_list_empty(&kb_group->wlr_group->devices)) caps |= WL_SEAT_CAPABILITY_KEYBOARD; + if (!wl_list_empty(&touch_groups)) + caps |= WL_SEAT_CAPABILITY_TOUCH; wlr_seat_set_capabilities(seat, caps); } @@ -4721,7 +4807,9 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, } wlr_cursor_move(cursor, device, dx, dy); - handlecursoractivity(); + if (!device || device->type != WLR_INPUT_DEVICE_TOUCH) { + handlecursoractivity(); + } wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); /* Update selmon (even while dragging a window) */ @@ -6666,6 +6754,14 @@ void setup(void) { wl_signal_add(&cursor->events.tablet_tool_button, &tablet_tool_button); wl_signal_add(&cursor->events.tablet_tool_tip, &tablet_tool_tip); + wl_list_init(&touch_groups); + + wl_signal_add(&cursor->events.touch_down, &touch_down); + wl_signal_add(&cursor->events.touch_frame, &touch_frame); + wl_signal_add(&cursor->events.touch_motion, &touch_motion); + wl_signal_add(&cursor->events.touch_up, &touch_up); + wl_signal_add(&cursor->events.touch_cancel, &touch_cancel); + // 这两句代码会造成obs窗口里的鼠标光标消失,不知道注释有什么影响 cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); wl_signal_add(&cursor_shape_mgr->events.request_set_shape, @@ -6782,6 +6878,256 @@ void startdrag(struct wl_listener *listener, void *data) { LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); } +void touchdown(struct wl_listener *listener, void *data) { + struct wlr_touch_down_event *event = data; + TouchGroup *tg = event->touch->data; + TouchPoint *t = ecalloc(1, sizeof(TouchPoint)); + double lx, ly; + double sx, sy; + double dx, dy; + struct wlr_surface *surface; + Client *c = NULL; + Monitor *m = NULL; + Monitor *m_iter; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + if (!cursor_hidden) { + hidecursor(NULL); + } + + wl_list_for_each(m_iter, &mons, link) { + if (m_iter == NULL || m_iter->wlr_output == NULL) { + continue; + } + if (event->touch->output_name != NULL && + 0 != strcmp(event->touch->output_name, m->wlr_output->name)) { + continue; + } + + wlr_cursor_map_input_to_output(cursor, &event->touch->base, + m_iter->wlr_output); + m = m_iter; + break; + } + + if (!tg->m) + tg->m = m; // TODO: properly handle output_name = null, instead of + // falling back to last monitor in the list; + + wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, + event->y, &lx, &ly); + + t->touch_id = event->touch_id; + t->start_x = lx; + t->start_y = ly; + gesture_touch_down(tg, t, lx, ly); + wl_list_insert(&tg->touch_points, &t->link); + + xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); + t->start_surface_x = sx; + t->start_surface_y = sy; + if (surface != NULL && wlr_surface_accepts_touch(surface, seat)) { + if (c) + focusclient(c, 0); + + wlr_seat_touch_notify_down(seat, surface, event->time_msec, + event->touch_id, sx, sy); + emulating_pointer_from_touch = false; + return; + } + + if (!emulating_pointer_from_touch) { + emulating_pointer_from_touch = true; + emulated_pointer_touch_id = event->touch_id; + + wlr_cursor_warp_closest(cursor, &event->touch->base, lx, ly); + dx = lx - cursor->x; + dy = ly - cursor->y; + motionnotify(event->time_msec, &event->touch->base, dx, dy, dx, dy); + + struct wlr_pointer_button_event button_event = { + .pointer = (struct wlr_pointer *)event->touch, + .time_msec = event->time_msec, + .button = BTN_LEFT, + .state = WL_POINTER_BUTTON_STATE_PRESSED}; + buttonpress(NULL, &button_event); + } +} + +void touchup(struct wl_listener *listener, void *data) { + struct wlr_touch_up_event *event = data; + TouchGroup *tg = event->touch->data; + TouchPoint *t = NULL; + TouchPoint *t_iter; + + wl_list_for_each(t_iter, &tg->touch_points, link) { + if (t_iter->touch_id == event->touch_id) { + t = t_iter; + break; + } + } + if (!t) + return; + + gesture_touch_up(tg, t); + + if (t->consumed_by_gesture) { + wl_list_remove(&t->link); + free(t); + return; + } + + wl_list_remove(&t->link); + free(t); + + if (emulating_pointer_from_touch) { + if (emulated_pointer_touch_id == event->touch_id) { + struct wlr_pointer_button_event button_event = { + .pointer = (struct wlr_pointer *)event->touch, + .time_msec = event->time_msec, + .button = BTN_LEFT, + .state = WL_POINTER_BUTTON_STATE_RELEASED}; + buttonpress(NULL, &button_event); + + emulating_pointer_from_touch = false; + } + return; + } + + if (!wlr_seat_touch_get_point(seat, event->touch_id)) { + return; + } + + wlr_seat_touch_notify_up(seat, event->time_msec, event->touch_id); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +} + +void touchframe(struct wl_listener *listener, void *data) { + if (emulating_pointer_from_touch) { + wlr_seat_pointer_notify_frame(seat); + } else { + wlr_seat_touch_notify_frame(seat); + } + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +} + +void touchmotion(struct wl_listener *listener, void *data) { + struct wlr_touch_motion_event *event = data; + TouchGroup *tg = event->touch->data; + TouchPoint *t = NULL; + TouchPoint *t_iter; + double lx, ly; + double sx, sy; + double dx, dy; + struct wlr_surface *surface; + Client *c = NULL; + struct wlr_touch_point *p = NULL; + + wl_list_for_each(t_iter, &tg->touch_points, link) { + if (t_iter->touch_id == event->touch_id) { + t = t_iter; + break; + } + } + if (!t) + return; + + wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, + event->y, &lx, &ly); + + gesture_touch_motion(tg, t, lx, ly); + + if (emulating_pointer_from_touch) { + if (emulated_pointer_touch_id == event->touch_id) { + dx = lx - cursor->x; + dy = ly - cursor->y; + motionnotify(event->time_msec, &event->touch->base, dx, dy, dx, dy); + } + return; + } + + p = wlr_seat_touch_get_point(seat, event->touch_id); + + if (!p) { + return; + } + + sx = t->start_surface_x + (lx - t->start_x); + sy = t->start_surface_y + (ly - t->start_y); + + surface = p->surface; + if (surface && surface->data) { + toplevel_from_wlr_surface(surface, &c, NULL); + if (c) + focusclient(c, 0); + } + wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, sx, + sy); + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +} + +void touchcancel(struct wl_listener *listener, void *data) { + struct wlr_touch_cancel_event *event = data; + TouchGroup *tg = event->touch->data; + TouchPoint *t = NULL; + TouchPoint *t_iter; + + wl_list_for_each(t_iter, &tg->touch_points, link) { + if (t_iter->touch_id == event->touch_id) { + t = t_iter; + break; + } + } + if (!t) + return; + + wl_list_remove(&t->link); + free(t); + + if (wl_list_length(&tg->touch_points) == 0) + tg->touch_points_pending_swipe = 0; + + handle_touchcancel(event); +} + +void handle_touchcancel(struct wlr_touch_cancel_event *event) { + struct wlr_touch_point *p = NULL; + struct wl_client *client = NULL; + struct wlr_seat_client *seat_client = NULL; + + if (emulating_pointer_from_touch) { + if (emulated_pointer_touch_id == event->touch_id) { + struct wlr_pointer_button_event button_event = { + .pointer = (struct wlr_pointer *)event->touch, + .time_msec = event->time_msec, + .button = BTN_LEFT, + .state = WL_POINTER_BUTTON_STATE_RELEASED}; + buttonpress(NULL, &button_event); + + emulating_pointer_from_touch = false; + } + return; + } + + p = wlr_seat_touch_get_point(seat, event->touch_id); + + if (!p) { + return; + } + + if (p->surface) { + client = wl_resource_get_client(p->surface->resource); + seat_client = wlr_seat_client_for_wl_client(seat, client); + if (seat_client != NULL) { + wlr_seat_touch_notify_cancel(seat, seat_client); + } + } + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +} + void tag_client(const Arg *arg, Client *target_client) { Client *fc = NULL; if (target_client && arg->ui & TAGMASK) {