File indexing completed on 2024-04-28 16:44:28
0001 /* 0002 * kstart.C. Part of the KDE project. 0003 * 0004 * SPDX-FileCopyrightText: 1997-2000 Matthias Ettrich <ettrich@kde.org> 0005 * SPDX-FileCopyrightText: David Faure <faure@kde.org> 0006 * SPDX-FileCopyrightText: Richard Moore <rich@kde.org> 0007 * 0008 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0009 */ 0010 0011 #include <config-kde-cli-tools.h> 0012 0013 #include "kstart.h" 0014 #include <ktoolinvocation.h> 0015 0016 #include <fcntl.h> 0017 #include <iostream> 0018 #include <stdlib.h> 0019 0020 #include <QApplication> 0021 #include <QCommandLineOption> 0022 #include <QCommandLineParser> 0023 #include <QDebug> 0024 #include <QDesktopWidget> 0025 #include <QRegExp> 0026 #include <QScreen> 0027 #include <QTimer> 0028 #include <QUrl> 0029 0030 #include <kaboutdata.h> 0031 #include <klocalizedstring.h> 0032 #include <kprocess.h> 0033 #include <kwindowsystem.h> 0034 0035 #include <kxmessages.h> 0036 0037 #include <KIO/ApplicationLauncherJob> 0038 #include <KIO/CommandLauncherJob> 0039 0040 #include <QX11Info> 0041 #include <X11/Xlib.h> 0042 #include <X11/Xutil.h> 0043 #include <netwm.h> 0044 0045 // some globals 0046 0047 static QString servicePath; // TODO KF6 remove 0048 static QString serviceName; 0049 static QString exe; 0050 static QStringList exeArgs; 0051 static QString url; 0052 static QString windowtitle; 0053 static QString windowclass; 0054 static int desktop = 0; 0055 static bool activate = false; 0056 static bool iconify = false; 0057 static bool fullscreen = false; 0058 static NET::States state = {}; 0059 static NET::States mask = {}; 0060 static NET::WindowType windowtype = NET::Unknown; 0061 0062 KStart::KStart() 0063 : QObject() 0064 { 0065 bool useRule = false; 0066 0067 #ifdef HAVE_X11 0068 if (QX11Info::isPlatformX11()) { 0069 NETRootInfo i(QX11Info::connection(), NET::Supported); 0070 useRule = i.isSupported(NET::WM2KDETemporaryRules); 0071 } 0072 #endif 0073 0074 if (useRule) { 0075 sendRule(); 0076 } else { 0077 // connect to window add to get the NEW windows 0078 connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &KStart::windowAdded); 0079 } 0080 0081 // finally execute the comand 0082 if (!servicePath.isEmpty()) { // TODO KF6 remove 0083 QString error; 0084 QString dbusService; 0085 int pid; 0086 if (KToolInvocation::startServiceByDesktopPath(exe, url, &error, &dbusService, &pid) == 0) { 0087 printf("%s\n", qPrintable(dbusService)); 0088 } else { 0089 qCritical() << error; 0090 } 0091 } else if (!serviceName.isEmpty()) { 0092 KService::Ptr service = KService::serviceByDesktopName(serviceName); 0093 if (!service) { 0094 qCritical() << "No such service" << exe; 0095 } else { 0096 auto *job = new KIO::ApplicationLauncherJob(service); 0097 if (!url.isEmpty()) { 0098 job->setUrls({QUrl(url)}); // TODO use QUrl::fromUserInput(PreferLocalFile)? 0099 } 0100 job->exec(); 0101 if (job->error()) { 0102 qCritical() << job->errorString(); 0103 } 0104 } 0105 } else { 0106 auto *job = new KIO::CommandLauncherJob(exe, exeArgs); 0107 job->exec(); 0108 } 0109 0110 QTimer::singleShot(useRule ? 0 : 120 * 1000, qApp, SLOT(quit())); 0111 } 0112 0113 void KStart::sendRule() 0114 { 0115 KXMessages msg; 0116 QString message; 0117 if (!windowtitle.isEmpty()) { 0118 message += QStringLiteral("title=") + windowtitle + QStringLiteral("\ntitlematch=3\n"); // 3 = regexp match 0119 } 0120 if (!windowclass.isEmpty()) { 0121 message += QStringLiteral("wmclass=") + windowclass + QStringLiteral("\nwmclassmatch=1\n") // 1 = exact match 0122 + QStringLiteral("wmclasscomplete=") 0123 // if windowclass contains a space (i.e. 2 words, use whole WM_CLASS) 0124 + (windowclass.contains(QLatin1Char(' ')) ? QStringLiteral("true") : QStringLiteral("false")) + QLatin1Char('\n'); 0125 } 0126 if ((!windowtitle.isEmpty()) || (!windowclass.isEmpty())) { 0127 // always ignore these window types 0128 message += QStringLiteral("types=") 0129 + QString().setNum(-1U & ~(NET::TopMenuMask | NET::ToolbarMask | NET::DesktopMask | NET::SplashMask | NET::MenuMask)) + QLatin1Char('\n'); 0130 } else { 0131 // accept only "normal" windows 0132 message += QStringLiteral("types=") + QString().setNum(NET::NormalMask | NET::DialogMask) + QLatin1Char('\n'); 0133 } 0134 if ((desktop > 0 && desktop <= KWindowSystem::numberOfDesktops()) || desktop == NETWinInfo::OnAllDesktops) { 0135 message += QStringLiteral("desktop=") + QString().setNum(desktop) + QStringLiteral("\ndesktoprule=3\n"); 0136 } 0137 if (activate) { 0138 message += QStringLiteral("fsplevel=0\nfsplevelrule=2\n"); 0139 } 0140 if (iconify) { 0141 message += QStringLiteral("minimize=true\nminimizerule=3\n"); 0142 } 0143 if (windowtype != NET::Unknown) { 0144 message += QStringLiteral("type=") + QString().setNum(windowtype) + QStringLiteral("\ntyperule=2"); 0145 } 0146 if (state) { 0147 if (state & NET::KeepAbove) { 0148 message += QStringLiteral("above=true\naboverule=3\n"); 0149 } 0150 if (state & NET::KeepBelow) { 0151 message += QStringLiteral("below=true\nbelowrule=3\n"); 0152 } 0153 if (state & NET::SkipTaskbar) { 0154 message += QStringLiteral("skiptaskbar=true\nskiptaskbarrule=3\n"); 0155 } 0156 if (state & NET::SkipPager) { 0157 message += QStringLiteral("skippager=true\nskippagerrule=3\n"); 0158 } 0159 if (state & NET::MaxVert) { 0160 message += QStringLiteral("maximizevert=true\nmaximizevertrule=3\n"); 0161 } 0162 if (state & NET::MaxHoriz) { 0163 message += QStringLiteral("maximizehoriz=true\nmaximizehorizrule=3\n"); 0164 } 0165 if (state & NET::FullScreen) { 0166 message += QStringLiteral("fullscreen=true\nfullscreenrule=3\n"); 0167 } 0168 } 0169 0170 msg.broadcastMessage("_KDE_NET_WM_TEMPORARY_RULES", message, -1); 0171 } 0172 0173 const NET::WindowTypes SUPPORTED_WINDOW_TYPES_MASK = NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask 0174 | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask; 0175 0176 void KStart::windowAdded(WId w) 0177 { 0178 KWindowInfo info(w, NET::WMWindowType | NET::WMName); 0179 0180 // always ignore these window types 0181 if (info.windowType(SUPPORTED_WINDOW_TYPES_MASK) == NET::TopMenu || info.windowType(SUPPORTED_WINDOW_TYPES_MASK) == NET::Toolbar 0182 || info.windowType(SUPPORTED_WINDOW_TYPES_MASK) == NET::Desktop) { 0183 return; 0184 } 0185 0186 if (!windowtitle.isEmpty()) { 0187 QString title = info.name().toLower(); 0188 QRegExp r(windowtitle.toLower()); 0189 if (!r.exactMatch(title)) { 0190 return; // no match 0191 } 0192 } 0193 if (windowtitle.isEmpty() && windowclass.isEmpty()) { 0194 // accept only "normal" windows 0195 if (info.windowType(SUPPORTED_WINDOW_TYPES_MASK) != NET::Unknown && info.windowType(SUPPORTED_WINDOW_TYPES_MASK) != NET::Normal 0196 && info.windowType(SUPPORTED_WINDOW_TYPES_MASK) != NET::Dialog) { 0197 return; 0198 } 0199 } 0200 applyStyle(w); 0201 QApplication::exit(); 0202 } 0203 0204 void KStart::applyStyle(WId w) 0205 { 0206 if (state || iconify || windowtype != NET::Unknown || desktop >= 1) { 0207 XWithdrawWindow(QX11Info::display(), w, QX11Info::appScreen()); 0208 } 0209 0210 NETWinInfo info(QX11Info::connection(), w, QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); 0211 0212 if ((desktop > 0 && desktop <= KWindowSystem::numberOfDesktops()) || desktop == NETWinInfo::OnAllDesktops) { 0213 info.setDesktop(desktop); 0214 } 0215 0216 if (iconify) { 0217 XWMHints *hints = XGetWMHints(QX11Info::display(), w); 0218 if (hints) { 0219 hints->flags |= StateHint; 0220 hints->initial_state = IconicState; 0221 XSetWMHints(QX11Info::display(), w, hints); 0222 XFree(hints); 0223 } 0224 } 0225 0226 if (windowtype != NET::Unknown) { 0227 info.setWindowType(windowtype); 0228 } 0229 0230 if (state) { 0231 info.setState(state, mask); 0232 } 0233 0234 if (fullscreen) { 0235 QRect r = QGuiApplication::primaryScreen()->geometry(); 0236 XMoveResizeWindow(QX11Info::display(), w, r.x(), r.y(), r.width(), r.height()); 0237 } 0238 0239 XSync(QX11Info::display(), False); 0240 0241 XMapWindow(QX11Info::display(), w); 0242 XSync(QX11Info::display(), False); 0243 0244 if (activate) { 0245 KWindowSystem::forceActiveWindow(w); 0246 } 0247 } 0248 0249 int main(int argc, char *argv[]) 0250 { 0251 QApplication app(argc, argv); 0252 KLocalizedString::setApplicationDomain("kstart5"); 0253 0254 KAboutData aboutData(QStringLiteral("kstart"), 0255 i18n("KStart"), 0256 QString::fromLatin1(PROJECT_VERSION), 0257 i18n("" 0258 "Utility to launch applications with special window properties \n" 0259 "such as iconified, maximized, a certain virtual desktop, a special decoration\n" 0260 "and so on."), 0261 KAboutLicense::GPL, 0262 i18n("(C) 1997-2000 Matthias Ettrich (ettrich@kde.org)")); 0263 0264 aboutData.addAuthor(i18n("Matthias Ettrich"), QString(), QStringLiteral("ettrich@kde.org")); 0265 aboutData.addAuthor(i18n("David Faure"), QString(), QStringLiteral("faure@kde.org")); 0266 aboutData.addAuthor(i18n("Richard J. Moore"), QString(), QStringLiteral("rich@kde.org")); 0267 KAboutData::setApplicationData(aboutData); 0268 0269 QCommandLineParser parser; 0270 aboutData.setupCommandLine(&parser); 0271 parser.addOption(QCommandLineOption(QStringList() << QLatin1String("!+command"), i18n("Command to execute"))); 0272 // TODO KF6 remove 0273 parser.addOption( 0274 QCommandLineOption(QStringList() << QLatin1String("service"), 0275 i18n("Alternative to <command>: desktop file path to start. D-Bus service will be printed to stdout. Deprecated: use --application"), 0276 QLatin1String("desktopfile"))); 0277 parser.addOption(QCommandLineOption(QStringList() << QLatin1String("application"), 0278 i18n("Alternative to <command>: desktop file to start."), 0279 QLatin1String("desktopfile"))); 0280 parser.addOption( 0281 QCommandLineOption(QStringList() << QLatin1String("url"), i18n("Optional URL to pass <desktopfile>, when using --service"), QLatin1String("url"))); 0282 // "!" means: all options after command are treated as arguments to the command 0283 parser.addOption( 0284 QCommandLineOption(QStringList() << QLatin1String("window"), i18n("A regular expression matching the window title"), QLatin1String("regexp"))); 0285 parser.addOption(QCommandLineOption(QStringList() << QLatin1String("windowclass"), 0286 i18n("A string matching the window class (WM_CLASS property)\n" 0287 "The window class can be found out by running\n" 0288 "'xprop | grep WM_CLASS' and clicking on a window\n" 0289 "(use either both parts separated by a space or only the right part).\n" 0290 "NOTE: If you specify neither window title nor window class,\n" 0291 "then the very first window to appear will be taken;\n" 0292 "omitting both options is NOT recommended."), 0293 QLatin1String("class"))); 0294 parser.addOption( 0295 QCommandLineOption(QStringList() << QLatin1String("desktop"), i18n("Desktop on which to make the window appear"), QLatin1String("number"))); 0296 parser.addOption(QCommandLineOption(QStringList() << QLatin1String("currentdesktop"), 0297 i18n("Make the window appear on the desktop that was active\nwhen starting the application"))); 0298 parser.addOption(QCommandLineOption(QStringList() << QLatin1String("alldesktops"), i18n("Make the window appear on all desktops"))); 0299 parser.addOption(QCommandLineOption(QStringList() << QLatin1String("iconify"), i18n("Iconify the window"))); 0300 parser.addOption(QCommandLineOption(QStringList() << QLatin1String("maximize"), i18n("Maximize the window"))); 0301 parser.addOption(QCommandLineOption(QStringList() << QLatin1String("maximize-vertically"), i18n("Maximize the window vertically"))); 0302 parser.addOption(QCommandLineOption(QStringList() << QLatin1String("maximize-horizontally"), i18n("Maximize the window horizontally"))); 0303 parser.addOption(QCommandLineOption(QStringList() << QLatin1String("fullscreen"), i18n("Show window fullscreen"))); 0304 parser.addOption(QCommandLineOption(QStringList() << QLatin1String("type"), 0305 i18n("The window type: Normal, Desktop, Dock, Toolbar, \nMenu, Dialog, TopMenu or Override"), 0306 QLatin1String("type"))); 0307 parser.addOption(QCommandLineOption(QStringList() << QLatin1String("activate"), 0308 i18n("Jump to the window even if it is started on a \n" 0309 "different virtual desktop"))); 0310 parser.addOption( 0311 QCommandLineOption(QStringList() << QLatin1String("ontop") << QLatin1String("keepabove"), i18n("Try to keep the window above other windows"))); 0312 parser.addOption( 0313 QCommandLineOption(QStringList() << QLatin1String("onbottom") << QLatin1String("keepbelow"), i18n("Try to keep the window below other windows"))); 0314 parser.addOption(QCommandLineOption(QStringList() << QLatin1String("skiptaskbar"), i18n("The window does not get an entry in the taskbar"))); 0315 parser.addOption(QCommandLineOption(QStringList() << QLatin1String("skippager"), i18n("The window does not get an entry on the pager"))); 0316 0317 parser.process(app); 0318 aboutData.processCommandLine(&parser); 0319 0320 if (parser.isSet(QStringLiteral("service"))) { 0321 servicePath = parser.value(QStringLiteral("service")); 0322 url = parser.value(QStringLiteral("url")); 0323 } else if (parser.isSet(QStringLiteral("application"))) { 0324 serviceName = parser.value(QStringLiteral("application")); 0325 url = parser.value(QStringLiteral("url")); 0326 } else { 0327 QStringList positionalArgs = parser.positionalArguments(); 0328 if (positionalArgs.isEmpty()) { 0329 qCritical() << i18n("No command specified"); 0330 parser.showHelp(1); 0331 } 0332 0333 exe = positionalArgs.takeFirst(); 0334 exeArgs = positionalArgs; 0335 } 0336 0337 desktop = parser.value(QStringLiteral("desktop")).toInt(); 0338 if (parser.isSet(QStringLiteral("alldesktops"))) { 0339 desktop = NETWinInfo::OnAllDesktops; 0340 } 0341 if (parser.isSet(QStringLiteral("currentdesktop"))) { 0342 desktop = KWindowSystem::currentDesktop(); 0343 } 0344 0345 windowtitle = parser.value(QStringLiteral("window")); 0346 windowclass = parser.value(QStringLiteral("windowclass")); 0347 if (!windowclass.isEmpty()) { 0348 windowclass = windowclass.toLower(); 0349 } 0350 0351 if (windowtitle.isEmpty() && windowclass.isEmpty()) { 0352 qWarning() << "Omitting both --window and --windowclass arguments is not recommended"; 0353 } 0354 0355 QString s = parser.value(QStringLiteral("type")); 0356 if (!s.isEmpty()) { 0357 s = s.toLower(); 0358 if (s == QLatin1String("desktop")) { 0359 windowtype = NET::Desktop; 0360 } else if (s == QLatin1String("dock")) { 0361 windowtype = NET::Dock; 0362 } else if (s == QLatin1String("toolbar")) { 0363 windowtype = NET::Toolbar; 0364 } else if (s == QLatin1String("menu")) { 0365 windowtype = NET::Menu; 0366 } else if (s == QLatin1String("dialog")) { 0367 windowtype = NET::Dialog; 0368 } else if (s == QLatin1String("override")) { 0369 windowtype = NET::Override; 0370 } else if (s == QLatin1String("topmenu")) { 0371 windowtype = NET::TopMenu; 0372 } else { 0373 windowtype = NET::Normal; 0374 } 0375 } 0376 0377 if (parser.isSet(QStringLiteral("keepabove"))) { 0378 state |= NET::KeepAbove; 0379 mask |= NET::KeepAbove; 0380 } else if (parser.isSet(QStringLiteral("keepbelow"))) { 0381 state |= NET::KeepBelow; 0382 mask |= NET::KeepBelow; 0383 } 0384 0385 if (parser.isSet(QStringLiteral("skiptaskbar"))) { 0386 state |= NET::SkipTaskbar; 0387 mask |= NET::SkipTaskbar; 0388 } 0389 0390 if (parser.isSet(QStringLiteral("skippager"))) { 0391 state |= NET::SkipPager; 0392 mask |= NET::SkipPager; 0393 } 0394 0395 activate = parser.isSet(QStringLiteral("activate")); 0396 0397 if (parser.isSet(QStringLiteral("maximize"))) { 0398 state |= NET::Max; 0399 mask |= NET::Max; 0400 } 0401 if (parser.isSet(QStringLiteral("maximize-vertically"))) { 0402 state |= NET::MaxVert; 0403 mask |= NET::MaxVert; 0404 } 0405 if (parser.isSet(QStringLiteral("maximize-horizontally"))) { 0406 state |= NET::MaxHoriz; 0407 mask |= NET::MaxHoriz; 0408 } 0409 0410 iconify = parser.isSet(QStringLiteral("iconify")); 0411 if (parser.isSet(QStringLiteral("fullscreen"))) { 0412 NETRootInfo i(QX11Info::connection(), NET::Supported); 0413 if (i.isSupported(NET::FullScreen)) { 0414 state |= NET::FullScreen; 0415 mask |= NET::FullScreen; 0416 } else { 0417 windowtype = NET::Override; 0418 fullscreen = true; 0419 } 0420 } 0421 0422 fcntl(XConnectionNumber(QX11Info::display()), F_SETFD, 1); 0423 0424 KStart start; 0425 0426 return app.exec(); 0427 }