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,
|
'window_border_width': float,
|
||||||
'wheel_scroll_multiplier': float,
|
'wheel_scroll_multiplier': float,
|
||||||
'click_interval': 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():
|
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;
|
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*
|
static PyObject*
|
||||||
is_key_pressed(Window *self, PyObject *args) {
|
is_key_pressed(Window *self, PyObject *args) {
|
||||||
int c;
|
int c;
|
||||||
@ -273,6 +281,7 @@ static PyMethodDef methods[] = {
|
|||||||
MND(get_cursor_pos, METH_NOARGS),
|
MND(get_cursor_pos, METH_NOARGS),
|
||||||
MND(should_close, METH_NOARGS),
|
MND(should_close, METH_NOARGS),
|
||||||
MND(set_should_close, METH_VARARGS),
|
MND(set_should_close, METH_VARARGS),
|
||||||
|
MND(set_input_mode, METH_VARARGS),
|
||||||
MND(is_key_pressed, METH_VARARGS),
|
MND(is_key_pressed, METH_VARARGS),
|
||||||
MND(set_click_cursor, METH_VARARGS),
|
MND(set_click_cursor, METH_VARARGS),
|
||||||
MND(make_context_current, METH_NOARGS),
|
MND(make_context_current, METH_NOARGS),
|
||||||
|
|||||||
@ -43,6 +43,14 @@ click_interval 0.5
|
|||||||
# that sufficient for most uses.
|
# that sufficient for most uses.
|
||||||
repaint_delay 20
|
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.
|
# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
|
||||||
window_border_width 2
|
window_border_width 2
|
||||||
|
|
||||||
|
|||||||
115
kitty/tabs.py
115
kitty/tabs.py
@ -7,8 +7,9 @@ import io
|
|||||||
import select
|
import select
|
||||||
import signal
|
import signal
|
||||||
import struct
|
import struct
|
||||||
|
import inspect
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from functools import partial
|
from functools import wraps
|
||||||
from threading import Thread, current_thread
|
from threading import Thread, current_thread
|
||||||
from queue import Queue, Empty
|
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 .constants import viewport_size, shell_path, appname, set_tab_manager, tab_manager, wakeup, cell_size, MODIFIER_KEYS, main_thread
|
||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
glViewport, glBlendFunc, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GLFW_PRESS,
|
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 .fonts import set_font_family
|
||||||
from .borders import Borders, BordersProgram
|
from .borders import Borders, BordersProgram
|
||||||
@ -29,6 +31,32 @@ from .utils import handle_unix_signals
|
|||||||
from .window import Window
|
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:
|
class Tab:
|
||||||
|
|
||||||
def __init__(self, opts, args):
|
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)
|
cell_size.width, cell_size.height = set_font_family(opts.font_family, opts.font_size)
|
||||||
self.opts, self.args = opts, args
|
self.opts, self.args = opts, args
|
||||||
self.glfw_window = glfw_window
|
self.glfw_window = glfw_window
|
||||||
glfw_window.framebuffer_size_callback = partial(self.queue_action, self.on_window_resize)
|
glfw_window.framebuffer_size_callback = self.on_window_resize
|
||||||
glfw_window.char_mods_callback = partial(self.queue_action, self.on_text_input)
|
glfw_window.char_mods_callback = self.on_text_input
|
||||||
glfw_window.key_callback = partial(self.queue_action, self.on_key)
|
glfw_window.key_callback = self.on_key
|
||||||
glfw_window.mouse_button_callback = partial(self.queue_action, self.on_mouse_button)
|
glfw_window.mouse_button_callback = self.on_mouse_button
|
||||||
glfw_window.scroll_callback = partial(self.queue_action, self.on_mouse_scroll)
|
glfw_window.scroll_callback = self.on_mouse_scroll
|
||||||
glfw_window.cursor_pos_callback = partial(self.queue_action, self.on_mouse_move)
|
glfw_window.cursor_pos_callback = self.on_mouse_move
|
||||||
glfw_window.window_focus_callback = partial(self.queue_action, self.on_focus)
|
glfw_window.window_focus_callback = self.on_focus
|
||||||
self.tabs = deque()
|
self.tabs = deque()
|
||||||
self.tabs.append(Tab(opts, args))
|
self.tabs.append(Tab(opts, args))
|
||||||
self.sprites = Sprites()
|
self.sprites = Sprites()
|
||||||
@ -137,6 +165,7 @@ class TabManager(Thread):
|
|||||||
self.sprites.do_layout(cell_size.width, cell_size.height)
|
self.sprites.do_layout(cell_size.width, cell_size.height)
|
||||||
self.queue_action(self.active_tab.new_window, False)
|
self.queue_action(self.active_tab.new_window, False)
|
||||||
self.glfw_window.set_click_cursor(False)
|
self.glfw_window.set_click_cursor(False)
|
||||||
|
self.show_mouse_cursor()
|
||||||
|
|
||||||
def signal_received(self):
|
def signal_received(self):
|
||||||
try:
|
try:
|
||||||
@ -230,10 +259,12 @@ class TabManager(Thread):
|
|||||||
if w.screen.is_dirty():
|
if w.screen.is_dirty():
|
||||||
self.timers.add_if_missing(self.screen_update_delay, w.update_screen)
|
self.timers.add_if_missing(self.screen_update_delay, w.update_screen)
|
||||||
|
|
||||||
|
@callback
|
||||||
def on_window_resize(self, window, w, h):
|
def on_window_resize(self, window, w, h):
|
||||||
# debounce resize events
|
# debounce resize events
|
||||||
self.timers.add(0.02, self.apply_pending_resize, w, h)
|
|
||||||
self.pending_resize = True
|
self.pending_resize = True
|
||||||
|
yield
|
||||||
|
self.timers.add(0.02, self.apply_pending_resize, w, h)
|
||||||
|
|
||||||
def apply_pending_resize(self, w, h):
|
def apply_pending_resize(self, w, h):
|
||||||
viewport_size.width, viewport_size.height = w, h
|
viewport_size.width, viewport_size.height = w, h
|
||||||
@ -256,25 +287,35 @@ class TabManager(Thread):
|
|||||||
if t is not None:
|
if t is not None:
|
||||||
return t.active_window
|
return t.active_window
|
||||||
|
|
||||||
|
@callback
|
||||||
def on_text_input(self, window, codepoint, mods):
|
def on_text_input(self, window, codepoint, mods):
|
||||||
data = interpret_text_event(codepoint, mods)
|
data = interpret_text_event(codepoint, mods)
|
||||||
if data:
|
if data:
|
||||||
w = self.active_window
|
w = self.active_window
|
||||||
if w is not None:
|
if w is not None:
|
||||||
|
yield w
|
||||||
w.write_to_child(data)
|
w.write_to_child(data)
|
||||||
|
|
||||||
|
@callback
|
||||||
def on_key(self, window, key, scancode, action, mods):
|
def on_key(self, window, key, scancode, action, mods):
|
||||||
if action == GLFW_PRESS or action == GLFW_REPEAT:
|
if action == GLFW_PRESS or action == GLFW_REPEAT:
|
||||||
func = get_shortcut(self.opts.keymap, mods, key)
|
func = get_shortcut(self.opts.keymap, mods, key)
|
||||||
tab = self.active_tab
|
tab = self.active_tab
|
||||||
window = self.active_window
|
|
||||||
if func is not None:
|
if func is not None:
|
||||||
func = getattr(self, func, getattr(tab, func, getattr(window, func, None)))
|
f = getattr(self, func, getattr(tab, func, None))
|
||||||
if func is not None:
|
if f is not None:
|
||||||
passthrough = func()
|
passthrough = f()
|
||||||
if not passthrough:
|
if not passthrough:
|
||||||
return
|
return
|
||||||
if window:
|
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.screen.auto_repeat_enabled() or action == GLFW_PRESS:
|
if window.screen.auto_repeat_enabled() or action == GLFW_PRESS:
|
||||||
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS:
|
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS:
|
||||||
window.scroll_end()
|
window.scroll_end()
|
||||||
@ -282,34 +323,64 @@ class TabManager(Thread):
|
|||||||
if data:
|
if data:
|
||||||
window.write_to_child(data)
|
window.write_to_child(data)
|
||||||
|
|
||||||
|
@callback
|
||||||
def on_focus(self, window, focused):
|
def on_focus(self, window, focused):
|
||||||
w = self.active_window
|
w = self.active_window
|
||||||
if w is not None:
|
if w is not None:
|
||||||
|
yield w
|
||||||
w.focus_changed(focused)
|
w.focus_changed(focused)
|
||||||
|
|
||||||
def window_for_pos(self, x, y):
|
def window_for_pos(self, x, y):
|
||||||
for w in self.active_tab:
|
tab = self.active_tab
|
||||||
if w.is_visible_in_layout and w.contains(x, y):
|
if tab is not None:
|
||||||
return w
|
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):
|
def on_mouse_button(self, window, button, action, mods):
|
||||||
|
self.show_mouse_cursor()
|
||||||
w = self.window_for_pos(*window.get_cursor_pos())
|
w = self.window_for_pos(*window.get_cursor_pos())
|
||||||
if w is not None:
|
if w is None:
|
||||||
if button == GLFW_MOUSE_BUTTON_1 and w is not self.active_window:
|
return
|
||||||
pass # TODO: Switch focus to this window
|
focus_moved = False
|
||||||
w.on_mouse_button(window, button, action, mods)
|
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):
|
def on_mouse_move(self, window, xpos, ypos):
|
||||||
|
self.show_mouse_cursor()
|
||||||
w = self.window_for_pos(*window.get_cursor_pos())
|
w = self.window_for_pos(*window.get_cursor_pos())
|
||||||
if w is not None:
|
if w is not None:
|
||||||
|
yield w
|
||||||
w.on_mouse_move(window, xpos, ypos)
|
w.on_mouse_move(window, xpos, ypos)
|
||||||
|
|
||||||
|
@callback
|
||||||
def on_mouse_scroll(self, window, x, y):
|
def on_mouse_scroll(self, window, x, y):
|
||||||
|
self.show_mouse_cursor()
|
||||||
w = self.window_for_pos(*window.get_cursor_pos())
|
w = self.window_for_pos(*window.get_cursor_pos())
|
||||||
if w is not None:
|
if w is not None:
|
||||||
|
yield w
|
||||||
w.on_mouse_scroll(window, x, y)
|
w.on_mouse_scroll(window, x, y)
|
||||||
|
|
||||||
# GUI thread API {{{
|
# 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):
|
def render(self):
|
||||||
if self.pending_resize:
|
if self.pending_resize:
|
||||||
return
|
return
|
||||||
|
|||||||
@ -16,21 +16,23 @@ class Timers:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.timers = []
|
self.timers = []
|
||||||
|
|
||||||
def add(self, delay, callback, *args):
|
def _add(self, delay, callback, args):
|
||||||
self.remove(callback)
|
|
||||||
self.timers.append(Event(monotonic() + delay, callback, args))
|
self.timers.append(Event(monotonic() + delay, callback, args))
|
||||||
self.timers.sort(key=get_at)
|
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):
|
def add_if_missing(self, delay, callback, *args):
|
||||||
for ev in self.timers:
|
for ev in self.timers:
|
||||||
if ev.callback is callback:
|
if ev.callback == callback:
|
||||||
break
|
return
|
||||||
else:
|
self._add(delay, callback, args)
|
||||||
self.add(delay, callback, *args)
|
|
||||||
|
|
||||||
def remove(self, callback):
|
def remove(self, callback):
|
||||||
for i, ev in enumerate(self.timers):
|
for i, ev in enumerate(self.timers):
|
||||||
if ev.callback is callback:
|
if ev.callback == callback:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|||||||
@ -24,6 +24,7 @@ class Window:
|
|||||||
|
|
||||||
def __init__(self, tab, child, opts, args):
|
def __init__(self, tab, child, opts, args):
|
||||||
self.tabref = weakref.ref(tab)
|
self.tabref = weakref.ref(tab)
|
||||||
|
self.destroyed = False
|
||||||
self.click_queue = deque(maxlen=3)
|
self.click_queue = deque(maxlen=3)
|
||||||
self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0)
|
self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0)
|
||||||
self.needs_layout = True
|
self.needs_layout = True
|
||||||
@ -59,6 +60,7 @@ class Window:
|
|||||||
tab_manager().close_window(self)
|
tab_manager().close_window(self)
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
|
self.destroyed = True
|
||||||
self.child.hangup()
|
self.child.hangup()
|
||||||
self.child.get_child_status() # Ensure child does not become zombie
|
self.child.get_child_status() # Ensure child does not become zombie
|
||||||
# At this point this window can still render to screen using its
|
# At this point this window can still render to screen using its
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user