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
|
- Unicode input kitten: Implement scrolling when more results are found than
|
||||||
the available display space (:pull:`4068`)
|
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
|
- The command line option ``--watcher`` has been deprecated in favor of the
|
||||||
:opt:`watcher` option in :file:`kitty.conf`. It has the advantage of
|
:opt:`watcher` option in :file:`kitty.conf`. It has the advantage of
|
||||||
applying to all windows, not just the initially created ones. Note that
|
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)
|
run_update_check(get_options().update_check_interval * 60 * 60)
|
||||||
self.update_check_started = True
|
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)
|
tm = self.os_window_map.get(os_window_id)
|
||||||
if tm is not None:
|
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:
|
def on_window_resize(self, os_window_id: int, w: int, h: int, dpi_changed: bool) -> None:
|
||||||
if dpi_changed:
|
if dpi_changed:
|
||||||
|
|||||||
@ -1254,3 +1254,7 @@ def num_users() -> int:
|
|||||||
|
|
||||||
def redirect_mouse_handling(yes: bool) -> None:
|
def redirect_mouse_handling(yes: bool) -> None:
|
||||||
pass
|
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);
|
unsigned long long notification_id = glfwDBusUserNotify(app_name, icon, summary, body, action_name, timeout, dbus_notification_created_callback, NULL);
|
||||||
return PyLong_FromUnsignedLongLong(notification_id);
|
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
|
#endif
|
||||||
|
|
||||||
id_type
|
id_type
|
||||||
@ -1472,6 +1477,7 @@ static PyMethodDef module_methods[] = {
|
|||||||
METHODB(glfw_window_hint, METH_VARARGS),
|
METHODB(glfw_window_hint, METH_VARARGS),
|
||||||
METHODB(get_primary_selection, METH_NOARGS),
|
METHODB(get_primary_selection, METH_NOARGS),
|
||||||
METHODB(x11_display, METH_NOARGS),
|
METHODB(x11_display, METH_NOARGS),
|
||||||
|
METHODB(get_click_interval, METH_NOARGS),
|
||||||
METHODB(x11_window_id, METH_O),
|
METHODB(x11_window_id, METH_O),
|
||||||
METHODB(set_primary_selection, METH_VARARGS),
|
METHODB(set_primary_selection, METH_VARARGS),
|
||||||
#ifndef __APPLE__
|
#ifndef __APPLE__
|
||||||
|
|||||||
@ -515,13 +515,10 @@ HANDLER(handle_event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
handle_tab_bar_mouse(int button, int UNUSED modifiers) {
|
handle_tab_bar_mouse(int button, int modifiers, int action) {
|
||||||
static monotonic_t last_click_at = 0;
|
if (button > -1) { // dont report motion events, as they are expensive and useless
|
||||||
if (button != GLFW_MOUSE_BUTTON_LEFT || !global_state.callback_os_window->mouse_button_pressed[button]) return;
|
call_boss(handle_click_on_tab, "Kdiii", global_state.callback_os_window->id, global_state.callback_os_window->mouse_x, button, modifiers, action);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@ -737,7 +734,7 @@ mouse_event(const int button, int modifiers, int action) {
|
|||||||
w = window_for_event(&window_idx, &in_tab_bar);
|
w = window_for_event(&window_idx, &in_tab_bar);
|
||||||
if (in_tab_bar) {
|
if (in_tab_bar) {
|
||||||
mouse_cursor_shape = HAND;
|
mouse_cursor_shape = HAND;
|
||||||
handle_tab_bar_mouse(button, modifiers);
|
handle_tab_bar_mouse(button, modifiers, action);
|
||||||
debug("handled by tab bar\n");
|
debug("handled by tab bar\n");
|
||||||
} else if (w) {
|
} else if (w) {
|
||||||
debug("grabbed: %d\n", w->render_data.screen->modes.mouse_tracking_mode != 0);
|
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 contextlib import suppress
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
from time import monotonic
|
||||||
from typing import (
|
from typing import (
|
||||||
Any, Deque, Dict, Generator, Iterable, Iterator, List, NamedTuple,
|
Any, Deque, Dict, Generator, Iterable, Iterator, List, NamedTuple,
|
||||||
Optional, Pattern, Sequence, Tuple, Union, cast
|
Optional, Pattern, Sequence, Tuple, Union, cast
|
||||||
@ -18,9 +19,11 @@ from .child import Child
|
|||||||
from .cli_stub import CLIOptions
|
from .cli_stub import CLIOptions
|
||||||
from .constants import appname, kitty_exe
|
from .constants import appname, kitty_exe
|
||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
add_tab, attach_window, detach_window, get_boss, get_options,
|
GLFW_MOUSE_BUTTON_LEFT, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, GLFW_RELEASE,
|
||||||
mark_tab_bar_dirty, next_window_id, remove_tab, remove_window, ring_bell,
|
add_tab, attach_window, detach_window, get_boss, get_click_interval,
|
||||||
set_active_tab, set_active_window, swap_tabs, sync_os_window_title
|
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.base import Layout
|
||||||
from .layout.interface import create_layout_object_for, evict_cached_layouts
|
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
|
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):
|
class TabDict(TypedDict):
|
||||||
id: int
|
id: int
|
||||||
is_focused: bool
|
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):
|
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.os_window_id = os_window_id
|
||||||
self.wm_class = wm_class
|
self.wm_class = wm_class
|
||||||
|
self.recent_mouse_events: Deque[TabMouseEvent] = deque()
|
||||||
self.wm_name = wm_name
|
self.wm_name = wm_name
|
||||||
self.last_active_tab_id = None
|
self.last_active_tab_id = None
|
||||||
self.args = args
|
self.args = args
|
||||||
@ -965,13 +977,32 @@ class TabManager: # {{{
|
|||||||
))
|
))
|
||||||
return ans
|
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)
|
i = self.tab_bar.tab_at(x)
|
||||||
|
now = monotonic()
|
||||||
if i is None:
|
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.new_tab()
|
||||||
|
self.recent_mouse_events.clear()
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
|
if action == GLFW_PRESS:
|
||||||
|
if button == GLFW_MOUSE_BUTTON_LEFT:
|
||||||
self.set_active_tab_idx(i)
|
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
|
@property
|
||||||
def tab_bar_rects(self) -> Tuple[Border, ...]:
|
def tab_bar_rects(self) -> Tuple[Border, ...]:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user