handle tmux window changes, session changes, new attached clients

by registering tmux hooks for the pane of ueberzug, hooked events:
- client-session-changed
- session-window-changed
- pane-mode-changed
pull/4/head
seebye 6 years ago
parent 7001a72eac
commit a5d321c423

@ -10,8 +10,7 @@ import ueberzug.xutil as xutil
class Executable:
def __init__(self, window_factory, windows, media):
self.window_factory = window_factory
def __init__(self, windows, media):
self.windows = windows
self.media = media
@ -61,47 +60,10 @@ class RemoveImageAction(Executable):
self.windows.draw()
class QueryWindowsAction(Executable):
"""Searches for added and removed tmux clients.
Added clients: additional windows will be mapped
Removed clients: existing windows will be destroyed
"""
def execute(self): #pylint: disable=W0221
draw = False
parent_window_infos = xutil.get_parent_window_infos()
map_parent_window_id_info = {info.window_id: info
for info in parent_window_infos}
parent_window_ids = map_parent_window_id_info.keys()
map_current_windows = {window.parent_window.id: window
for window in self.windows}
current_window_ids = map_current_windows.keys()
diff_window_ids = parent_window_ids ^ current_window_ids
added_window_ids = diff_window_ids & parent_window_ids
removed_window_ids = diff_window_ids & current_window_ids
if added_window_ids:
draw = True
self.windows += self.window_factory.create(*[
map_parent_window_id_info.get(wid)
for wid in added_window_ids
])
if removed_window_ids:
draw = True
self.windows -= [
map_current_windows.get(wid)
for wid in removed_window_ids
]
if (draw and self.windows):
self.windows.draw()
@enum.unique
class Command(str, enum.Enum):
ADD = 'add', AddImageAction
REMOVE = 'remove', RemoveImageAction
FOCUS_CHANGED = 'query_windows', QueryWindowsAction
def __new__(cls, identifier, action_class):
inst = str.__new__(cls)

@ -1,4 +1,5 @@
import subprocess
import shlex
import os
@ -22,10 +23,10 @@ def is_window_focused():
"""
result = subprocess.check_output([
'tmux', 'display', '-p',
'-F', '#{window_active}',
'-F', '#{window_active},#{pane_in_mode}',
'-t', get_pane()
]).decode()
return result == "1\n"
return result == "1,0\n"
def get_clients():
@ -55,3 +56,27 @@ def get_client_ttys_by_pid():
'-t', get_pane()
]).decode().splitlines()
for pid, tty in (pid_tty.split(','),)}
def register_hook(event, command):
"""Updates the hook of the passed event
for the pane this program runs in
to the execution of a program.
Note: tmux does not support multiple hooks for the same target.
So if there's already an hook registered it will be overwritten.
"""
subprocess.check_call([
'tmux', 'set-hook',
'-t', get_pane(),
event, 'run-shell ' + shlex.quote(command)
])
def unregister_hook(event):
"""Removes the hook of the passed event
for the pane this program runs in.
"""
subprocess.check_call([
'tmux', 'set-hook', '-u', '-t', get_pane(), event
])

@ -31,7 +31,6 @@ import pathlib
import traceback
import docopt
import Xlib.display as Xdisplay
import ueberzug.aio as aio
import ueberzug.xutil as xutil
@ -39,6 +38,7 @@ import ueberzug.parser as parser
import ueberzug.ui as ui
import ueberzug.batch as batch
import ueberzug.action as action
import ueberzug.tmux_util as tmux_util
async def main_xevents(loop, display, windows):
@ -49,7 +49,7 @@ async def main_xevents(loop, display, windows):
async def main_commands(loop, shutdown_routine, parser_object,
window_factory, windows, media):
windows, media):
"""Coroutine which processes the input of stdin"""
async for line in aio.LineReader(loop, sys.stdin):
if not line:
@ -59,7 +59,7 @@ async def main_commands(loop, shutdown_routine, parser_object,
try:
data = parser_object.parse(line[:-1])
command = action.Command(data.pop('action')) #pylint: disable=E1120
command.action_class(window_factory, windows, media) \
command.action_class(windows, media) \
.execute(**data)
except (parser.ParseError, KeyError, ValueError, TypeError) as error:
cause = (error.args[0]
@ -73,6 +73,42 @@ async def main_commands(loop, shutdown_routine, parser_object,
}), file=sys.stderr)
async def query_windows(window_factory, windows):
"""Signal handler for SIGUSR1.
Searches for added and removed tmux clients.
Added clients: additional windows will be mapped
Removed clients: existing windows will be destroyed
"""
draw = False
parent_window_infos = xutil.get_parent_window_infos()
map_parent_window_id_info = {info.window_id: info
for info in parent_window_infos}
parent_window_ids = map_parent_window_id_info.keys()
map_current_windows = {window.parent_window.id: window
for window in windows}
current_window_ids = map_current_windows.keys()
diff_window_ids = parent_window_ids ^ current_window_ids
added_window_ids = diff_window_ids & parent_window_ids
removed_window_ids = diff_window_ids & current_window_ids
if added_window_ids:
draw = True
windows += window_factory.create(*[
map_parent_window_id_info.get(wid)
for wid in added_window_ids
])
if removed_window_ids:
draw = True
windows -= [
map_current_windows.get(wid)
for wid in removed_window_ids
]
if (draw and windows):
windows.draw()
async def shutdown(loop):
tasks = [task for task in asyncio.Task.all_tasks() if task is not
asyncio.tasks.Task.current_task()]
@ -81,6 +117,34 @@ async def shutdown(loop):
loop.stop()
def setup_tmux_hooks():
"""Registers tmux hooks which are
required to notice a change in the visibility
of the pane this program runs in.
Also it's required to notice new tmux clients
displaying our pane.
Returns:
function which unregisters the registered hooks
"""
events = (
'client-session-changed',
'session-window-changed',
'pane-mode-changed'
)
command = 'kill -USR1 ' + str(os.getpid())
for event in events:
tmux_util.register_hook(event, command)
def remove_hooks():
"""Removes the hooks registered by the outer function."""
for event in events:
tmux_util.unregister_hook(event)
return remove_hooks
def main_image(options):
display = xutil.get_display()
window_infos = xutil.get_parent_window_infos()
@ -92,6 +156,9 @@ def main_image(options):
window_factory = ui.OverlayWindow.Factory(display, media)
windows = batch.BatchList(window_factory.create(*window_infos))
if tmux_util.is_used():
atexit.register(setup_tmux_hooks())
with windows:
# this could lead to unexpected behavior,
# but hey otherwise it breaks exiting the script..
@ -108,10 +175,14 @@ def main_image(options):
sig,
functools.partial(asyncio.ensure_future, shutdown_routine))
loop.add_signal_handler(
signal.SIGUSR1,
lambda: asyncio.ensure_future(query_windows(window_factory, windows)))
asyncio.ensure_future(main_xevents(loop, display, windows))
asyncio.ensure_future(main_commands(
loop, shutdown_routine, parser_class(),
window_factory, windows, media))
windows, media))
try:
loop.run_forever()

Loading…
Cancel
Save