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 os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from contextlib import suppress
|
from contextlib import contextmanager, suppress
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from time import monotonic, sleep
|
from time import monotonic, sleep
|
||||||
@ -17,6 +17,7 @@ from typing import (
|
|||||||
Callable,
|
Callable,
|
||||||
Container,
|
Container,
|
||||||
Dict,
|
Dict,
|
||||||
|
Generator,
|
||||||
Iterable,
|
Iterable,
|
||||||
Iterator,
|
Iterator,
|
||||||
List,
|
List,
|
||||||
@ -381,6 +382,7 @@ class Boss:
|
|||||||
si = startup_sessions or create_sessions(get_options(), self.args, default_session=get_options().startup_session)
|
si = startup_sessions or create_sessions(get_options(), self.args, default_session=get_options().startup_session)
|
||||||
focused_os_window = wid = 0
|
focused_os_window = wid = 0
|
||||||
token = os.environ.pop('XDG_ACTIVATION_TOKEN', '')
|
token = os.environ.pop('XDG_ACTIVATION_TOKEN', '')
|
||||||
|
with Window.set_ignore_focus_changes_for_new_windows():
|
||||||
for startup_session in si:
|
for startup_session in si:
|
||||||
# The window state from the CLI options will override and apply to every single OS window in startup session
|
# 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
|
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)
|
focus_os_window(focused_os_window, True, token)
|
||||||
elif token and is_wayland() and wid:
|
elif token and is_wayland() and wid:
|
||||||
focus_os_window(wid, True, token)
|
focus_os_window(wid, True, token)
|
||||||
|
for w in self.all_windows:
|
||||||
|
w.ignore_focus_changes = False
|
||||||
|
|
||||||
def add_os_window(
|
def add_os_window(
|
||||||
self,
|
self,
|
||||||
@ -807,11 +811,24 @@ class Boss:
|
|||||||
if not self.shutting_down:
|
if not self.shutting_down:
|
||||||
self.mark_os_window_for_close(src_tab.os_window_id)
|
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:
|
def on_child_death(self, window_id: int) -> None:
|
||||||
prev_active_window = self.active_window
|
prev_active_window = self.active_window
|
||||||
window = self.window_id_map.pop(window_id, None)
|
window = self.window_id_map.pop(window_id, None)
|
||||||
if window is None:
|
if window is None:
|
||||||
return
|
return
|
||||||
|
with self.suppress_focus_change_events():
|
||||||
for close_action in window.actions_on_close:
|
for close_action in window.actions_on_close:
|
||||||
try:
|
try:
|
||||||
close_action(window)
|
close_action(window)
|
||||||
@ -838,6 +855,7 @@ class Boss:
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
del window.actions_on_close[:], window.actions_on_removal[:]
|
del window.actions_on_close[:], window.actions_on_removal[:]
|
||||||
window = self.active_window
|
window = self.active_window
|
||||||
|
|
||||||
if window is not prev_active_window:
|
if window is not prev_active_window:
|
||||||
if prev_active_window is not None:
|
if prev_active_window is not None:
|
||||||
prev_active_window.focus_changed(False)
|
prev_active_window.focus_changed(False)
|
||||||
@ -2498,6 +2516,7 @@ class Boss:
|
|||||||
src_tab = self.tab_for_window(window)
|
src_tab = self.tab_for_window(window)
|
||||||
if src_tab is None:
|
if src_tab is None:
|
||||||
return
|
return
|
||||||
|
with self.suppress_focus_change_events():
|
||||||
if target_os_window_id == 'new':
|
if target_os_window_id == 'new':
|
||||||
target_os_window_id = self.add_os_window()
|
target_os_window_id = self.add_os_window()
|
||||||
tm = self.os_window_map[target_os_window_id]
|
tm = self.os_window_map[target_os_window_id]
|
||||||
|
|||||||
@ -435,7 +435,7 @@ force_window_launch = ForceWindowLaunch()
|
|||||||
non_window_launch_types = 'background', 'clipboard', 'primary'
|
non_window_launch_types = 'background', 'clipboard', 'primary'
|
||||||
|
|
||||||
|
|
||||||
def launch(
|
def _launch(
|
||||||
boss: Boss,
|
boss: Boss,
|
||||||
opts: LaunchCLIOptions,
|
opts: LaunchCLIOptions,
|
||||||
args: List[str],
|
args: List[str],
|
||||||
@ -588,14 +588,19 @@ def launch(
|
|||||||
tab = tab_for_window(boss, opts, target_tab)
|
tab = tab_for_window(boss, opts, target_tab)
|
||||||
if tab is not None:
|
if tab is not None:
|
||||||
watchers = load_watch_modules(opts.watcher)
|
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:
|
if spacing:
|
||||||
patch_window_edges(new_window, spacing)
|
patch_window_edges(new_window, spacing)
|
||||||
tab.relayout()
|
tab.relayout()
|
||||||
if opts.color:
|
if opts.color:
|
||||||
apply_colors(new_window, 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)
|
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:
|
if opts.logo:
|
||||||
new_window.set_logo(opts.logo, opts.logo_position or '', opts.logo_alpha)
|
new_window.set_logo(opts.logo, opts.logo_position or '', opts.logo_alpha)
|
||||||
if opts.type == 'overlay-main':
|
if opts.type == 'overlay-main':
|
||||||
@ -604,6 +609,25 @@ def launch(
|
|||||||
return None
|
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
|
@run_once
|
||||||
def clone_safe_opts() -> FrozenSet[str]:
|
def clone_safe_opts() -> FrozenSet[str]:
|
||||||
return frozenset((
|
return frozenset((
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import weakref
|
import weakref
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from contextlib import suppress
|
from contextlib import contextmanager, suppress
|
||||||
from enum import Enum, IntEnum, auto
|
from enum import Enum, IntEnum, auto
|
||||||
from functools import lru_cache, partial
|
from functools import lru_cache, partial
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
@ -19,6 +19,7 @@ from typing import (
|
|||||||
Callable,
|
Callable,
|
||||||
Deque,
|
Deque,
|
||||||
Dict,
|
Dict,
|
||||||
|
Generator,
|
||||||
Iterable,
|
Iterable,
|
||||||
List,
|
List,
|
||||||
NamedTuple,
|
NamedTuple,
|
||||||
@ -542,6 +543,22 @@ class Window:
|
|||||||
|
|
||||||
window_custom_type: str = ''
|
window_custom_type: str = ''
|
||||||
overlay_type = OverlayType.transient
|
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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -560,6 +577,7 @@ class Window:
|
|||||||
else:
|
else:
|
||||||
self.watchers = global_watchers().copy()
|
self.watchers = global_watchers().copy()
|
||||||
self.last_focused_at = 0.
|
self.last_focused_at = 0.
|
||||||
|
self.is_focused: bool = False
|
||||||
self.last_resized_at = 0.
|
self.last_resized_at = 0.
|
||||||
self.started_at = monotonic()
|
self.started_at = monotonic()
|
||||||
self.current_remote_data: List[str] = []
|
self.current_remote_data: List[str] = []
|
||||||
@ -574,6 +592,7 @@ class Window:
|
|||||||
self.pty_resized_once = False
|
self.pty_resized_once = False
|
||||||
self.last_reported_pty_size = (-1, -1, -1, -1)
|
self.last_reported_pty_size = (-1, -1, -1, -1)
|
||||||
self.needs_attention = False
|
self.needs_attention = False
|
||||||
|
self.ignore_focus_changes = self.initial_ignore_focus_changes
|
||||||
self.override_title = override_title
|
self.override_title = override_title
|
||||||
self.default_title = os.path.basename(child.argv[0] or appname)
|
self.default_title = os.path.basename(child.argv[0] or appname)
|
||||||
self.child_title = self.default_title
|
self.child_title = self.default_title
|
||||||
@ -993,8 +1012,9 @@ class Window:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def focus_changed(self, focused: bool) -> None:
|
def focus_changed(self, focused: bool) -> None:
|
||||||
if self.destroyed:
|
if self.destroyed or self.ignore_focus_changes or self.is_focused == focused:
|
||||||
return
|
return
|
||||||
|
self.is_focused = focused
|
||||||
call_watchers(weakref.ref(self), 'on_focus_change', {'focused': focused})
|
call_watchers(weakref.ref(self), 'on_focus_change', {'focused': focused})
|
||||||
for c in self.actions_on_focus_change:
|
for c in self.actions_on_focus_change:
|
||||||
try:
|
try:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user