Auto-hide mouse cursor when unused
This commit is contained in:
parent
02fec34629
commit
c9b34e98f9
@ -48,6 +48,8 @@ type_map = {
|
||||
'window_border_width': float,
|
||||
'wheel_scroll_multiplier': float,
|
||||
'click_interval': float,
|
||||
'mouse_hide_wait': float,
|
||||
'cursor_stop_blinking_after': float,
|
||||
}
|
||||
|
||||
for name in 'foreground background cursor active_border_color inactive_border_color selection_foreground selection_background'.split():
|
||||
|
||||
@ -239,6 +239,14 @@ set_should_close(Window *self, PyObject *args) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
set_input_mode(Window *self, PyObject *args) {
|
||||
int which, value;
|
||||
if (!PyArg_ParseTuple(args, "ii", &which, &value)) return NULL;
|
||||
glfwSetInputMode(self->window, which, value);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
is_key_pressed(Window *self, PyObject *args) {
|
||||
int c;
|
||||
@ -273,6 +281,7 @@ static PyMethodDef methods[] = {
|
||||
MND(get_cursor_pos, METH_NOARGS),
|
||||
MND(should_close, METH_NOARGS),
|
||||
MND(set_should_close, METH_VARARGS),
|
||||
MND(set_input_mode, METH_VARARGS),
|
||||
MND(is_key_pressed, METH_VARARGS),
|
||||
MND(set_click_cursor, METH_VARARGS),
|
||||
MND(make_context_current, METH_NOARGS),
|
||||
|
||||
@ -43,6 +43,14 @@ click_interval 0.5
|
||||
# that sufficient for most uses.
|
||||
repaint_delay 20
|
||||
|
||||
# Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to
|
||||
# zero or a negative number to disable mouse cursor hiding.
|
||||
mouse_hide_wait 3.0
|
||||
|
||||
# Stop blinking cursor after the specified number of seconds of keyboard inactivity. Set to
|
||||
# zero or a negative number to never stop blinking.
|
||||
cursor_stop_blinking_after 5.0
|
||||
|
||||
# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
|
||||
window_border_width 2
|
||||
|
||||
|
||||
109
kitty/tabs.py
109
kitty/tabs.py
@ -7,8 +7,9 @@ import io
|
||||
import select
|
||||
import signal
|
||||
import struct
|
||||
import inspect
|
||||
from collections import deque
|
||||
from functools import partial
|
||||
from functools import wraps
|
||||
from threading import Thread, current_thread
|
||||
from queue import Queue, Empty
|
||||
|
||||
@ -16,7 +17,8 @@ from .child import Child
|
||||
from .constants import viewport_size, shell_path, appname, set_tab_manager, tab_manager, wakeup, cell_size, MODIFIER_KEYS, main_thread
|
||||
from .fast_data_types import (
|
||||
glViewport, glBlendFunc, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GLFW_PRESS,
|
||||
GLFW_REPEAT, GLFW_MOUSE_BUTTON_1, glfw_post_empty_event
|
||||
GLFW_REPEAT, GLFW_MOUSE_BUTTON_1, glfw_post_empty_event,
|
||||
GLFW_CURSOR_NORMAL, GLFW_CURSOR, GLFW_CURSOR_HIDDEN
|
||||
)
|
||||
from .fonts import set_font_family
|
||||
from .borders import Borders, BordersProgram
|
||||
@ -29,6 +31,32 @@ from .utils import handle_unix_signals
|
||||
from .window import Window
|
||||
|
||||
|
||||
def conditional_run(w, i):
|
||||
if w is None or not w.destroyed:
|
||||
next(i, None)
|
||||
|
||||
|
||||
def callback(func):
|
||||
''' Wrapper for function that executes first half (up to a yield statement)
|
||||
in the UI thread and the rest in the child thread. If the function yields
|
||||
something, the destroyed attribute of that something is checked before
|
||||
running the second half. If the function returns before the yield, the
|
||||
second half is not run. '''
|
||||
|
||||
assert inspect.isgeneratorfunction(func)
|
||||
|
||||
@wraps(func)
|
||||
def f(self, *a):
|
||||
i = func(self, *a)
|
||||
try:
|
||||
w = next(i)
|
||||
except StopIteration:
|
||||
pass
|
||||
else:
|
||||
self.queue_action(conditional_run, w, i)
|
||||
return f
|
||||
|
||||
|
||||
class Tab:
|
||||
|
||||
def __init__(self, opts, args):
|
||||
@ -120,13 +148,13 @@ class TabManager(Thread):
|
||||
cell_size.width, cell_size.height = set_font_family(opts.font_family, opts.font_size)
|
||||
self.opts, self.args = opts, args
|
||||
self.glfw_window = glfw_window
|
||||
glfw_window.framebuffer_size_callback = partial(self.queue_action, self.on_window_resize)
|
||||
glfw_window.char_mods_callback = partial(self.queue_action, self.on_text_input)
|
||||
glfw_window.key_callback = partial(self.queue_action, self.on_key)
|
||||
glfw_window.mouse_button_callback = partial(self.queue_action, self.on_mouse_button)
|
||||
glfw_window.scroll_callback = partial(self.queue_action, self.on_mouse_scroll)
|
||||
glfw_window.cursor_pos_callback = partial(self.queue_action, self.on_mouse_move)
|
||||
glfw_window.window_focus_callback = partial(self.queue_action, self.on_focus)
|
||||
glfw_window.framebuffer_size_callback = self.on_window_resize
|
||||
glfw_window.char_mods_callback = self.on_text_input
|
||||
glfw_window.key_callback = self.on_key
|
||||
glfw_window.mouse_button_callback = self.on_mouse_button
|
||||
glfw_window.scroll_callback = self.on_mouse_scroll
|
||||
glfw_window.cursor_pos_callback = self.on_mouse_move
|
||||
glfw_window.window_focus_callback = self.on_focus
|
||||
self.tabs = deque()
|
||||
self.tabs.append(Tab(opts, args))
|
||||
self.sprites = Sprites()
|
||||
@ -137,6 +165,7 @@ class TabManager(Thread):
|
||||
self.sprites.do_layout(cell_size.width, cell_size.height)
|
||||
self.queue_action(self.active_tab.new_window, False)
|
||||
self.glfw_window.set_click_cursor(False)
|
||||
self.show_mouse_cursor()
|
||||
|
||||
def signal_received(self):
|
||||
try:
|
||||
@ -230,10 +259,12 @@ class TabManager(Thread):
|
||||
if w.screen.is_dirty():
|
||||
self.timers.add_if_missing(self.screen_update_delay, w.update_screen)
|
||||
|
||||
@callback
|
||||
def on_window_resize(self, window, w, h):
|
||||
# debounce resize events
|
||||
self.timers.add(0.02, self.apply_pending_resize, w, h)
|
||||
self.pending_resize = True
|
||||
yield
|
||||
self.timers.add(0.02, self.apply_pending_resize, w, h)
|
||||
|
||||
def apply_pending_resize(self, w, h):
|
||||
viewport_size.width, viewport_size.height = w, h
|
||||
@ -256,25 +287,35 @@ class TabManager(Thread):
|
||||
if t is not None:
|
||||
return t.active_window
|
||||
|
||||
@callback
|
||||
def on_text_input(self, window, codepoint, mods):
|
||||
data = interpret_text_event(codepoint, mods)
|
||||
if data:
|
||||
w = self.active_window
|
||||
if w is not None:
|
||||
yield w
|
||||
w.write_to_child(data)
|
||||
|
||||
@callback
|
||||
def on_key(self, window, key, scancode, action, mods):
|
||||
if action == GLFW_PRESS or action == GLFW_REPEAT:
|
||||
func = get_shortcut(self.opts.keymap, mods, key)
|
||||
tab = self.active_tab
|
||||
window = self.active_window
|
||||
if func is not None:
|
||||
func = getattr(self, func, getattr(tab, func, getattr(window, func, None)))
|
||||
if func is not None:
|
||||
passthrough = func()
|
||||
f = getattr(self, func, getattr(tab, func, None))
|
||||
if f is not None:
|
||||
passthrough = f()
|
||||
if not passthrough:
|
||||
return
|
||||
window = self.active_window
|
||||
if window is not None:
|
||||
yield window
|
||||
if func is not None:
|
||||
f = getattr(window, func, None)
|
||||
if f is not None:
|
||||
passthrough = f()
|
||||
if not passthrough:
|
||||
return
|
||||
if window:
|
||||
if window.screen.auto_repeat_enabled() or action == GLFW_PRESS:
|
||||
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS:
|
||||
window.scroll_end()
|
||||
@ -282,34 +323,64 @@ class TabManager(Thread):
|
||||
if data:
|
||||
window.write_to_child(data)
|
||||
|
||||
@callback
|
||||
def on_focus(self, window, focused):
|
||||
w = self.active_window
|
||||
if w is not None:
|
||||
yield w
|
||||
w.focus_changed(focused)
|
||||
|
||||
def window_for_pos(self, x, y):
|
||||
for w in self.active_tab:
|
||||
tab = self.active_tab
|
||||
if tab is not None:
|
||||
for w in tab:
|
||||
if w.is_visible_in_layout and w.contains(x, y):
|
||||
return w
|
||||
|
||||
@callback
|
||||
def on_mouse_button(self, window, button, action, mods):
|
||||
self.show_mouse_cursor()
|
||||
w = self.window_for_pos(*window.get_cursor_pos())
|
||||
if w is not None:
|
||||
if button == GLFW_MOUSE_BUTTON_1 and w is not self.active_window:
|
||||
pass # TODO: Switch focus to this window
|
||||
if w is None:
|
||||
return
|
||||
focus_moved = False
|
||||
old_focus = self.active_window
|
||||
if button == GLFW_MOUSE_BUTTON_1 and w is not old_focus:
|
||||
# TODO: Switch focus to this window
|
||||
focus_moved = True
|
||||
yield
|
||||
if focus_moved:
|
||||
if old_focus is not None and not old_focus.destroyed:
|
||||
old_focus.focus_changed(False)
|
||||
w.focus_changed(True)
|
||||
w.on_mouse_button(window, button, action, mods)
|
||||
|
||||
@callback
|
||||
def on_mouse_move(self, window, xpos, ypos):
|
||||
self.show_mouse_cursor()
|
||||
w = self.window_for_pos(*window.get_cursor_pos())
|
||||
if w is not None:
|
||||
yield w
|
||||
w.on_mouse_move(window, xpos, ypos)
|
||||
|
||||
@callback
|
||||
def on_mouse_scroll(self, window, x, y):
|
||||
self.show_mouse_cursor()
|
||||
w = self.window_for_pos(*window.get_cursor_pos())
|
||||
if w is not None:
|
||||
yield w
|
||||
w.on_mouse_scroll(window, x, y)
|
||||
|
||||
# GUI thread API {{{
|
||||
|
||||
def show_mouse_cursor(self):
|
||||
self.glfw_window.set_input_mode(GLFW_CURSOR, GLFW_CURSOR_NORMAL)
|
||||
if self.opts.mouse_hide_wait > 0:
|
||||
self.ui_timers.add(self.opts.mouse_hide_wait, self.hide_mouse_cursor)
|
||||
|
||||
def hide_mouse_cursor(self):
|
||||
self.glfw_window.set_input_mode(GLFW_CURSOR, GLFW_CURSOR_HIDDEN)
|
||||
|
||||
def render(self):
|
||||
if self.pending_resize:
|
||||
return
|
||||
|
||||
@ -16,21 +16,23 @@ class Timers:
|
||||
def __init__(self):
|
||||
self.timers = []
|
||||
|
||||
def add(self, delay, callback, *args):
|
||||
self.remove(callback)
|
||||
def _add(self, delay, callback, args):
|
||||
self.timers.append(Event(monotonic() + delay, callback, args))
|
||||
self.timers.sort(key=get_at)
|
||||
|
||||
def add(self, delay, callback, *args):
|
||||
self.remove(callback)
|
||||
self._add(delay, callback, args)
|
||||
|
||||
def add_if_missing(self, delay, callback, *args):
|
||||
for ev in self.timers:
|
||||
if ev.callback is callback:
|
||||
break
|
||||
else:
|
||||
self.add(delay, callback, *args)
|
||||
if ev.callback == callback:
|
||||
return
|
||||
self._add(delay, callback, args)
|
||||
|
||||
def remove(self, callback):
|
||||
for i, ev in enumerate(self.timers):
|
||||
if ev.callback is callback:
|
||||
if ev.callback == callback:
|
||||
break
|
||||
else:
|
||||
return
|
||||
|
||||
@ -24,6 +24,7 @@ class Window:
|
||||
|
||||
def __init__(self, tab, child, opts, args):
|
||||
self.tabref = weakref.ref(tab)
|
||||
self.destroyed = False
|
||||
self.click_queue = deque(maxlen=3)
|
||||
self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0)
|
||||
self.needs_layout = True
|
||||
@ -59,6 +60,7 @@ class Window:
|
||||
tab_manager().close_window(self)
|
||||
|
||||
def destroy(self):
|
||||
self.destroyed = True
|
||||
self.child.hangup()
|
||||
self.child.get_child_status() # Ensure child does not become zombie
|
||||
# At this point this window can still render to screen using its
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user