@@ -190,6 +190,120 @@ def _run(*args: str) -> bool:
190190
191191 return completed .returncode == 0
192192
193+ @staticmethod
194+ def _run_capture (* args : str ) -> Optional [str ]:
195+ """
196+ Execute a command and return trimmed stdout on success.
197+
198+ @return stdout without trailing whitespace, or ``None`` on failure/empty output.
199+ """
200+ try :
201+ completed = subprocess .run (
202+ [* args ],
203+ check = False ,
204+ capture_output = True ,
205+ text = True ,
206+ )
207+ except Exception :
208+ return None
209+
210+ if completed .returncode != 0 :
211+ return None
212+
213+ output = (completed .stdout or "" ).strip ()
214+ return output or None
215+
216+ @staticmethod
217+ def _parse_profile_name (raw_output : Optional [str ]) -> Optional [str ]:
218+ """
219+ Extract profile name from ``remotinator`` output.
220+ """
221+ if not raw_output :
222+ return None
223+
224+ lines = [line .strip () for line in raw_output .splitlines () if line .strip ()]
225+ if not lines :
226+ return None
227+
228+ candidate = lines [- 1 ].strip ("'\" " )
229+ lower_candidate = candidate .lower ()
230+ for prefix in ("current profile:" , "profile:" , "profile=" ):
231+ if lower_candidate .startswith (prefix ):
232+ candidate = candidate [len (prefix ) :].strip ().strip ("'\" " )
233+ break
234+
235+ return candidate or None
236+
237+ def _get_current_profile (self , terminal_uuid : str ) -> Optional [str ]:
238+ """
239+ Query current profile for a terminal UUID using ``remotinator``.
240+ """
241+ output = self ._run_capture ("remotinator" , "get_profile" , "-u" , terminal_uuid )
242+ return self ._parse_profile_name (output )
243+
244+ @staticmethod
245+ def _profiles_block_bounds (lines : list [str ]) -> Optional [tuple [int , int ]]:
246+ """
247+ Locate the [profiles] block bounds in Terminator config lines.
248+ """
249+ profiles_start = next ((i for i , line in enumerate (lines ) if line .strip () == "[profiles]" ), None )
250+ if profiles_start is None :
251+ return None
252+
253+ profiles_end = len (lines )
254+ for index in range (profiles_start + 1 , len (lines )):
255+ if TerminatorTerminalAdapter ._TOP_LEVEL_SECTION_RE .match (lines [index ]) and lines [index ].strip () != "[profiles]" :
256+ profiles_end = index
257+ break
258+
259+ return (profiles_start , profiles_end )
260+
261+ @classmethod
262+ def _list_profiles (cls , config_path : Path ) -> list [str ]:
263+ """
264+ Return profile names defined in the Terminator config.
265+ """
266+ try :
267+ lines = config_path .read_text (encoding = "utf-8" ).splitlines (keepends = True )
268+ except Exception :
269+ return []
270+
271+ bounds = cls ._profiles_block_bounds (lines )
272+ if not bounds :
273+ return []
274+
275+ profiles_start , profiles_end = bounds
276+ names : list [str ] = []
277+ for index in range (profiles_start + 1 , profiles_end ):
278+ header_match = cls ._PROFILE_HEADER_RE .match (lines [index ].rstrip ("\r \n " ))
279+ if header_match :
280+ names .append (header_match .group (2 ).strip ())
281+ return names
282+
283+ def _fallback_restore_profile (self , config_path : Path ) -> Optional [str ]:
284+ """
285+ Pick a best-effort restore profile when runtime query is unavailable.
286+ """
287+ profiles = [name for name in self ._list_profiles (config_path ) if name != self ._config .terminator_profile_hidden ]
288+ if not profiles :
289+ return None
290+
291+ configured_base = self ._config .terminator_profile_base
292+ if configured_base in profiles and configured_base != "default" :
293+ return configured_base
294+
295+ non_default = [name for name in profiles if name != "default" ]
296+ if configured_base == "default" and len (non_default ) == 1 and "default" in profiles :
297+ return non_default [0 ]
298+
299+ if configured_base in profiles :
300+ return configured_base
301+ if len (non_default ) == 1 :
302+ return non_default [0 ]
303+ if "default" in profiles :
304+ return "default"
305+ return profiles [0 ]
306+
193307 @staticmethod
194308 def _config_path () -> Path :
195309 """
@@ -212,15 +326,10 @@ def _ensure_hidden_profile(cls, config_path: Path, base_profile: str, hidden_pro
212326 except Exception :
213327 return False
214328
215- profiles_start = next (( i for i , line in enumerate ( lines ) if line . strip () == "[profiles]" ), None )
216- if profiles_start is None :
329+ bounds = cls . _profiles_block_bounds ( lines )
330+ if not bounds :
217331 return False
218-
219- profiles_end = len (lines )
220- for index in range (profiles_start + 1 , len (lines )):
221- if cls ._TOP_LEVEL_SECTION_RE .match (lines [index ]) and lines [index ].strip () != "[profiles]" :
222- profiles_end = index
223- break
332+ profiles_start , profiles_end = bounds
224333
225334 profile_headers : list [tuple [str , int , str ]] = []
226335 for index in range (profiles_start + 1 , profiles_end ):
@@ -298,7 +407,7 @@ def detect(self) -> bool:
298407 def apply (self ) -> Optional [tuple [str , str ]]:
299408 """
300409 @brief Switch current Terminator tab to hidden-scroll profile.
301- @return ``(uuid, base_profile )`` when switching succeeds, else ``None``.
410+ @return ``(uuid, restore_profile )`` when switching succeeds, else ``None``.
302411 """
303412 terminal_uuid = os .environ .get ("TERMINATOR_UUID" )
304413 if not terminal_uuid :
@@ -311,12 +420,28 @@ def apply(self) -> Optional[tuple[str, str]]:
311420 if not config_path .is_file ():
312421 return None
313422
423+ restore_profile = self ._get_current_profile (terminal_uuid )
424+ if not restore_profile :
425+ # Older remotinator versions do not expose the active profile.
426+ # Use a config-based best effort so profile switching still works.
427+ restore_profile = self ._fallback_restore_profile (config_path )
428+ if not restore_profile :
429+ return None
430+
314431 if not self ._ensure_hidden_profile (
315432 config_path = config_path ,
316- base_profile = self . _config . terminator_profile_base ,
433+ base_profile = restore_profile ,
317434 hidden_profile = self ._config .terminator_profile_hidden ,
318435 ):
319- return None
436+ # Fallback to configured profile in case runtime query is unavailable or untrusted.
437+ if restore_profile == self ._config .terminator_profile_base :
438+ return None
439+ if not self ._ensure_hidden_profile (
440+ config_path = config_path ,
441+ base_profile = self ._config .terminator_profile_base ,
442+ hidden_profile = self ._config .terminator_profile_hidden ,
443+ ):
444+ return None
320445
321446 switched = self ._run (
322447 "remotinator" ,
@@ -329,7 +454,7 @@ def apply(self) -> Optional[tuple[str, str]]:
329454 if not switched :
330455 return None
331456
332- return (terminal_uuid , self . _config . terminator_profile_base )
457+ return (terminal_uuid , restore_profile )
333458
334459 def restore (self , state : Optional [tuple [str , str ]]) -> None :
335460 """
0 commit comments