parent
6c7420f4e7
commit
b316e97a4f
@ -76,6 +76,8 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
- Unicode input kitten: Implement scrolling when more results are found than
|
||||
the available display space (:pull:`4068`)
|
||||
|
||||
- Allow middle clicking on a tab to close it (:iss:`4151`)
|
||||
|
||||
- The command line option ``--watcher`` has been deprecated in favor of the
|
||||
:opt:`watcher` option in :file:`kitty.conf`. It has the advantage of
|
||||
applying to all windows, not just the initially created ones. Note that
|
||||
|
||||
@ -591,10 +591,10 @@ class Boss:
|
||||
run_update_check(get_options().update_check_interval * 60 * 60)
|
||||
self.update_check_started = True
|
||||
|
||||
def activate_tab_at(self, os_window_id: int, x: int, is_double: bool = False) -> int:
|
||||
def handle_click_on_tab(self, os_window_id: int, x: int, button: int, modifiers: int, action: int) -> int:
|
||||
tm = self.os_window_map.get(os_window_id)
|
||||
if tm is not None:
|
||||
tm.activate_tab_at(x, is_double)
|
||||
tm.handle_click_on_tab(x, button, modifiers, action)
|
||||
|
||||
def on_window_resize(self, os_window_id: int, w: int, h: int, dpi_changed: bool) -> None:
|
||||
if dpi_changed:
|
||||
|
||||
@ -1254,3 +1254,7 @@ def num_users() -> int:
|
||||
|
||||
def redirect_mouse_handling(yes: bool) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def get_click_interval() -> float:
|
||||
pass
|
||||
|
||||
@ -1424,6 +1424,11 @@ dbus_send_notification(PyObject *self UNUSED, PyObject *args) {
|
||||
unsigned long long notification_id = glfwDBusUserNotify(app_name, icon, summary, body, action_name, timeout, dbus_notification_created_callback, NULL);
|
||||
return PyLong_FromUnsignedLongLong(notification_id);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
get_click_interval(PyObject *self UNUSED, PyObject *args UNUSED) {
|
||||
return PyFloat_FromDouble(monotonic_t_to_s_double(OPT(click_interval)));
|
||||
}
|
||||
#endif
|
||||
|
||||
id_type
|
||||
@ -1472,6 +1477,7 @@ static PyMethodDef module_methods[] = {
|
||||
METHODB(glfw_window_hint, METH_VARARGS),
|
||||
METHODB(get_primary_selection, METH_NOARGS),
|
||||
METHODB(x11_display, METH_NOARGS),
|
||||
METHODB(get_click_interval, METH_NOARGS),
|
||||
METHODB(x11_window_id, METH_O),
|
||||
METHODB(set_primary_selection, METH_VARARGS),
|
||||
#ifndef __APPLE__
|
||||
|
||||
@ -515,13 +515,10 @@ HANDLER(handle_event) {
|
||||
}
|
||||
|
||||
static void
|
||||
handle_tab_bar_mouse(int button, int UNUSED modifiers) {
|
||||
static monotonic_t last_click_at = 0;
|
||||
if (button != GLFW_MOUSE_BUTTON_LEFT || !global_state.callback_os_window->mouse_button_pressed[button]) return;
|
||||
monotonic_t now = monotonic();
|
||||
bool is_double = now - last_click_at <= OPT(click_interval);
|
||||
last_click_at = is_double ? 0 : now;
|
||||
call_boss(activate_tab_at, "KdO", global_state.callback_os_window->id, global_state.callback_os_window->mouse_x, is_double ? Py_True : Py_False);
|
||||
handle_tab_bar_mouse(int button, int modifiers, int action) {
|
||||
if (button > -1) { // dont report motion events, as they are expensive and useless
|
||||
call_boss(handle_click_on_tab, "Kdiii", global_state.callback_os_window->id, global_state.callback_os_window->mouse_x, button, modifiers, action);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
@ -737,7 +734,7 @@ mouse_event(const int button, int modifiers, int action) {
|
||||
w = window_for_event(&window_idx, &in_tab_bar);
|
||||
if (in_tab_bar) {
|
||||
mouse_cursor_shape = HAND;
|
||||
handle_tab_bar_mouse(button, modifiers);
|
||||
handle_tab_bar_mouse(button, modifiers, action);
|
||||
debug("handled by tab bar\n");
|
||||
} else if (w) {
|
||||
debug("grabbed: %d\n", w->render_data.screen->modes.mouse_tracking_mode != 0);
|
||||
|
||||
@ -8,6 +8,7 @@ from collections import deque
|
||||
from contextlib import suppress
|
||||
from functools import partial
|
||||
from operator import attrgetter
|
||||
from time import monotonic
|
||||
from typing import (
|
||||
Any, Deque, Dict, Generator, Iterable, Iterator, List, NamedTuple,
|
||||
Optional, Pattern, Sequence, Tuple, Union, cast
|
||||
@ -18,9 +19,11 @@ from .child import Child
|
||||
from .cli_stub import CLIOptions
|
||||
from .constants import appname, kitty_exe
|
||||
from .fast_data_types import (
|
||||
add_tab, attach_window, detach_window, get_boss, get_options,
|
||||
mark_tab_bar_dirty, next_window_id, remove_tab, remove_window, ring_bell,
|
||||
set_active_tab, set_active_window, swap_tabs, sync_os_window_title
|
||||
GLFW_MOUSE_BUTTON_LEFT, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, GLFW_RELEASE,
|
||||
add_tab, attach_window, detach_window, get_boss, get_click_interval,
|
||||
get_options, mark_tab_bar_dirty, next_window_id, remove_tab, remove_window,
|
||||
ring_bell, set_active_tab, set_active_window, swap_tabs,
|
||||
sync_os_window_title
|
||||
)
|
||||
from .layout.base import Layout
|
||||
from .layout.interface import create_layout_object_for, evict_cached_layouts
|
||||
@ -32,6 +35,14 @@ from .window import Watchers, Window, WindowDict
|
||||
from .window_list import WindowList
|
||||
|
||||
|
||||
class TabMouseEvent(NamedTuple):
|
||||
button: int = -1
|
||||
modifiers: int = 0
|
||||
action: int = GLFW_PRESS
|
||||
at: float = -1000.
|
||||
tab_idx: Optional[int] = None
|
||||
|
||||
|
||||
class TabDict(TypedDict):
|
||||
id: int
|
||||
is_focused: bool
|
||||
@ -657,6 +668,7 @@ class TabManager: # {{{
|
||||
def __init__(self, os_window_id: int, args: CLIOptions, wm_class: str, wm_name: str, startup_session: Optional[SessionType] = None):
|
||||
self.os_window_id = os_window_id
|
||||
self.wm_class = wm_class
|
||||
self.recent_mouse_events: Deque[TabMouseEvent] = deque()
|
||||
self.wm_name = wm_name
|
||||
self.last_active_tab_id = None
|
||||
self.args = args
|
||||
@ -965,13 +977,32 @@ class TabManager: # {{{
|
||||
))
|
||||
return ans
|
||||
|
||||
def activate_tab_at(self, x: int, is_double: bool = False) -> None:
|
||||
def handle_click_on_tab(self, x: int, button: int, modifiers: int, action: int) -> None:
|
||||
i = self.tab_bar.tab_at(x)
|
||||
now = monotonic()
|
||||
if i is None:
|
||||
if is_double:
|
||||
if button == GLFW_MOUSE_BUTTON_LEFT and action == GLFW_PRESS and len(self.recent_mouse_events) > 1:
|
||||
ci = get_click_interval()
|
||||
prev, prev2 = self.recent_mouse_events[-1], self.recent_mouse_events[-2]
|
||||
if (
|
||||
prev.button == button and prev2.button == button and
|
||||
prev.action == GLFW_RELEASE and prev2.action == GLFW_PRESS and
|
||||
prev.tab_idx is None and prev2.tab_idx is None and
|
||||
now - prev.at <= ci and now - prev2.at <= 2 * ci
|
||||
): # double click
|
||||
self.new_tab()
|
||||
self.recent_mouse_events.clear()
|
||||
return
|
||||
else:
|
||||
if action == GLFW_PRESS:
|
||||
if button == GLFW_MOUSE_BUTTON_LEFT:
|
||||
self.set_active_tab_idx(i)
|
||||
elif button == GLFW_MOUSE_BUTTON_MIDDLE:
|
||||
tab = self.tabs[i]
|
||||
get_boss().close_tab(tab)
|
||||
self.recent_mouse_events.append(TabMouseEvent(button, modifiers, action, now, i))
|
||||
if len(self.recent_mouse_events) > 5:
|
||||
self.recent_mouse_events.popleft()
|
||||
|
||||
@property
|
||||
def tab_bar_rects(self) -> Tuple[Border, ...]:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user