Skip to content
This repository has been archived by the owner on Nov 29, 2022. It is now read-only.

ct bash: make ct follow pwd of bash? #94

Closed
LouisDuVerdier opened this issue Sep 9, 2021 · 17 comments
Closed

ct bash: make ct follow pwd of bash? #94

LouisDuVerdier opened this issue Sep 9, 2021 · 17 comments
Labels
enhancement New feature or request

Comments

@LouisDuVerdier
Copy link

LouisDuVerdier commented Sep 9, 2021

Hello,

First of all, thanks for this tool, this is really great and very easy to customize!

Just a small enhancement request in case you find this useful: certain terms such as Konsole from KDE seem to use the current pwd of the running app (bash/etc) to spawn another tab on the same pwd. I found it useful to do "ct bash" instead of bash to have colors but since ct doesn't follow bash's pwd, Konsole doesn't see the right pwd.

One way to fix that would be to periodically perform a chdir on the current pwd of the running process? To get it, one way could be to check /proc/child pid/cwd, but there might be better ways.

Don't know if this makes much sense, I can attempt to be more precise, but that would be helpful!

Thanks,
Louis

@hSaria
Copy link
Owner

hSaria commented Sep 9, 2021

I think I understand what you mean. Terminal on macOS does the same thing; by default, if you open a new tab, it'll use the same cwd.

Interestingly, the PATH doesn't seem to be populated with the usual paths. Does that happen to you?

In addition, zsh is only experiencing the issue with the PATH variable, while the cwd is maintained when opening a new tab.

I'm using execvp to spawn the process (i.e. bash), so it should inherit the environment. And it seems like it does. I'll look around to see what's going on.

@hSaria
Copy link
Owner

hSaria commented Sep 9, 2021

If you use --login when launching your terminal, it works as you would expect. For instance:

/usr/local/bin/ct /bin/bash --login

The reason the directory isn't being tracked on bash is because update_terminal_cwd is not present. In my case, it was because it wasn't being loaded from /etc/bashrc*. So when the terminal was opening a new tab, it didn't know cwd so it just defaulted. The --login argument fixes that as well as the PATH issue.

After some digging and with the help of this answer, the spawned shell is interactive non-login, but it should be interactive login. From this answer, it would seem that the terminal is signalling to the shell the login status by prefixing the shell command with - (see echo $0). Since programs like ssh can spawn a login shell, there should be a way for me to do so as well.

@LouisDuVerdier
Copy link
Author

LouisDuVerdier commented Sep 9, 2021

In term of environment propagation, this seems to be working properly, meaning that execvp seems to spawn the process (i.e. bash) in the right directory. However I'm working under Linux so maybe the behavior is different under macOS? I mean in my case there doesn't seem to be any issue with PATH.

I'll attempt to illustrate a bit the issue that I'm facing, just to be clearer:

So this works:

[considering Konsole would use ~/scripts as the default working directory, and launches "ct bash"]
Launch a new tab in Konsole
> here, ct's cwd is ~/scripts and bash inherits from ct's cwd, so it's also started in ~/scripts, which is perfect

Then going further, this doesn't work:

[same considerations as above]
Launch a new tab in Konsole
$> cd ~
> here, ct's cwd is still ~/scripts and bash updated its cwd from ~/scripts to ~
Duplicate the tab in Konsole
> new tab will be ran on the cwd of ct (~/scripts) instead of "~"

It's not a bug, but for this kind of case, it would be convenient to make "ct" periodically perform a chdir or something to update its own cwd with the cwd of its process (bash here).

Don't know if that makes more sense?

Regarding --login maybe there is something to do as well, I'm not really familiar with this.

Thank you,
Louis

@hSaria
Copy link
Owner

hSaria commented Sep 9, 2021

it would be convenient to make "ct" periodically perform a chdir or something to update its own cwd with the cwd of its process (bash here).

The path is updated because of an ANSI escape sequence that the terminal emulator processes. It's basically:

printf "\e]7;%s\a" "/path/to/file"

After every command, the shell will run whatever function in $PROMPT_COMMAND. That function contains the sequence needed to inform the terminal emulator of the current working directory.

With your current setup, what do you get when running echo $PROMPT_COMMAND? Afterwards, can you change your new terminal command to ct bash --login, launch a new terminal, and let me know the output of echo $PROMPT_COMMAND?

Also, what's the OS you're running? To save you from all of this back-and-forth, I'll set up a virtual machine to test.

@hSaria
Copy link
Owner

hSaria commented Sep 9, 2021

I installed Fedora KDE and currently testing. The login status is not affecting it; the $PROMPT_COMMAND is the same either way.

@LouisDuVerdier
Copy link
Author

LouisDuVerdier commented Sep 9, 2021

On my side I have this (Centos 8), with and without --login (I double-checked that one case was in login and the other in non-login):

printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/\~}"

Just for the sake of testing I did this to simulate ct synchronizing its chdir to the one of its child process:

With move_chdir.sh (performing a chdir from gdb):

#!/bin/bash
pid="$1"
cwd="$2"
gdb -q <<EOF
  attach $pid
  call (int) chdir("$cwd")
  detach
  quit
EOF

I ran this on the pid of "ct" to another path, and then Konsole properly duplicated the tab to this new path, so that confirmed the theory that it seems to read the cwd of the command it runs and not the prompt (not sure if this is related to --login or not then).

@LouisDuVerdier
Copy link
Author

LouisDuVerdier commented Sep 9, 2021

Small screenshots to make sure we are aligned, in case this helps:

image

At startup, things are similar, Konsole detects the cwd of ct.

image

Here Konsole didn't detect the change of pwd (probably because it reads ct's cwd that didn't move, since it doesn't follow bash's one and since bash cannot update its parent cwd).

image

Simulating the change of ct's cwd to follow bash's one: now it's fine, and duplicating the tab will run properly on ~/scripts.

Thank you!
Louis

@hSaria
Copy link
Owner

hSaria commented Sep 9, 2021

Thanks for the detailed response.

Yea, Konsole is indeed reading the cwd from the running process (src). I always thought terminal emulators relied on the ANSI sequence to update the title.

Polling the cwd wouldn't work as the spawned process is separate from the ChromaTerm instance (it's a fork with the slave running execvp using the program specified, like bash). So, I can't run os.getcwd() on the slave process as it's already replaced by whatever program is there. I'm sure I can figure something else out, though.

@LouisDuVerdier
Copy link
Author

LouisDuVerdier commented Sep 9, 2021

I guess that the only way is to do exactly what Konsole does in the source that you linked, i.e. reading the symlink of /proc/pid/cwd? Since you forked, from the parent you probably have access to the pid of the child to check this? I guess that would have to be done periodically to avoid to harm performances, if this is something you are willing to support!

@hSaria
Copy link
Owner

hSaria commented Sep 9, 2021

I'm not strictly against it, though, as with Konsole, that solution is platform dependent; macOS, for instance, doesn't even have /proc.

I'm currently experimenting with having the slave part of the fork be ChromaTerm. Seems to work so far, though still gotta experiment a bit more.

@LouisDuVerdier
Copy link
Author

Do you mean that you wish to exec in the parent and not in the child part of the fork? I guess this could work very well, however you might end up with "ct" becoming a zombie due to the parent not doing waitpid() on the child?

@hSaria
Copy link
Owner

hSaria commented Sep 9, 2021

Yea, that's pretty much what I ran into.

I'm slowly opening up to the idea of having a thread that does what you described: periodically update cwd of ChromaTerm based on /proc/__child_process__/cwd. It might not be a universal solution, but it works on enough kernels that it's reasonable to do so.

@LouisDuVerdier
Copy link
Author

LouisDuVerdier commented Sep 9, 2021

I wonder if there might be some ways to deal with this by forking twice and closing so the parent of ct becomes 1, removing the need to waitpid() and preventing zombies, not sure if that could work, especially since you are probably using pipes.

Regarding /proc/<pid>/cwd, that could be done for all but macOs (checking the os.platform() or something) that could be covered using lsof instead? I saw multiple solutions using this for macOS, but not sure how good/stable they are.

EDIT: still looking at Konsole, for macOS they seem to be using proc_pidinfo for MacProcessInfo, that might work and be better than using lsof. Not sure how this can be achieved in Python, maybe with ctypes.

@hSaria
Copy link
Owner

hSaria commented Sep 10, 2021

I remembered the I'm already using psutil that can do the platform-dependent parts of process management, like getting their cwd. It's relatively simple and, if not supported, won't do any harm.

diff --git a/chromaterm/__main__.py b/chromaterm/__main__.py
index 872dce6..d25b262 100644
--- a/chromaterm/__main__.py
+++ b/chromaterm/__main__.py
@@ -23,6 +23,9 @@ CONFIG_LOCATIONS = [
     '/etc/chromaterm/chromaterm',
 ]
 
+# The frequency to check the child process' `cwd` and update our own
+CWD_UPDATE_INTERVAL = 1
+
 # ChromaTerm cannot determine if it's processing data faster than input rate or
 # if the input has finished. Therefore, ChromaTerm waits before processing the
 # last chunk in the buffer. The waiting is stopped if data becomes ready.
@@ -361,6 +364,7 @@ def run_program(program_args):
     import fcntl
     import pty
     import termios
+    import threading
     import tty
 
     try:
@@ -398,6 +402,27 @@ def run_program(program_args):
             signal.signal(signal.SIGWINCH, window_resize_handler)
             window_resize_handler()
 
+        # Some terminals update their titles based on `cwd` (see #94)
+        def update_cwd():
+            import psutil
+            import time
+
+            try:
+                child_process = psutil.Process(pid)
+            except (psutil.AccessDenied, psutil.NoSuchProcess):
+                return
+
+            while True:
+                time.sleep(CWD_UPDATE_INTERVAL)
+
+                try:
+                    os.chdir(child_process.cwd())
+                except OSError:
+                    pass
+
+        # Daemonized to exit immediately with ChromaTerm
+        threading.Thread(target=update_cwd, daemon=True).start()
+
         return master_fd

@LouisDuVerdier
Copy link
Author

Just tested quickly, it seems to be working! :) Indeed regarding psutil, that's way better!

@hSaria
Copy link
Owner

hSaria commented Sep 10, 2021

v0.7.5 released. Thanks a lot for your help on this one.

@hSaria hSaria closed this as completed Sep 10, 2021
@LouisDuVerdier
Copy link
Author

Perfect, thank you for your time and valuable work!

@hSaria hSaria added the enhancement New feature or request label Sep 12, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants