Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support push/pop window title sequence (CSI 22/23) #2668

Open
mitchellh opened this issue Nov 14, 2024 · 14 comments
Open

Support push/pop window title sequence (CSI 22/23) #2668

mitchellh opened this issue Nov 14, 2024 · 14 comments
Labels
vt Control sequence related

Comments

@mitchellh
Copy link
Contributor

From #2667

Source: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html

This is a fairly well defined sequence that is supported -- at least in its most basic push/pop form -- by many terminals (Kitty, iTerm, xterm). It can be combined with the smcup and rmcup terminfo entries to push pop by default (xterm does this) on alt screen.

The primary sequences we should support:

         Ps = 2 2 ; 0  ⇒  Save xterm icon and window title on stack.
         Ps = 2 2 ; 2  ⇒  Save xterm window title on stack.
         Ps = 2 3 ; 0  ⇒  Restore xterm icon and window title from
       stack.
         Ps = 2 3 ; 2  ⇒  Restore xterm window title from stack.

We can ignore the "icon title" variants as they aren't used by any modern window managers that I know of. We can implement those later as needed.

For bonus points, we should consider this:

      XTWINOPS 2 2  (save/push title) and 2 3  (restore/pop title)
      accept an optional third parameter for direct access to the
      stack.  Parameters in the range 1 through 10, may be used to
      store the title into the stack or retrieve the title from the
      stack without pushing/popping.

cc @lilyball

@mitchellh mitchellh added the vt Control sequence related label Nov 14, 2024
@mitchellh
Copy link
Contributor Author

mitchellh commented Nov 14, 2024

Some details from reading the xterm source:

  • The stack size is 10 and is non-configurable
  • On push and pop, the index is 1-indexed if specified
  • On push, if an index is specified, it is used even if the stack isn't that large (i.e. index 7 but no pushes)
  • On push, an index <= 0 means 0 (push op)
  • On push and pop, the index is always % 10 so values greater than 10 are allowed and just mod
  • The stack is screen-specific (primary/alt have different title stacks)
  • Full reset (ESC c) does not reset the title stack

mitchellh added a commit that referenced this issue Nov 14, 2024
Related to #2668

This just implements the VT stream parsing. The actual handling of these
events still needs to be done.
@mitchellh
Copy link
Contributor Author

I added VT parsing for this in #2669. Implementation still needs to be done.

mitchellh added a commit that referenced this issue Nov 14, 2024
Related to #2668

This just implements the VT stream parsing. The actual handling of these
events still needs to be done.
@lilyball
Copy link

xterm uses index 0 in its smcup/rmcup entries. Does that mean the same thing as omitting the index, since you said it’s a 1-based index?

Which way does the stack grow? Is index 1 always the last-pushed item, or does each push take the next slot (and then shift everything once it fills up)?

@lilyball
Copy link

Oh also to note, the title stack should include the proxy icon data as well.

@mitchellh
Copy link
Contributor Author

mitchellh commented Nov 14, 2024

xterm uses index 0 in its smcup/rmcup entries. Does that mean the same thing as omitting the index, since you said it’s a 1-based index?

Yes.

Which way does the stack grow? Is index 1 always the last-pushed item, or does each push take the next slot (and then shift everything once it fills up)?

It's actually not implemented as a stack at all in xterm, it's implemented as a circular buffer. I haven't verified this with shell scripts, just reading the source code. I'm a doofus. It is written as a circular buffer but the "top" index is moved so it is a stack by shifting all the elements (removing the oldest).

Oh also to note, the title stack should include the proxy icon data as well.

I'll have to think about this. Obviously xterm doesn't know about pwd reporting at all so it doesn't store this, but I'll have to look into other terminals to see what they do about this. Are we also 100% sure that Terminal.app also does this?

@mitchellh
Copy link
Contributor Author

Updated my discoveries from reading the source to add that the title stack is per-screen (primary/alt have their own stack).

@lilyball
Copy link

Are we also 100% sure that Terminal.app also does this?

I am 100% sure that Terminal.app is restoring both window title and proxy icon state on a mode 1049 exit back to the values they had before entering mode 1049. I do not believe it's implementing any sort of stack, since it does not use the 22/23 sequence at all (and the macOS terminfo database doesn't include the 22/23 sequence in smcup/rmcup). Here's a script I just ran to test this:

printf '\e]7;file:///Users/lily/Documents\a\e]2;foo\a'
read
printf '\e[?1049h'
read
printf '\e]7;file:///Users/lily/Music\a\e]2;bar\a'
read
printf '\e[?1049l'
read

Since you're implementing the title stack instead of tying this to mode 1049, then the title stack should include the proxy icon. I just looked up what the "icon title" thing is in case it made more sense to use that, but nope, that was a thing for the title to use when minimizing a window into an icon.

Incidentally, Terminal.app also supports OSC 6 for the "document" path, which is like OSC 7 except it treats the path as a file instead of a directory. It stores them separately but displays the document path if set (i.e. if you set both, it shows the document path, and if you then unset the document path, it shows the working directory). Ghostty does not currently support OSC 6 (maybe it should? I'm not sure right now if any software actually uses it; vim is actually sending an OSC 7 in Ghostty when editing a file even though it doesn't do that in Terminal.app and I haven't dug into that yet) but if it does support that then it too needs to be part of the title stack.

@mitchellh
Copy link
Contributor Author

mitchellh commented Nov 15, 2024

Since you're implementing the title stack instead of tying this to mode 1049, then the title stack should include the proxy icon. I just looked up what the "icon title" thing is in case it made more sense to use that, but nope, that was a thing for the title to use when minimizing a window into an icon.

I don't think this is the way I should implement this. Since this is a macOS specific thing, if we decide to mimic it (since we support the proxy icon we maybe should), we should probably relegate this to aport-specific behavior rather than being part of libghostty at all.

The fact is that no other terminal persists the pwd as part of the title stack or mode 1049. This is purely a Terminal.app oddity. For example, it'd be really strange if this sequence occurred: (1) set pwd A (2) push title (3) set pwd B (4) pop title (5) report pwd and get "A" back. Every other terminal in the world (importantly xterm) would return "B" here.

Therefore, I think the cleanest thing to do would be to separate it from this issue altogether and consider it a wholly separate feature. And I think the right way to design that one would be to implement apprt actions when alt screen is entered/exited, and have the macOS AppKit app handle pushing/popping this.

The end result would be that the macOS app proxy icon behaves like Terminal.app, and the core VT emulation behaves like the rest of the world.

For the sake of this issue, I'll say this issue should focus solely on CSI t 22/23 with xterm compatibility.

@mitchellh
Copy link
Contributor Author

Another update from reading the xterm code, verified with a shell script (below): full reset (ESC c) does NOT reset the title stack. That is surprising.

#!/usr/bin/env bash

function csi() {
  echo -ne "\033[$1"
}


echo -ne "\033]2;Outside\a"
csi "22;0t"
csi "?1049h"
echo -ne "\033]2;Inside\a"
echo -ne "\033c"
csi "?1049l"
csi "23;0t"
read

@lilyball
Copy link

The fact is that no other terminal persists the pwd as part of the title stack or mode 1049. This is purely a Terminal.app oddity.

What other terminal even supports OSC 7? I'm guessing Kitty does simply because the Ghostty bash script uses kitty-shell-cwd:// instead of file://, but iTerm doesn't. OSC 7 was invented by Terminal.app, so it's the authority on how it's supposed to behave. If you want to tie the proxy icon restoration to the mode 1049 switch instead of the title stack I guess that's fine since either way the behavior is tput rmcup restores it, it just seems a bit odd to separate out, and means you can't do something like use the title stack codes separately from mode 1049 to bracket subshells (which otherwise would be a fairly neat way of ensuring the pwd is restored when exiting the subshell).

report pwd

I'm not aware of any code to report the pwd? It's hardly needed since processes already know their own pwd.

@mitchellh
Copy link
Contributor Author

mitchellh commented Nov 15, 2024

What other terminal even supports OSC 7?

Almost all of them nowadays (libvte supports it so that makes pretty much every Linux terminal, Kitty, WezTerm, Foot, etc.). More support it than don't in terms of terminal that are used by people. 😄

OSC 7 was invented by Terminal.app, so it's the authority on how it's supposed to behave.

There's no real authority on how terminals work unfortunately. terminal-wg is a kind of authority and they've taken over the OSC7 specification. You can take up any arguments with them. Ghostty's stance on VT sequences has always been xterm followed by the majority of other terminals.

I'll test to see if any other terminals associate pwd with 1049 or push/pop, but I still stand by that until more research is done, we should keep it separate from this issue. To be clear, that's not a no, I'm just not willing to make a hasty VT behavioral decision without more research into the issue across different terminal emulators, roping in those maintainers if necessary.

@jparise
Copy link
Collaborator

jparise commented Nov 15, 2024

I'm guessing Kitty does simply because the Ghostty bash script uses kitty-shell-cwd:// instead of file://, but iTerm doesn't.

One note here: I don't know if there's a standard for this, but file:// expects URL-escaped paths while kitty-shell-cwd:// does not:

https://github.com/kovidgoyal/kitty/blob/d31459b0926f2afddc317d76314e4afd0d07d473/kitty/utils.py#L972-L980

I believe this is why we're using kitty-shell-cwd:// in our bash and zsh shell integration scripts but file:// in our fish integration script (which has built-in support for URL escaping):

printf \e\]7\;file://%s%s\a $hostname (string escape --style=url $PWD)

Ghostty attempts to Uri.parse the values in both cases:

const uri = std.Uri.parse(url) catch |e| {
log.warn("invalid url in OSC 7: {}", .{e});
return;
};
if (!std.mem.eql(u8, "file", uri.scheme) and
!std.mem.eql(u8, "kitty-shell-cwd", uri.scheme))
{
log.warn("OSC 7 scheme must be file, got: {s}", .{uri.scheme});
return;
}

@lilyball
Copy link

I believe this is why we're using kitty-shell-cwd:// in our bash and zsh shell integration scripts but file:// in our fish integration script (which has built-in support for URL escaping):

Oh fun, so bash and zsh don't have a way of detecting when it's a remote path (beyond simply hoping the remote shell isn't set up to emit this). FWIW /etc/bashrc_Apple_Terminal actually reimplements URL escaping in pure bash.

@markpeek
Copy link
Collaborator

Throwing this in here more as an FYI...

Last week I was annoyed by the "ignoring CSI t with unimplemented parameter" log messages so I researched the xterm/iTerm/kitty implementations.

Per what @mitchellh mentioned above, xterm currently implements it as a circular buffer/stack but that was implemented in the past year-ish. Previously (xterm-384c) it was a linked list and the third parameter wasn't implemented (commit date 2023/09/15). Pulling down the xterm source for debian 12 showed it using xterm-379 with the older behavior.

iTerm implements this with NSMutableArray as a stack using the older behavior.

kitty implements it via a Python deque of length 10 and implementing the newer behavior.

Running a quick test with the below, I do see iTerm and the older xterm retain titles > 10:.

#!/usr/bin/env bash

function csi() {
  echo -ne "\033[$1"
}

function osc() {
  echo -ne "\033]$1\a"
}

for i in {1..15}; do
  echo "save title and set $i"
  csi "22;0t"
  osc "0;title $i"
  sleep 1
done

for i in {15..1}; do
  echo "pop title $i"
  csi "23;0t"
  sleep 1
done

read

This is one of those cases where implementations diverge against what is "standard" across the various terminals.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
vt Control sequence related
Projects
None yet
Development

No branches or pull requests

4 participants