Reduce the number of spurious focus events
1) When performing operations known to cause lots of focus changes such as creating new sessions/windows or moving windows, forcibly ignore focus events 2) Track window focus state and dont report focus events when the state is unchanged by a focus_changed() call This allows focus specific code to be restricted to just 2-3 places instead of having to track every possible function that could change focus. Fixes #6083
This commit is contained in:
parent
719fe9ea04
commit
eab3b2a689
@ -7,7 +7,7 @@ import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from contextlib import suppress
|
||||
from contextlib import contextmanager, suppress
|
||||
from functools import partial
|
||||
from gettext import gettext as _
|
||||
from time import monotonic, sleep
|
||||
@ -17,6 +17,7 @@ from typing import (
|
||||
Callable,
|
||||
Container,
|
||||
Dict,
|
||||
Generator,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
@ -381,6 +382,7 @@ class Boss:
|
||||
si = startup_sessions or create_sessions(get_options(), self.args, default_session=get_options().startup_session)
|
||||
focused_os_window = wid = 0
|
||||
token = os.environ.pop('XDG_ACTIVATION_TOKEN', '')
|
||||
with Window.set_ignore_focus_changes_for_new_windows():
|
||||
for startup_session in si:
|
||||
# The window state from the CLI options will override and apply to every single OS window in startup session
|
||||
wstate = self.args.start_as if self.args.start_as and self.args.start_as != 'normal' else None
|
||||
@ -392,6 +394,8 @@ class Boss:
|
||||
focus_os_window(focused_os_window, True, token)
|
||||
elif token and is_wayland() and wid:
|
||||
focus_os_window(wid, True, token)
|
||||
for w in self.all_windows:
|
||||
w.ignore_focus_changes = False
|
||||
|
||||
def add_os_window(
|
||||
self,
|
||||
@ -807,11 +811,24 @@ class Boss:
|
||||
if not self.shutting_down:
|
||||
self.mark_os_window_for_close(src_tab.os_window_id)
|
||||
|
||||
@contextmanager
|
||||
def suppress_focus_change_events(self) -> Generator[None, None, None]:
|
||||
changes = {}
|
||||
for w in self.window_id_map.values():
|
||||
changes[w] = w.ignore_focus_changes
|
||||
w.ignore_focus_changes = True
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
for w, val in changes.items():
|
||||
w.ignore_focus_changes = val
|
||||
|
||||
def on_child_death(self, window_id: int) -> None:
|
||||
prev_active_window = self.active_window
|
||||
window = self.window_id_map.pop(window_id, None)
|
||||
if window is None:
|
||||
return
|
||||
with self.suppress_focus_change_events():
|
||||
for close_action in window.actions_on_close:
|
||||
try:
|
||||
close_action(window)
|
||||
@ -838,6 +855,7 @@ class Boss:
|
||||
traceback.print_exc()
|
||||
del window.actions_on_close[:], window.actions_on_removal[:]
|
||||
window = self.active_window
|
||||
|
||||
if window is not prev_active_window:
|
||||
if prev_active_window is not None:
|
||||
prev_active_window.focus_changed(False)
|
||||
@ -2498,6 +2516,7 @@ class Boss:
|
||||
src_tab = self.tab_for_window(window)
|
||||
if src_tab is None:
|
||||
return
|
||||
with self.suppress_focus_change_events():
|
||||
if target_os_window_id == 'new':
|
||||
target_os_window_id = self.add_os_window()
|
||||
tm = self.os_window_map[target_os_window_id]
|
||||
|
||||
@ -435,7 +435,7 @@ force_window_launch = ForceWindowLaunch()
|
||||
non_window_launch_types = 'background', 'clipboard', 'primary'
|
||||
|
||||
|
||||
def launch(
|
||||
def _launch(
|
||||
boss: Boss,
|
||||
opts: LaunchCLIOptions,
|
||||
args: List[str],
|
||||
@ -588,14 +588,19 @@ def launch(
|
||||
tab = tab_for_window(boss, opts, target_tab)
|
||||
if tab is not None:
|
||||
watchers = load_watch_modules(opts.watcher)
|
||||
new_window: Window = tab.new_window(env=env or None, watchers=watchers or None, is_clone_launch=is_clone_launch, **kw)
|
||||
with Window.set_ignore_focus_changes_for_new_windows(opts.keep_focus):
|
||||
new_window: Window = tab.new_window(
|
||||
env=env or None, watchers=watchers or None, is_clone_launch=is_clone_launch, **kw)
|
||||
if spacing:
|
||||
patch_window_edges(new_window, spacing)
|
||||
tab.relayout()
|
||||
if opts.color:
|
||||
apply_colors(new_window, opts.color)
|
||||
if opts.keep_focus and active:
|
||||
if opts.keep_focus:
|
||||
if active:
|
||||
boss.set_active_window(active, switch_os_window_if_needed=True, for_keep_focus=True)
|
||||
if not Window.initial_ignore_focus_changes_context_manager_in_operation:
|
||||
new_window.ignore_focus_changes = False
|
||||
if opts.logo:
|
||||
new_window.set_logo(opts.logo, opts.logo_position or '', opts.logo_alpha)
|
||||
if opts.type == 'overlay-main':
|
||||
@ -604,6 +609,25 @@ def launch(
|
||||
return None
|
||||
|
||||
|
||||
def launch(
|
||||
boss: Boss,
|
||||
opts: LaunchCLIOptions,
|
||||
args: List[str],
|
||||
target_tab: Optional[Tab] = None,
|
||||
force_target_tab: bool = False,
|
||||
active: Optional[Window] = None,
|
||||
is_clone_launch: str = '',
|
||||
rc_from_window: Optional[Window] = None,
|
||||
) -> Optional[Window]:
|
||||
active = active or boss.active_window_for_cwd
|
||||
if opts.keep_focus and active:
|
||||
orig, active.ignore_focus_changes = active.ignore_focus_changes, True
|
||||
try:
|
||||
return _launch(boss, opts, args, target_tab, force_target_tab, active, is_clone_launch, rc_from_window)
|
||||
finally:
|
||||
if opts.keep_focus and active:
|
||||
active.ignore_focus_changes = orig
|
||||
|
||||
@run_once
|
||||
def clone_safe_opts() -> FrozenSet[str]:
|
||||
return frozenset((
|
||||
|
||||
@ -7,7 +7,7 @@ import re
|
||||
import sys
|
||||
import weakref
|
||||
from collections import deque
|
||||
from contextlib import suppress
|
||||
from contextlib import contextmanager, suppress
|
||||
from enum import Enum, IntEnum, auto
|
||||
from functools import lru_cache, partial
|
||||
from gettext import gettext as _
|
||||
@ -19,6 +19,7 @@ from typing import (
|
||||
Callable,
|
||||
Deque,
|
||||
Dict,
|
||||
Generator,
|
||||
Iterable,
|
||||
List,
|
||||
NamedTuple,
|
||||
@ -542,6 +543,22 @@ class Window:
|
||||
|
||||
window_custom_type: str = ''
|
||||
overlay_type = OverlayType.transient
|
||||
initial_ignore_focus_changes: bool = False
|
||||
initial_ignore_focus_changes_context_manager_in_operation: bool = False
|
||||
|
||||
@classmethod
|
||||
@contextmanager
|
||||
def set_ignore_focus_changes_for_new_windows(cls, value: bool = True) -> Generator[None, None, None]:
|
||||
if cls.initial_ignore_focus_changes_context_manager_in_operation:
|
||||
yield
|
||||
else:
|
||||
orig, cls.initial_ignore_focus_changes = cls.initial_ignore_focus_changes, value
|
||||
cls.initial_ignore_focus_changes_context_manager_in_operation = True
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
cls.initial_ignore_focus_changes = orig
|
||||
cls.initial_ignore_focus_changes_context_manager_in_operation = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -560,6 +577,7 @@ class Window:
|
||||
else:
|
||||
self.watchers = global_watchers().copy()
|
||||
self.last_focused_at = 0.
|
||||
self.is_focused: bool = False
|
||||
self.last_resized_at = 0.
|
||||
self.started_at = monotonic()
|
||||
self.current_remote_data: List[str] = []
|
||||
@ -574,6 +592,7 @@ class Window:
|
||||
self.pty_resized_once = False
|
||||
self.last_reported_pty_size = (-1, -1, -1, -1)
|
||||
self.needs_attention = False
|
||||
self.ignore_focus_changes = self.initial_ignore_focus_changes
|
||||
self.override_title = override_title
|
||||
self.default_title = os.path.basename(child.argv[0] or appname)
|
||||
self.child_title = self.default_title
|
||||
@ -993,8 +1012,9 @@ class Window:
|
||||
return False
|
||||
|
||||
def focus_changed(self, focused: bool) -> None:
|
||||
if self.destroyed:
|
||||
if self.destroyed or self.ignore_focus_changes or self.is_focused == focused:
|
||||
return
|
||||
self.is_focused = focused
|
||||
call_watchers(weakref.ref(self), 'on_focus_change', {'focused': focused})
|
||||
for c in self.actions_on_focus_change:
|
||||
try:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user