Bug 404643 - Launching a second instance of single-instance application doesn't give focus to the running instance at Low or higher focus stealing prevention level
Summary: Launching a second instance of single-instance application doesn't give focus...
Status: RESOLVED NOT A BUG
Alias: None
Product: kwin
Classification: Plasma
Component: core (show other bugs)
Version: 5.15.0
Platform: Manjaro Linux
: NOR normal
Target Milestone: ---
Assignee: KWin default assignee
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2019-02-21 12:48 UTC by Igor Kushnir
Modified: 2019-02-27 08:32 UTC (History)
1 user (show)

See Also:
Latest Commit:
Version Fixed In:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Igor Kushnir 2019-02-21 12:48:35 UTC
SUMMARY
*Important*: the issue happens much more often if the already running instance of the application is *not* hidden to tray or minimized, when it is visible but behind one or more other windows.

This issue is application-dependent. When a second instance of a single-instance application is launched (from Konsole, Application Launcher or KRunner), the corresponding primary instance gets focus with inconsistent-across-applications focus stealing settings. I tested only different focus stealing prevention levels, have set all other Window Management settings to Defaults.

The following table lists applications and focus stealing prevention levels that give them focus (based on my testing in Plasma 5.14.5 and 5.15.0):
Level     Applications
<any>       (systemsettings5, KDevelop and KInfocenter)
<High     (GoldenDict, Thunderbird, medit)
<Low      ("qtcreator -client", Pidgin, Qmmp, LibreOffice, Calibre, Audacious, gnome-disks, <all other non-KDE apps I tested>)

I suppose applications that get focus at any level use KWindowSystem::forceActiveWindow() as described here: https://api.kde.org/frameworks/kdbusaddons/html/classKDBusService.html#af0ad38f597aedaf22a30045d0423ed0e

Those few apps that get focus at levels lower than _High_ probably use custom workarounds. For example, GoldenDict uses X11 API to emulate a left mouse button click in the top-left corner of its main window to get focus.

The apps that get focus only at the _None_ level use regular high-level APIs such as the following Qt-based application code:
  qApp->setActiveWindow( this );
  raise();
  activateWindow();

SOFTWARE/OS VERSIONS
Linux: Manjaro 64-bit
KDE Plasma Versions: 5.15.0, 5.14.5
KDE Frameworks Versions: 5.55.0, 5.54.0
Qt Versions: 5.12.1, 5.12.0

ADDITIONAL INFORMATION
From KDE documentation (https://docs.kde.org/trunk5/en/kde-workspace/kcontrol/windowbehaviour/index.html):
  Focus stealing prevention level -> Low:
    Prevention is enabled; when some window does not have support for the underlying mechanism and KWin cannot reliably decide whether to activate the window or not, it will be activated. This setting may have both worse and better results than the medium level, depending on the applications.

What "underlying mechanism" do the docs refer to? KDE frameworks APIs, KWindowSystem? If most non-KDE applications do not have support for "the underlying mechanism", then I think that the current KWin behavior does not match this documentation.

This issue came up as I tried to remove the low-level X11 mouse-click-emulation workaround from GoldenDict. Without the ugly workaround GoldenDict doesn't get focus when a second instance is launched on Plasma desktop. See my thoughts about possible alternative ways to get focus from the second instance here: https://github.com/goldendict/goldendict/issues/781#issuecomment-462841795.

How should non-KDE, pure-Qt, single-instance applications request focus on second instance launch to get it in all Plasma versions?
Comment 1 Martin Flöser 2019-02-21 17:01:06 UTC
We use regular X11 Startup notifications. Please implement the according standard.
Comment 2 Igor Kushnir 2019-02-21 19:47:15 UTC
Do you mean that each non-KDE single-instance application should contain a duplicate KWindowSystemPrivateX11::forceActiveWindow() implementation (https://code.woboq.org/qt5/kf5/kwindowsystem/src/platforms/xcb/kwindowsystem.cpp.html#_ZN23KWindowSystemPrivateX1117forceActiveWindowEyl) and possibly KStartupInfo::setNewStartupId() implementation (https://api.kde.org/frameworks/kwindowsystem/html/kstartupinfo_8cpp_source.html#l00778) in order to get focus with default Plasma Window Management settings?

Are you sure that there is no bug in KWin? Shouldn't KWin activate windows without "support for the underlying mechanism" at Low focus stealing prevention level as the option's documentation states? Note that currently a very low percentage of non-KDE windows (15% of those I've tested) gets focus if the already running instance is visible. And at least some of those ~15% get focus due to hackish workarounds rather than the according standard implementation.
Comment 3 David Edmundson 2019-02-21 21:38:24 UTC
They should not contain a fork of forceActiveWindow. 

See https://www.freedesktop.org/wiki/Software/startup-notification/ 

>How should non-KDE, pure-Qt, single-instance applications request focus on second instance launch to get it in all Plasma versions?

Pass the startup ID to the second instance. See QX11Info::setNextStartupId() 
then calling activate will "just work"
Comment 4 Igor Kushnir 2019-02-26 09:16:01 UTC
> >How should non-KDE, pure-Qt, single-instance applications request focus on second instance launch to get it in all Plasma versions?
> 
> Pass the startup ID to the second instance. See QX11Info::setNextStartupId() 
> then calling activate will "just work"

1. When an application is launched from terminal, it gets an empty QX11Info::nextStartupId(). So this simple implementation can't work as well as KDE applications in this case.

2. Even when I pass a nonempty QX11Info::nextStartupId() from the secondary instance, then pass it to QX11Info::setNextStartupId() in the primary instance, then activate the primary instance's main window, it still doesn't get focus if it is not minimized/hidden. So I haven't noticed any effect of the simple implementation. I use QtSingleApplication to pass messages between instances. Code that runs in the primary instance:
    QByteArray startupId = QByteArray::fromBase64(receivedBase64EncodedId);
    qCritical() << startupId;
    QX11Info::setNextStartupId(startupId);
    qApp->setActiveWindow(this);
    raise();
    activateWindow();

A typical received id in a KDE neon VM is "KDE-neon-VM;1551168703;784674;950_TIME254792". I tried removing the "qApp->setActiveWindow(this)" line, reordering "raise()" and "activateWindow()" lines; setting StartupNotify both to true and false in the application desktop file. But nothing makes a difference. Does the secondary instance have to explicitly send the "remove" message to end the launch sequence as described in https://standards.freedesktop.org/startup-notification-spec/startup-notification-latest.txt?
Comment 5 Martin Flöser 2019-02-26 18:42:33 UTC
I recommend using existing libraries like KRun in combination with KWindowsystem to achieve this. Way better solution than trying to reinvent the wheel.

Also I recommend to remove everything where you try to activate or raise. That's where the window manager kicks in. If you get the startup notification done the window manager will ensure the window has focus and it's raised. That's his damn job. If you try to raise or active the window manager recognizes an attempt to steal focus and prevent it - that's also his damn job.

Also ensure your user time stamps are up to date. That helps a lot.
Comment 6 Igor Kushnir 2019-02-27 08:32:31 UTC
> I recommend using existing libraries like KRun in combination with
> KWindowsystem to achieve this. Way better solution than trying to reinvent
> the wheel.
KRun is part of KIO - a tier 3 framework with complex dependencies. Few non-KDE applications would want to depend on it. Reinventing the wheel also doesn't belong to application code. No wonder that so few independent apps support the startup notification standard. This standard has to be implemented in widely used cross-platform frameworks, such as Qt, in order to increase adoption. Or at least in cross-platform, lightweight, easy-to-use libraries, such as QtSingleApplication.