@@ -316,40 +316,77 @@ def set_client_flags(
316316 * ,
317317 no_output : bool | None = None ,
318318 pause_after : int | None = None ,
319+ wait_exit : bool | None = None ,
320+ ignore_size : bool | None = None ,
321+ active_pane : bool | None = None ,
322+ read_only : bool | None = None ,
323+ no_detach_on_destroy : bool | None = None ,
319324 ) -> None :
320- """Set control client flags via refresh-client.
325+ """Set control client flags via `` refresh-client -f`` .
321326
322- These correspond to tmux's runtime client flags (set via
323- ``refresh-client -f``), not connection-time parameters. This follows
324- tmux's design where CLIENT_CONTROL_* flags are modified at runtime .
327+ These are runtime flags on the connected client. Boolean flags are
328+ toggled using tmux's ``!flag`` negation semantics and are left unchanged
329+ when passed ``None`` .
325330
326331 Parameters
327332 ----------
328333 no_output : bool, optional
329- Filter %output notifications (reduces noise when attached to active panes).
330- Set to True to enable, False to disable, None to leave unchanged.
334+ Filter ``%output`` notifications. ``False`` clears the flag.
331335 pause_after : int, optional
332- Pause output after N seconds of buffering (flow control).
333- Set to 0 to disable, positive int to enable, None to leave unchanged.
336+ Pause after N seconds of buffering; 0 clears the flag.
337+ wait_exit : bool, optional
338+ Keep control connection alive until tmux exit is reported.
339+ ignore_size : bool, optional
340+ Ignore size updates from the client.
341+ active_pane : bool, optional
342+ Mark client as active-pane.
343+ read_only : bool, optional
344+ Prevent modifications from this client.
345+ no_detach_on_destroy : bool, optional
346+ Mirror tmux's ``no-detach-on-destroy`` client flag.
334347
335348 Examples
336349 --------
337350 >>> engine.set_client_flags(no_output=True) # doctest: +SKIP
338351 >>> engine.set_client_flags(pause_after=5) # doctest: +SKIP
352+ >>> engine.set_client_flags(wait_exit=True) # doctest: +SKIP
339353 >>> engine.set_client_flags(no_output=False) # doctest: +SKIP
340354 """
355+
356+ def _bool_flag (name : str , value : bool | None ) -> str | None :
357+ if value is None :
358+ return None
359+ return name if value else f"!{ name } "
360+
341361 flags : list [str ] = []
342- if no_output is True :
343- flags . append ("no-output" )
344- elif no_output is False :
345- flags .append ("no-output=off" )
362+
363+ maybe_flag = _bool_flag ("no-output" , no_output )
364+ if maybe_flag :
365+ flags .append (maybe_flag )
346366
347367 if pause_after is not None :
368+ if pause_after < 0 :
369+ msg = "pause_after must be >= 0"
370+ raise ValueError (msg )
348371 if pause_after == 0 :
349- flags .append ("pause-after=none " )
372+ flags .append ("! pause-after" )
350373 else :
351374 flags .append (f"pause-after={ pause_after } " )
352375
376+ maybe_flag = _bool_flag ("wait-exit" , wait_exit )
377+ if maybe_flag :
378+ flags .append (maybe_flag )
379+
380+ for name , value in (
381+ ("ignore-size" , ignore_size ),
382+ ("active-pane" , active_pane ),
383+ ("read-only" , read_only ),
384+ ("no-detach-on-destroy" , no_detach_on_destroy ),
385+ ):
386+ maybe_flag = _bool_flag (name , value )
387+ if maybe_flag :
388+ flags .append (maybe_flag )
389+
353390 if flags :
354391 server_args = self ._server_context .to_args () if self ._server_context else ()
355392 self .run (
@@ -358,6 +395,56 @@ def set_client_flags(
358395 server_args = server_args ,
359396 )
360397
398+ def set_pane_flow (self , pane_id : str | int , state : str = "continue" ) -> None :
399+ """Set per-pane flow control for the control client.
400+
401+ This maps to ``refresh-client -A pane:state`` where ``state`` is one of
402+ ``on``, ``off``, ``pause``, or ``continue``. The default resumes a
403+ paused pane.
404+ """
405+ if state not in {"on" , "off" , "pause" , "continue" }:
406+ msg = "state must be one of on|off|pause|continue"
407+ raise ValueError (msg )
408+
409+ server_args = self ._server_context .to_args () if self ._server_context else ()
410+ self .run (
411+ "refresh-client" ,
412+ cmd_args = ("-A" , f"{ pane_id } :{ state } " ),
413+ server_args = server_args ,
414+ )
415+
416+ def subscribe (
417+ self ,
418+ name : str ,
419+ * ,
420+ what : str | None = None ,
421+ fmt : str | None = None ,
422+ ) -> None :
423+ """Manage control-mode subscriptions.
424+
425+ Subscriptions emit ``%subscription-changed`` notifications when the
426+ provided format changes. Passing ``format=None`` removes the
427+ subscription by name.
428+ """
429+ server_args = self ._server_context .to_args () if self ._server_context else ()
430+
431+ if fmt is None :
432+ # Remove subscription
433+ self .run (
434+ "refresh-client" ,
435+ cmd_args = ("-B" , name ),
436+ server_args = server_args ,
437+ )
438+ return
439+
440+ target = what or ""
441+ payload = f"{ name } :{ target } :{ fmt } "
442+ self .run (
443+ "refresh-client" ,
444+ cmd_args = ("-B" , payload ),
445+ server_args = server_args ,
446+ )
447+
361448 def get_stats (self ) -> EngineStats :
362449 """Return diagnostic statistics for the engine."""
363450 return self ._protocol .get_stats (restarts = self ._restarts )
0 commit comments