├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── kmozillahelper.notifyrc ├── main.cpp └── main.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | # Cmake 31 | build/ 32 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.12) 2 | 3 | project( kmozillahelper ) 4 | 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 6 | 7 | find_package(ECM 1.0.0 REQUIRED NO_MODULE) 8 | 9 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) 10 | 11 | include(KDEInstallDirs) 12 | include(KDECMakeSettings) 13 | include(KDECompilerSettings) 14 | include(FeatureSummary) 15 | 16 | find_package(KF5 REQUIRED COMPONENTS Notifications KIO WindowSystem I18n) 17 | 18 | add_executable(kmozillahelper main.cpp) 19 | 20 | target_link_libraries(kmozillahelper KF5::I18n KF5::KIOWidgets KF5::Notifications KF5::WindowSystem) 21 | 22 | install(TARGETS kmozillahelper DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/mozilla/) 23 | install(FILES kmozillahelper.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR}) 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 The openSUSE Project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kmozillahelper 2 | KDE mozilla integration 3 | -------------------------------------------------------------------------------- /kmozillahelper.notifyrc: -------------------------------------------------------------------------------- 1 | [Global] 2 | IconName=firefox 3 | Comment=Firefox 4 | 5 | # this is borrowed from KGet 6 | [Event/downloadfinished] 7 | Name=Download Finished 8 | Name[ar]=التنزيل انتهى 9 | Name[ca]=Ha acabat la descàrrega 10 | Name[cs]=Stahování ukončeno 11 | Name[da]=Download afsluttet 12 | Name[de]=Download abgeschlossen 13 | Name[el]=Η μεταφορά ολοκληρώθηκε 14 | Name[eo]=Elŝuto finis 15 | Name[es]=Descarga finalizada 16 | Name[et]=Allalaadimine lõpetatud 17 | Name[eu]=Deskarga amaituta 18 | Name[fi]=Haku valmistui 19 | Name[fr]=Téléchargement terminé 20 | Name[ga]=Críochnaíodh an tÍosluchtú 21 | Name[gl]=Rematou unha transferencia 22 | Name[hi]=डाउनलोड सम्पन्न 23 | Name[hne]=डाउनलोड पूरा 24 | Name[hu]=Egy letöltés befejeződött 25 | Name[is]=Niðurhali lokið 26 | Name[it]=Scaricamento completato 27 | Name[ja]=ダウンロード完了 28 | Name[km]=បាន​បញ្ចប់​ការ​ទាញយក 29 | Name[ko]=다운로드 완료됨 30 | Name[lt]=Atsiuntimas baigtas 31 | Name[lv]=Lejupielāde pabeigta 32 | Name[ml]=ഇറക്കിവെപ്പ്‍ അവസാനിച്ചു 33 | Name[nb]=Nedlasting ferdig 34 | Name[nds]=Daalladen afslaten 35 | Name[nl]=Download voltooid 36 | Name[nn]=Nedlastinga er ferdig 37 | Name[pa]=ਡਾਊਨਲੋਡ ਮੁਕੰਮਲ ਹੋਇਆ 38 | Name[pl]=Pobieranie zakończone 39 | Name[pt]=Transferência Terminada 40 | Name[pt_BR]=Download concluído 41 | Name[ro]=Descărcare încheiată 42 | Name[ru]=Загрузка завершена 43 | Name[sl]=Konec prenosa 44 | Name[sr]=Преузимање завршено 45 | Name[sr@latin]=Preuzimanje završeno 46 | Name[sv]=Nerladdning klar 47 | Name[tr]=İndirme İşlemi Bitti 48 | Name[uk]=Звантаження завершено 49 | Name[x-test]=xxDownload Finishedxx 50 | Name[zh_CN]=下载完成 51 | Name[zh_TW]=下載已完成 52 | Comment=Downloading finished 53 | Comment[ar]=التنزيل انتهى 54 | Comment[bn]=ডাউনলোড শেষ করল 55 | Comment[br]=Echu eo an enkargañ 56 | Comment[ca]=Ha acabat la descàrrega 57 | Comment[cs]=Stahování bylo dokončeno 58 | Comment[da]=Download afsluttet 59 | Comment[de]=Herunterladen abgeschlossen 60 | Comment[el]=Η λήψη του αρχείου ολοκληρώθηκε 61 | Comment[eo]=Elŝuto finita 62 | Comment[es]=Descarga finalizada 63 | Comment[et]=Allalaadimine lõpetatud 64 | Comment[eu]=Deskarga amaitu da 65 | Comment[fi]=Tiedoston haku valmistui 66 | Comment[fr]=Téléchargement terminé 67 | Comment[ga]=Críochnaíodh an t-íosluchtú 68 | Comment[gl]=Rematou unha transferencia 69 | Comment[he]=ההורדה הסתיימה 70 | Comment[hi]=डाउनलोडिंग सम्पन्न 71 | Comment[hne]=डाउनलोडिंग पूरा 72 | Comment[hu]=Egy letöltés befejeződött 73 | Comment[is]=Niðurhali lokið 74 | Comment[it]=Scaricamento completato 75 | Comment[ja]=ダウンロードが完了しました 76 | Comment[km]=បាន​បញ្ចប់​ការ​ទាញយក 77 | Comment[ko]=다운로드 완료됨 78 | Comment[lt]=Atsiuntimas baigtas 79 | Comment[lv]=Lejupielāde ir pabeigta 80 | Comment[ml]=ഇറക്കിവെക്കല്‍ അവസാനിച്ചു 81 | Comment[nb]=Nedlasting ferdig 82 | Comment[nds]=Daalladen beendt 83 | Comment[nl]=Het downloaden is voltooid 84 | Comment[nn]=Nedlastinga er ferdig 85 | Comment[pa]=ਡਾਊਨਲੋਡ ਮੁਕੰਮਲ ਹੋਇਆ 86 | Comment[pl]=Pobieranie pliku zostało zakończone 87 | Comment[pt]=A transferência terminou 88 | Comment[pt_BR]=Download concluído 89 | Comment[ro]=Descărcarea s-a încheiat 90 | Comment[ru]=Загрузка завершена 91 | Comment[sk]=Sťahovanie ukončené 92 | Comment[sl]=Prenašanje se je zaključilo 93 | Comment[sr]=Преузимање је завршено 94 | Comment[sr@latin]=Preuzimanje je završeno 95 | Comment[sv]=Nerladdning klar 96 | Comment[tr]=Dosya indirme tamamlandı 97 | Comment[uk]=Звантаження завершено 98 | Comment[x-test]=xxDownloading finishedxx 99 | Comment[zh_CN]=下载已完成 100 | Comment[zh_HK]=下載已完成 101 | Comment[zh_TW]=下載已完成 102 | Action=Popup 103 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | 3 | Copyright (C) 2009 Lubos Lunak 4 | Copyright (C) 2017 Fabian Vogt 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 20 | AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | ******************************************************************/ 24 | 25 | #include "main.h" 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | 55 | //#define DEBUG_KDE 56 | 57 | #define HELPER_VERSION 6 58 | #define APP_HELPER_VERSION "5.0.6" 59 | 60 | int main(int argc, char* argv[]) 61 | { 62 | // Avoid getting started by the session manager 63 | qunsetenv("SESSION_MANAGER"); 64 | 65 | QApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); 66 | 67 | QApplication app(argc, argv); 68 | 69 | QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); 70 | 71 | // Check whether we're called from Firefox or Thunderbird 72 | QString appname = i18n("Mozilla Firefox"); 73 | QString parent = QFile::symLinkTarget(QStringLiteral("/proc/%1/exe").arg(int(getppid()))); 74 | if(parent.contains("thunderbird", Qt::CaseInsensitive)) 75 | appname = i18n("Mozilla Thunderbird"); 76 | 77 | // This shows on file dialogs 78 | KAboutData about("kmozillahelper", appname, APP_HELPER_VERSION); 79 | about.setBugAddress("https://bugzilla.opensuse.org/enter_bug.cgi"); 80 | KAboutData::setApplicationData(about); 81 | QApplication::setQuitOnLastWindowClosed(false); 82 | 83 | QCommandLineParser parser; 84 | about.setupCommandLine(&parser); 85 | 86 | app.setQuitOnLastWindowClosed(false); 87 | 88 | Helper helper; 89 | 90 | app.installEventFilter(&helper); 91 | 92 | return app.exec(); 93 | } 94 | 95 | Helper::Helper() 96 | : notifier(STDIN_FILENO, QSocketNotifier::Read) 97 | , arguments_read(false) 98 | { 99 | connect(¬ifier, &QSocketNotifier::activated, 100 | this, &Helper::readCommand); 101 | } 102 | 103 | void Helper::readCommand() 104 | { 105 | QString command = readLine(); 106 | if(!std::cin.good()) 107 | { 108 | #ifdef DEBUG_KDE 109 | std::cerr << "EOF, exiting." << std::endl; 110 | #endif 111 | QCoreApplication::exit(); 112 | return; 113 | } 114 | 115 | /* Allow multiple commands at once. 116 | Firefox nests the event loop in the same way we do, 117 | so if a file dialog is open, another command may arrive which we handle 118 | in our nested event loop... 119 | // For now we only allow one command at once. 120 | // We need to do this as dialogs spawn their own eventloop and thus they get nested... 121 | notifier.setEnabled(false); */ 122 | 123 | #ifdef DEBUG_KDE 124 | std::cerr << "COMMAND: " << command.toStdString() << std::endl; 125 | #endif 126 | bool status; 127 | if(command == "CHECK") 128 | status = handleCheck(); 129 | else if(command == "GETPROXY") 130 | status = handleGetProxy(); 131 | else if(command == "HANDLEREXISTS") 132 | status = handleHandlerExists(); 133 | else if(command == "GETFROMEXTENSION") 134 | status = handleGetFromExtension(); 135 | else if(command == "GETFROMTYPE") 136 | status = handleGetFromType(); 137 | else if(command == "GETAPPDESCFORSCHEME") 138 | status = handleGetAppDescForScheme(); 139 | else if(command == "APPSDIALOG") 140 | status = handleAppsDialog(); 141 | else if(command == "GETOPENFILENAME") 142 | status = handleGetOpenOrSaveX(false, false); 143 | else if(command == "GETOPENURL") 144 | status = handleGetOpenOrSaveX(true, false); 145 | else if(command == "GETSAVEFILENAME") 146 | status = handleGetOpenOrSaveX(false, true); 147 | else if(command == "GETSAVEURL") 148 | status = handleGetOpenOrSaveX(true, true); 149 | else if(command == "GETDIRECTORYFILENAME") 150 | status = handleGetDirectoryX(false); 151 | else if(command == "GETDIRECTORYURL") 152 | status = handleGetDirectoryX(true); 153 | else if(command == "OPEN") 154 | status = handleOpen(); 155 | else if(command == "REVEAL") 156 | status = handleReveal(); 157 | else if(command == "RUN") 158 | status = handleRun(); 159 | else if(command == "GETDEFAULTFEEDREADER") 160 | status = handleGetDefaultFeedReader(); 161 | else if(command == "OPENMAIL") 162 | status = handleOpenMail(); 163 | else if(command == "OPENNEWS") 164 | status = handleOpenNews(); 165 | else if(command == "ISDEFAULTBROWSER") 166 | status = handleIsDefaultBrowser(); 167 | else if(command == "SETDEFAULTBROWSER") 168 | status = handleSetDefaultBrowser(); 169 | else if(command == "DOWNLOADFINISHED") 170 | status = handleDownloadFinished(); 171 | else 172 | { 173 | std::cerr << "Unknown command for KDE helper: " << command.toStdString() << std::endl; 174 | status = false; 175 | } 176 | // status done as \1 (==ok) and \0 (==not ok), because otherwise this cannot happen 177 | // in normal data (\ is escaped otherwise) 178 | outputLine(status ? "\\1" : "\\0", false); // do not escape 179 | 180 | /* See comment on setEnabled above 181 | notifier.setEnabled(true); */ 182 | } 183 | 184 | bool Helper::handleCheck() 185 | { 186 | if(!readArguments(1)) 187 | return false; 188 | int version = getArgument().toInt(); // requested version 189 | if(!allArgumentsUsed()) 190 | return false; 191 | if(version <= HELPER_VERSION) // we must have the exact requested version 192 | return true; 193 | std::cerr << "KDE helper version too old." << std::endl; 194 | return false; 195 | } 196 | 197 | bool Helper::handleGetProxy() 198 | { 199 | if(!readArguments(1)) 200 | return false; 201 | QUrl url = QUrl::fromUserInput(getArgument()); 202 | if(!allArgumentsUsed()) 203 | return false; 204 | QString proxy; 205 | KProtocolManager::slaveProtocol(url, proxy); 206 | if(proxy.isEmpty() || proxy == "DIRECT") // TODO return DIRECT if empty? 207 | { 208 | outputLine("DIRECT"); 209 | return true; 210 | } 211 | QUrl proxyurl = QUrl::fromUserInput(proxy); 212 | if(proxyurl.isValid()) 213 | { // firefox wants this format 214 | outputLine("PROXY" " " + proxyurl.host() + ":" + QString::number(proxyurl.port())); 215 | // TODO there is also "SOCKS " type 216 | return true; 217 | } 218 | return false; 219 | } 220 | 221 | bool Helper::handleHandlerExists() 222 | { 223 | // Cache protocols types to avoid causing Thunderbird to hang (https://bugzilla.suse.com/show_bug.cgi?id=1037806). 224 | static QHash known_protocols; 225 | 226 | if(!readArguments(1)) 227 | return false; 228 | QString protocol = getArgument(); 229 | if(!allArgumentsUsed()) 230 | return false; 231 | 232 | auto it(known_protocols.find(protocol)); 233 | if(it == known_protocols.end()) 234 | it = known_protocols.insert(protocol, KProtocolInfo::isHelperProtocol(protocol)); 235 | 236 | if(*it) 237 | return true; 238 | 239 | return KMimeTypeTrader::self()->preferredService(QLatin1String("x-scheme-handler/") + protocol) != nullptr; 240 | } 241 | 242 | bool Helper::handleGetFromExtension() 243 | { 244 | if(!readArguments(1)) 245 | return false; 246 | QString ext = getArgument(); 247 | if(!allArgumentsUsed()) 248 | return false; 249 | if(!ext.isEmpty()) 250 | { 251 | QList mimeList = QMimeDatabase().mimeTypesForFileName("foo." + ext); 252 | for (const QMimeType &mime : mimeList) 253 | if(mime.isValid()) 254 | return writeMimeInfo(mime); 255 | } 256 | return false; 257 | } 258 | 259 | bool Helper::handleGetFromType() 260 | { 261 | if(!readArguments(1)) 262 | return false; 263 | QString type = getArgument(); 264 | if(!allArgumentsUsed()) 265 | return false; 266 | QMimeType mime = QMimeDatabase().mimeTypeForName(type); 267 | if(mime.isValid()) 268 | return writeMimeInfo(mime); 269 | // firefox also asks for protocol handlers using getfromtype 270 | QString app = getAppForProtocol(type); 271 | if(!app.isEmpty()) 272 | { 273 | outputLine(type); 274 | outputLine(type); // TODO probably no way to find a good description 275 | outputLine(app); 276 | return true; 277 | } 278 | return false; 279 | } 280 | 281 | bool Helper::writeMimeInfo(QMimeType mime) 282 | { 283 | KService::Ptr service = KMimeTypeTrader::self()->preferredService(mime.name()); 284 | if(service) 285 | { 286 | outputLine(mime.name()); 287 | outputLine(mime.comment()); 288 | outputLine(service->name()); 289 | return true; 290 | } 291 | return false; 292 | } 293 | 294 | bool Helper::handleGetAppDescForScheme() 295 | { 296 | if(!readArguments(1)) 297 | return false; 298 | QString scheme = getArgument(); 299 | if(!allArgumentsUsed()) 300 | return false; 301 | QString app = getAppForProtocol(scheme); 302 | if(!app.isEmpty()) 303 | { 304 | outputLine(app); 305 | return true; 306 | } 307 | return false; 308 | } 309 | 310 | bool Helper::handleAppsDialog() 311 | { 312 | if(!readArguments(1)) 313 | return false; 314 | QString title = getArgument(); 315 | long wid = getArgumentParent(); 316 | if(!allArgumentsUsed()) 317 | return false; 318 | KOpenWithDialog dialog(NULL); 319 | if(!title.isEmpty()) 320 | dialog.setWindowTitle(title); 321 | dialog.hideNoCloseOnExit(); 322 | dialog.hideRunInTerminal(); // TODO 323 | if(wid != 0) 324 | { 325 | dialog.setAttribute(Qt::WA_NativeWindow, true); 326 | QWindow *subWindow = dialog.windowHandle(); 327 | if(subWindow) 328 | KWindowSystem::setMainWindow(subWindow, wid); 329 | } 330 | if(dialog.exec()) 331 | { 332 | KService::Ptr service = dialog.service(); 333 | QString command; 334 | if(service) 335 | command = service->exec(); 336 | else if(!dialog.text().isEmpty()) 337 | command = dialog.text(); 338 | else 339 | return false; 340 | command = command.split(" ").first(); // only the actual command 341 | command = QStandardPaths::findExecutable(command); 342 | if(command.isEmpty()) 343 | return false; 344 | outputLine(QUrl::fromUserInput(command).url()); 345 | return true; 346 | } 347 | return false; 348 | } 349 | 350 | QStringList Helper::convertToNameFilters(const QString &input) 351 | { 352 | QStringList ret; 353 | 354 | // Filters separated by newline 355 | for (auto &filter : input.split('\n')) 356 | { 357 | // Filer exp and name separated by '|'. 358 | // TODO: Is it possible that | appears in either of those? 359 | auto data = filter.split('|'); 360 | 361 | if (data.length() == 1) 362 | ret.append(QStringLiteral("%0 Files(%0)").arg(data[0])); 363 | else if (data.length() >= 2) 364 | ret.append(QStringLiteral("%0 (%1)(%1)").arg(data[1]).arg(data[0])); 365 | } 366 | 367 | return ret; 368 | } 369 | 370 | bool Helper::handleGetOpenOrSaveX(bool url, bool save) 371 | { 372 | if(!readArguments(4)) 373 | return false; 374 | QUrl defaultPath = QUrl::fromLocalFile(getArgument()); 375 | // Use dialog.nameFilters() instead of filtersParsed as setNameFilters does some syntax changes 376 | QStringList filtersParsed = convertToNameFilters(getArgument()); 377 | int selectFilter = getArgument().toInt(); 378 | QString title = getArgument(); 379 | bool multiple = save ? false : isArgument("MULTIPLE"); 380 | this->wid = getArgumentParent(); 381 | if(!allArgumentsUsed()) 382 | return false; 383 | 384 | if(title.isEmpty()) 385 | title = save ? i18n("Save") : i18n("Open"); 386 | 387 | QFileDialog dialog(nullptr, title, defaultPath.path()); 388 | 389 | dialog.selectFile(defaultPath.fileName()); 390 | dialog.setNameFilters(filtersParsed); 391 | dialog.setOption(QFileDialog::DontConfirmOverwrite, false); 392 | dialog.setAcceptMode(save ? QFileDialog::AcceptSave : QFileDialog::AcceptOpen); 393 | 394 | if(save) 395 | dialog.setFileMode((QFileDialog::AnyFile)); 396 | else 397 | dialog.setFileMode(multiple ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile); 398 | 399 | if(selectFilter >= 0 && selectFilter >= dialog.nameFilters().size()) 400 | dialog.selectNameFilter(dialog.nameFilters().at(selectFilter)); 401 | 402 | // If url == false only allow local files. Impossible to do with Qt < 5.6... 403 | #if(QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) 404 | if(url == false) 405 | dialog.setSupportedSchemes(QStringList(QStringLiteral("file"))); 406 | #endif 407 | 408 | // Run dialog 409 | if(dialog.exec() != QDialog::Accepted) 410 | return false; 411 | 412 | int usedFilter = dialog.nameFilters().indexOf(dialog.selectedNameFilter()); 413 | 414 | if(url) 415 | { 416 | QList result = dialog.selectedUrls(); 417 | result.removeAll(QUrl()); 418 | if(!result.isEmpty()) 419 | { 420 | outputLine(QStringLiteral("%0").arg(usedFilter)); 421 | for (const QUrl &url : result) 422 | outputLine(url.url()); 423 | return true; 424 | } 425 | } 426 | else 427 | { 428 | QStringList result = dialog.selectedFiles(); 429 | result.removeAll(QString()); 430 | if(!result.isEmpty()) 431 | { 432 | outputLine(QStringLiteral("%0").arg(usedFilter)); 433 | for (const QString &str : result) 434 | outputLine(str); 435 | return true; 436 | } 437 | } 438 | return false; 439 | } 440 | 441 | bool Helper::handleGetDirectoryX(bool url) 442 | { 443 | if(!readArguments(2)) 444 | return false; 445 | QString startDir = getArgument(); 446 | QString title = getArgument(); 447 | this->wid = getArgumentParent(); 448 | if(!allArgumentsUsed()) 449 | return false; 450 | 451 | if(url) 452 | { 453 | QUrl result = QFileDialog::getExistingDirectoryUrl(nullptr, title, startDir); 454 | if(result.isValid()) 455 | { 456 | outputLine(result.url()); 457 | return true; 458 | } 459 | } 460 | else 461 | { 462 | QString result = QFileDialog::getExistingDirectory(nullptr, title, startDir); 463 | if(!result.isEmpty()) 464 | { 465 | outputLine(result); 466 | return true; 467 | } 468 | } 469 | return false; 470 | } 471 | 472 | bool Helper::handleOpen() 473 | { 474 | if(!readArguments(1)) 475 | return false; 476 | QUrl url = QUrl::fromUserInput(getArgument()); 477 | QString mime; 478 | if(isArgument("MIMETYPE")) 479 | mime = getArgument(); 480 | if(!allArgumentsUsed()) 481 | return false; 482 | // try to handle the case when the server has broken mimetypes and e.g. claims something is application/octet-stream 483 | QMimeType mimeType = QMimeDatabase().mimeTypeForName(mime); 484 | if(!mime.isEmpty() && mimeType.isValid() && KMimeTypeTrader::self()->preferredService(mimeType.name())) 485 | { 486 | return KRun::runUrl(url, mime, NULL, KRun::RunFlags()); // TODO parent 487 | } 488 | else 489 | { 490 | (void) new KRun(url, NULL); // TODO parent 491 | // QObject::connect(run, SIGNAL(finished()), &app, SLOT(openDone())); 492 | // QObject::connect(run, SIGNAL(error()), &app, SLOT(openDone())); 493 | return true; // TODO check for errors? 494 | } 495 | } 496 | 497 | bool Helper::handleReveal() 498 | { 499 | if(!readArguments(1)) 500 | return false; 501 | QString path = getArgument(); 502 | if(!allArgumentsUsed()) 503 | return false; 504 | const KService::List apps = KMimeTypeTrader::self()->query("inode/directory", "Application"); 505 | if(apps.size() != 0) 506 | { 507 | QString command = apps.at(0)->exec().split(" ").first(); // only the actual command 508 | if(command == "dolphin" || command == "konqueror") 509 | { 510 | command = QStandardPaths::findExecutable(command); 511 | if(command.isEmpty()) 512 | return false; 513 | return KProcess::startDetached(command, QStringList() << "--select" << path); 514 | } 515 | } 516 | QFileInfo info(path); 517 | QString dir = info.dir().path(); 518 | (void) new KRun(QUrl::fromLocalFile(dir), NULL); // TODO parent 519 | return true; // TODO check for errors? 520 | } 521 | 522 | bool Helper::handleRun() 523 | { 524 | if(!readArguments(2)) 525 | return false; 526 | QString app = getArgument(); 527 | QString arg = getArgument(); 528 | if(!allArgumentsUsed()) 529 | return false; 530 | return KRun::runCommand(KShell::quoteArg(app) + " " + KShell::quoteArg(arg), NULL); // TODO parent, ASN 531 | } 532 | 533 | bool Helper::handleGetDefaultFeedReader() 534 | { 535 | if(!readArguments(0)) 536 | return false; 537 | // firefox wants the full path 538 | QString reader = QStandardPaths::findExecutable("akregator"); // TODO there is no KDE setting for this 539 | if(!reader.isEmpty()) 540 | { 541 | outputLine(reader); 542 | return true; 543 | } 544 | return false; 545 | } 546 | 547 | bool Helper::handleOpenMail() 548 | { 549 | if(!readArguments(0)) 550 | return false; 551 | // this is based on ktoolinvocation_x11.cpp, there is no API for this 552 | KConfig config("emaildefaults"); 553 | QString groupname = KConfigGroup(&config, "Defaults").readEntry("Profile", "Default"); 554 | KConfigGroup group(&config, QString("PROFILE_%1").arg(groupname)); 555 | QString command = group.readPathEntry("EmailClient", QString()); 556 | if(command.isEmpty()) 557 | command = "kmail"; 558 | if(group.readEntry("TerminalClient", false)) 559 | { 560 | QString terminal = KConfigGroup(KSharedConfig::openConfig(), "General").readPathEntry("TerminalApplication", "konsole"); 561 | command = terminal + " -e " + command; 562 | } 563 | KService::Ptr mail = KService::serviceByDesktopName(command.split(" ").first()); 564 | if(mail) 565 | { 566 | return KRun::runService(*mail, QList(), NULL); // TODO parent 567 | } 568 | return false; 569 | } 570 | 571 | bool Helper::handleOpenNews() 572 | { 573 | if(!readArguments(0)) 574 | return false; 575 | KService::Ptr news = KService::serviceByDesktopName("knode"); // TODO there is no KDE setting for this 576 | if(news) 577 | { 578 | //KApplication::updateUserTimestamp(0); // TODO 579 | return KRun::runService(*news, QList(), NULL); // TODO parent 580 | } 581 | return false; 582 | } 583 | 584 | bool Helper::handleIsDefaultBrowser() 585 | { 586 | if(!readArguments(0)) 587 | return false; 588 | QString browser = KConfigGroup(KSharedConfig::openConfig("kdeglobals"), "General") 589 | .readEntry("BrowserApplication"); 590 | return browser == "MozillaFirefox" || browser == "MozillaFirefox.desktop" 591 | || browser == "!firefox" || browser == "!/usr/bin/firefox" 592 | || browser == "firefox" || browser == "firefox.desktop"; 593 | } 594 | 595 | bool Helper::handleSetDefaultBrowser() 596 | { 597 | if(!readArguments(1)) 598 | return false; 599 | bool alltypes = (getArgument() == "ALLTYPES"); 600 | if(!allArgumentsUsed()) 601 | return false; 602 | KConfigGroup(KSharedConfig::openConfig("kdeglobals"), "General") 603 | .writeEntry("BrowserApplication", "firefox"); 604 | if(alltypes) 605 | { 606 | // TODO there is no API for this and it is a bit complex 607 | } 608 | return true; 609 | } 610 | 611 | bool Helper::handleDownloadFinished() 612 | { 613 | if(!readArguments(1)) 614 | return false; 615 | QString download = getArgument(); 616 | if(!allArgumentsUsed()) 617 | return false; 618 | // TODO cheat a bit due to i18n freeze - the strings are in the .notifyrc file, 619 | // taken from KGet, but the notification itself needs the text too. 620 | // So create it from there. 621 | KConfig cfg("kmozillahelper.notifyrc", KConfig::FullConfig, QStandardPaths::AppDataLocation); 622 | QString message = KConfigGroup(&cfg, "Event/downloadfinished").readEntry("Comment"); 623 | KNotification::event("downloadfinished", download + " : " + message); 624 | return true; 625 | } 626 | 627 | QString Helper::getAppForProtocol(const QString& protocol) 628 | { 629 | /* Inspired by kio's krun.cpp */ 630 | const KService::Ptr service = KMimeTypeTrader::self()->preferredService(QLatin1String("x-scheme-handler/") + protocol); 631 | if (service) 632 | return service->name(); 633 | 634 | /* Some KDE services (e.g. vnc) also support application associations. 635 | * Those are known as "Helper Protocols". 636 | * However, those aren't also registered using fake mime types and there 637 | * is no link to a .desktop file... 638 | * So we need to query for the service to use and then find the .desktop 639 | * file for that application by comparing the Exec values. */ 640 | 641 | if(!KProtocolInfo::isHelperProtocol(protocol)) 642 | return {}; 643 | 644 | QString exec = KProtocolInfo::exec(protocol); 645 | 646 | if(exec.isEmpty()) 647 | return {}; 648 | 649 | if(exec.contains(' ')) 650 | exec = exec.split(' ').first(); // first part of command 651 | 652 | if(KService::Ptr service = KService::serviceByDesktopName(exec)) 653 | return service->name(); 654 | 655 | QString servicename; 656 | foreach(KService::Ptr service, KService::allServices()) 657 | { 658 | QString exec2 = service->exec(); 659 | if(exec2.contains(' ')) 660 | exec2 = exec2.split(' ').first(); // first part of command 661 | if(exec == exec2) 662 | { 663 | servicename = service->name(); 664 | break; 665 | } 666 | } 667 | 668 | if(servicename.isEmpty() && exec == "kmailservice") // kmailto is handled internally by kmailservice 669 | servicename = i18n("KDE"); 670 | 671 | return servicename; 672 | } 673 | 674 | QString Helper::readLine() 675 | { 676 | std::string line; 677 | if(!std::getline(std::cin, line)) 678 | return {}; 679 | 680 | QString qline = QString::fromStdString(line); 681 | qline.replace("\\n", "\n"); 682 | qline.replace("\\" "\\", "\\"); 683 | return qline; 684 | } 685 | 686 | /* Qt just uses the QWidget* parent as transient parent for native 687 | * platform dialogs. This makes it impossible to make them transient 688 | * to a bare QWindow*. So we catch the show event for the QDialog 689 | * and setTransientParent here instead. */ 690 | bool Helper::eventFilter(QObject *obj, QEvent *ev) 691 | { 692 | if(ev->type() == QEvent::Show && obj->inherits("QDialog")) 693 | { 694 | QWidget *widget = static_cast(obj); 695 | if(wid != 0) 696 | { 697 | widget->setAttribute(Qt::WA_NativeWindow, true); 698 | QWindow *subWindow = widget->windowHandle(); 699 | if(subWindow) 700 | KWindowSystem::setMainWindow(subWindow, wid); 701 | } 702 | } 703 | 704 | return false; 705 | } 706 | 707 | void Helper::outputLine(QString line, bool escape) 708 | { 709 | if(escape) 710 | { 711 | line.replace("\\", "\\" "\\"); 712 | line.replace("\n", "\\n"); 713 | } 714 | std::cout << line.toStdString() << std::endl; 715 | #ifdef DEBUG_KDE 716 | std::cerr << "OUTPUT: " << line.toStdString() << std::endl; 717 | #endif 718 | } 719 | 720 | bool Helper::readArguments(int mincount) 721 | { 722 | assert(arguments.isEmpty()); 723 | for(;;) 724 | { 725 | QString line = readLine(); 726 | if(!std::cin.good()) 727 | { 728 | arguments.clear(); 729 | return false; 730 | } 731 | if(line == "\\E") 732 | { 733 | arguments_read = true; 734 | if(arguments.count() >= mincount) 735 | return true; 736 | std::cerr << "Not enough arguments for KDE helper." << std::endl; 737 | return false; 738 | } 739 | arguments.append(line); 740 | } 741 | } 742 | 743 | QString Helper::getArgument() 744 | { 745 | assert(!arguments.isEmpty()); 746 | return arguments.takeFirst(); 747 | } 748 | 749 | bool Helper::isArgument(const QString& argument) 750 | { 751 | if(!arguments.isEmpty() && arguments.first() == argument) 752 | { 753 | arguments.removeFirst(); 754 | return true; 755 | } 756 | return false; 757 | } 758 | 759 | bool Helper::allArgumentsUsed() 760 | { 761 | assert(arguments_read); 762 | arguments_read = false; 763 | if(arguments.isEmpty()) 764 | return true; 765 | std::cerr << "Unused arguments for KDE helper:" << arguments.join(" ").toStdString() << std::endl; 766 | arguments.clear(); 767 | return false; 768 | } 769 | 770 | long Helper::getArgumentParent() 771 | { 772 | if(isArgument("PARENT")) 773 | return getArgument().toLong(); 774 | return 0; 775 | } 776 | -------------------------------------------------------------------------------- /main.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | 3 | Copyright (C) 2009 Lubos Lunak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 19 | AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | ******************************************************************/ 23 | 24 | #ifndef MAIN_H 25 | #define MAIN_H 26 | 27 | #include 28 | #include 29 | 30 | class Helper : public QObject 31 | { 32 | Q_OBJECT 33 | public: 34 | Helper(); 35 | private: 36 | bool handleCheck(); 37 | bool handleGetProxy(); 38 | bool handleHandlerExists(); 39 | bool handleGetFromExtension(); 40 | bool handleGetFromType(); 41 | bool handleGetAppDescForScheme(); 42 | bool handleAppsDialog(); 43 | bool handleGetOpenOrSaveX(bool url, bool save); 44 | bool handleGetDirectoryX(bool url); 45 | bool handleOpen(); 46 | bool handleReveal(); 47 | bool handleRun(); 48 | bool handleGetDefaultFeedReader(); 49 | bool handleOpenMail(); 50 | bool handleOpenNews(); 51 | bool handleIsDefaultBrowser(); 52 | bool handleSetDefaultBrowser(); 53 | bool handleDownloadFinished(); 54 | QStringList convertToNameFilters(const QString &input); 55 | bool writeMimeInfo(QMimeType mime); 56 | QString getAppForProtocol(const QString& protocol); 57 | bool readArguments(int mincount); 58 | QString getArgument(); 59 | bool isArgument(const QString& name); // also discards the line with it 60 | bool allArgumentsUsed(); 61 | long getArgumentParent(); 62 | void outputLine(QString line, bool escape = true); 63 | QString readLine(); 64 | protected: 65 | virtual bool eventFilter(QObject *obj, QEvent *ev) override; 66 | private slots: 67 | void readCommand(); 68 | private: 69 | QSocketNotifier notifier; 70 | QStringList arguments; 71 | bool arguments_read; 72 | long wid; 73 | }; 74 | 75 | #endif 76 | --------------------------------------------------------------------------------