Skip to content

Conversation

@basil
Copy link

@basil basil commented Feb 1, 2026

Fixes #606. Tested by reproducing the issue described there and verifying it no longer occurs after this PR.

OSC 7 is a standard escape sequence that allows shells to report their
current working directory to the terminal emulator.
Comment on lines +143 to +147
/**
* Sets the initial working directory.
*/
void setInitialWorkingDirectory(const QString &dir);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +136 to +153
void Pty::setInitialWorkingDirectory(const QString &dir)
{
QString pwd = dir;

// remove trailing slash in the path when appropriate
// example: /usr/share/icons/ ==> /usr/share/icons
if (pwd.length() > 1 && pwd.endsWith(QLatin1Char('/'))) {
pwd.chop(1);
}

setWorkingDirectory(pwd);

// setting PWD to "." will cause problem for bash & zsh
if (pwd != QLatin1String(".")) {
setEnv(QStringLiteral("PWD"), pwd);
}
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

connect(m_impl->m_session, SIGNAL(resizeRequest(QSize)), this, SLOT(setSize(QSize)));
connect(m_impl->m_session, SIGNAL(finished()), this, SLOT(sessionFinished()));
connect(m_impl->m_session, &Session::titleChanged, this, &QTermWidget::titleChanged);
connect(m_impl->m_session, &Session::currentDirectoryChanged, this, &QTermWidget::currentDirectoryChanged);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines -439 to -454
#ifdef Q_OS_LINUX
// Christian Surlykke: On linux we could look at /proc/<pid>/cwd which should be a link to current
// working directory (<pid>: process id of the shell). I don't know about BSD.
// Maybe we could just offer it when running linux, for a start.
QDir d(QString::fromLatin1("/proc/%1/cwd").arg(getShellPID()));
if (!d.exists())
{
qDebug() << "Cannot find" << d.dirName();
goto fallback;
}
return d.canonicalPath();
#endif

fallback:
// fallback, initial WD
return m_impl->m_session->initialWorkingDirectory();
Copy link
Author

@basil basil Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved without modification to Session.cpp.

Comment on lines +291 to +296
/**
* Emitted when the current working directory of the active session
* has changed.
*/
void currentDirectoryChanged(const QString &dir);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 307 to 311
if (!_initialWorkingDir.isEmpty()) {
_shellProcess->setWorkingDirectory(_initialWorkingDir);
_shellProcess->setInitialWorkingDirectory(_initialWorkingDir);
} else {
_shellProcess->setWorkingDirectory(cwd);
_shellProcess->setInitialWorkingDirectory(cwd);
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +377 to +382
if (what == 7) {
_reportedWorkingUrl = QUrl::fromUserInput(caption);
emit currentDirectoryChanged(currentWorkingDirectory());
modified = true;
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +202 to +207
/**
* Returns the current directory of the foreground process in the
* session.
*/
QString currentWorkingDirectory();

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +431 to +437
/**
* Emitted when the current working directory of this session changes.
*
* @param dir The new current working directory of the session.
*/
void currentDirectoryChanged(const QString &dir);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

int _sessionId;

QString _initialWorkingDir;
QUrl _reportedWorkingUrl;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tsujan
Copy link
Member

tsujan commented Feb 1, 2026

Thanks a lot for the PR!

It should be checked thoroughly for making sure that it won't cause a regression anywhere. Also, there may be a more direct way of doing it. Sadly, I may not have the required time soon. So, unless another dev reviews it, please be patient.

@basil
Copy link
Author

basil commented Feb 1, 2026

The code is taken verbatim from Konsole (references linked above) and has been stable for many years. In addition to that, I have been using it as my daily driver with no issues.

There is not a more direct way of doing this. I know this because I have implemented this previously in Ghostty as well. The Konsole approach (directly copied into this PR) is standard among all modern terminal emulators. The shell keeps track of changes to the current working directory. OSC 7 codes are needed for the shell to inform the terminal emulator of directory changes. (Ghostty goes one step further than Konsole and actually injects OSC 7 code into the shell's startup sequence, a bit too much magic for me.) The terminal can then parse the OSC 7 code and know the current working directory reliably. The only remaining step is to set PWD when starting a new shell (to the value retrieved from the OSC 7 code), without which the shell would have to recompute the realpath from /proc/$pid/cwd and all the work we did would be lost. Thus the handoff is complete.

@tsujan
Copy link
Member

tsujan commented Feb 1, 2026

The code is taken verbatim from Konsole

Konsole has bugs, and as I mentioned elsewhere, the codes of konsole and qterminal have diverged.

@basil
Copy link
Author

basil commented Feb 1, 2026

All software has bugs, but code that has been shipping in production for years without any major issues being reported is less likely to have them than code that has been freshly written. This PR consists of the former rather than the latter. While Konsole has diverged plenty in other areas, in this particular area it has diverged very little.

As for my patience, mentioned in a previous comment, I serve on the governing board of a large open source project, as well as an elected role in the Linux Foundation, so I am quite familiar with the time scale of open source software development. Some changes take days, other takes weeks, while yet others take months or years, but ultimately working code that solves problems prevails.

@tsujan
Copy link
Member

tsujan commented Feb 1, 2026

As for my patience…

I asked for it because I should deal with lots of codes in several apps (during the last hour, I was working on 5 different apps), and understandably I can do it only in my free time. We lack enough manpower, and I got very happy when I saw that you attended to those 2 reports. Please keep up the good work.

@basil
Copy link
Author

basil commented Feb 1, 2026

That comes with the territory of open source projects maintained by volunteers, with which (as I mentioned) I am more than familiar. Take your time.

To summarize the flow of things (in virtually all modern terminal emulators):

  • The shell uses PWD if passed to it from the terminal emulator (ideally), or else does a real path of /proc/$pid/cwd (not ideal, because it resolves symlinks to the realpath).
  • As you use the cd shell builtin, the shell keeps track of the current working directory (with correct symlink names).
  • If you configure the shell to emit this via OSC 7, then the shell reports this back to the terminal emulator with every command.
  • If the terminal emulator can read the OSC 7 emitted by the shell, now the terminal emulator can reliably keep track of the (symlink) working directory.
  • Once the terminal emulator can keep track of this reliably (and not by having to query /proc from the kernel), it can then pass this to new shells (e.g. new tabs or new windows) in the PWD environment variable. The shell uses this in step 1 if provided rather than calculating its own.

Hope this helps!

@tsujan
Copy link
Member

tsujan commented Feb 3, 2026

The code seems good and works with qterminal -w ….

But with this patch, when I open QTerminal inside the symlink folder with pcmanfm-qt (→ #606 (comment)), it shows the target folder instead. Konsole has the same problem but XTerm doesn't — and, of course, QTerminal didn't either without the patch. So, I think Konsole's problem is transferred to qtermwidget now.

I don't think the issue can be in libfm-qt but will check its code again later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Do not resolve the symlink for initial working directory

2 participants