diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f7a23d..b02d601 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,24 @@ +cmake_minimum_required(VERSION 3.1) project (activityfox) - + +cmake_policy(SET CMP0059 NEW) + find_package(KDE4 REQUIRED) include_directories(${KDE4_INCLUDES}) -set(tutorial2_SRCS - main.cpp +set(SOURCE_FILES + src/main.cpp + src/BrowserManager.h + src/BrowserManager.cpp + src/FirefoxManager.h + src/FirefoxManager.cpp + src/ChromiumManager.h + src/ChromiumManager.cpp + src/GoogleChromeManager.h + src/GoogleChromeManager.cpp ) -kde4_add_executable(activityfox ${tutorial2_SRCS}) -target_link_libraries(activityfox ${KDE4_KDEUI_LIBS}) \ No newline at end of file +kde4_add_executable(activityfox ${SOURCE_FILES}) +target_link_libraries(activityfox ${KDE4_KDEUI_LIBS}) + +set_property(TARGET activityfox PROPERTY CXX_STANDARD 11) \ No newline at end of file diff --git a/README b/README deleted file mode 100644 index 9f51651..0000000 --- a/README +++ /dev/null @@ -1,99 +0,0 @@ -activityfox is a small KDE program that acts as a workaround to allow Firefox to -play nice with KDE Plasma Activities. - -See this blog post for more info: -http://yuenhoe.com/blog/2012/08/associating-firefox-profiles-with-kde-activities/ - -============ -Installing -============ - -Before you build, you may need to change the firefoxbin_path variable at the top of -main.cpp to the path of the firefox-bin executable on your system. It will usually be at - -"/usr/lib/firefox/firefox-bin" for 32bit systems -and -"/usr/lib64/firefox/firefox-bin" for 64bit systems. - -NOTE: It needs to be the *firefox-bin* executable path, not *firefox*, because -firefox will just spawn firefox-bin, which will end up out of reach of the program -preventing it from killing firefox when you stop the associated activity. - -When done, just do the usual: -> mkdir build -> cd build -> cmake .. -> make - -That should give you the 'activityfox' executable in the build folder. If you want -you can copy it somewhere accessible, like somewhere in $PATH - -=========== -Usage -=========== - -To associate a Firefox profile with a KDE activity, first activate and enter that -activity on your desktop. While in that activity, run: - -> activityfox - -The profile doesn't need to exist: the program will create it if it doesn't. A small -blank window should appear, which would launch the Firefox process by proxy. -Wait a bit, and a Firefox window for that profile should appear. If the profile is new -then everything will be set to default settings, with no extensions, themes and whatnot: -that's how Firefox profiles work. Do what you want with the profile. In particular, -you would want to set - -Edit > Preferences > General > When Firefox starts: -to -"Show my windows and tabs from last time" - -So that your tabs get restored a'la Konqueror after you stop and restart the activity. -You can do this for any number of activities: just make sure you associate a different -profile name to each activity. Then stopping any activity will close the associated -Firefox Profile's window while leaving the others alone, and restarting any activity -will reopen the associated Firefox profile's window. - -Sometimes when a Firefox profile window is restored, you'll get the "this is embarassing" -crash recovery tab. Just hit "restore session" and get your tabs back. Unfortunately -there doesn't seem to be a way to prevent this from happening once in awhile. - -See http://blip.tv/moofang/associating-firefox-profiles-with-kde-activities-6306721 for -a brief screencast demonstrating basic usage. - -=========== -FAQ -=========== - - -Q: My new Firefox profile instances have no plugins! This sucks! -A: If your new profile Firefox instance doesn't have any plugins, it may be because, -as in my system (OpenSUSE), somehow only the default instance looks in /usr/lib64/browser-plugins -for plugins. You can simply symlink all the plugins in that folder into ~/.mozilla/plugins, -for example: - -> ln -s /usr/lib64/browser-plugins/libflashplayer.so ~/.mozilla/plugins/libflashplayer.so - -And that should give your other profiles the plugin too. - - - -Q: Can't we do something about the initial little blank window? It's ugly and annoying. -A: To hide the initial blank window, we can do a little bit of Kwin window-rule magic: -Right-click the title-bar of the window, select More Actions > Special Application Settings. -In the window rules settings that pop up, set: - -- Size & Position > Minimized to Apply Initially, Yes -- Arrangement & Access > Keep below to Apply initially, Yes -- Arrangement & Access > Skip Taskbar to Apply initially, Yes -- Arrangement & Access > Skip Pager to Apply initially, Yes -- Arrangement & Access > Skip Switcher to Apply initially, Yes - -Basically this will keep it minimized while excluding it from the taskbar and from all -Kwin window switching capabilities, effectively rendering it phantom. - -=========== -License -=========== -GPL v3 -Copyright 2012 Yuen Hoe (Jason moofang) \ No newline at end of file diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..15d0de5 --- /dev/null +++ b/README.MD @@ -0,0 +1,91 @@ +## Description + +ActivityFox is a small KDE program that acts as a workaround to allow Firefox, Google Chrome and Chromium-based browsers to +play nice with KDE Plasma Activities. + +ActivityFox lets you use separate browser instances for each activity, keeping tabs related to that activity. ActivityFox starts the browser with a profile linked to your current activity, closes the browser when you close the activity and automatically restores it when you open it again. + +## Usage + +When run without specifying command line arguments, ActivityFox starts Firefox with a profile named as your current activity id, creating it in a default profile directory if it doesn't exist. + +To launch Google Chrome or Chromium with a profile linked to your current activity, use the following commands: + +```bash +activityfox --google-chrome +activityfox --chromium +``` + +If your browser is located in a location different from the default one, you should specify the location of a binary file: + +```bash +activityfox --google-chrome --bin-path /usr/lib/firefox/firefox +activityfox --google-chrome --bin-path /opt/google/chrome/google-chrome +activityfox --chromium --bin-path /usr/lib/chromium-browser/chromium-browser +``` +In case your profiles are located in a different directory: + +```bash +activityfox --google-chrome --profile-dir /home//.mozilla/firefox +activityfox --google-chrome --profile-dir /home//.config/google-chrome +activityfox --chromium --profile-dir /home//.config/chromium +``` + +If you want to launch your browser with a specific profile, use: + +```bash +activityfox profile_name +``` +To make ActivityFox create profiles for new activities based on a template profile, first create it: + +```bash +activityfox template_profile_name +``` + +Set it up by making your browser show tabs from the last time, then use this command to automatically create new profiles based on your template: + +```bash +activityfox --template-profile-name template_profile_name +``` + +You may create a desktop file with this command and place it on your desktop or in your launcher menu. This way, in every new activity, you will be able to open a browser instance linked to that activity with an automatically created profile based on your template. + +## Hide ActivityFox window + +Creating a visible window is required to make the session manager call our application on session saving, but it's possible to hide it with KWin rules. + +Right-click the title-bar of the window, select More Actions > Special Application Settings. +In the window rules settings that pop up, set: + +- Size & Position > Minimized to Apply Initially, Yes +- Arrangement & Access > Skip Taskbar to Apply initially, Yes +- Arrangement & Access > Skip Pager to Apply initially, Yes +- Arrangement & Access > Skip Switcher to Apply initially, Yes + +## Build + +~~~bash +mkdir build +cd build +cmake -DCMAKE_BUILD_TYPE=Release .. +make +~~~ + +That should give you the 'activityfox' executable in the build folder. + +## Changelog + +### 0.2 +- Add Google Chrome and Chromium support +- ActivityFox automatically opens the browser with a profile linked to the current activity id +- ActivityFox gently closes browsers by sending a signal to close the window, mitigating crashes +- Creation of new profiles based on a template profile +- ActivityFox restores browsers in activites they were initially launched + +### 0.1 +- ActivityFox launches Firefox and terminates it on session saving, restores on session start + +## License + +GPL v3 +Copyright [2012 Yuen Hoe (Jason moofang)](http://yuenhoe.com/blog/2012/08/associating-firefox-profiles-with-kde-activities/), 2018 Leonid Kalichkin (hellishnoob) diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 621875c..0000000 --- a/main.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -//#include -#include - -const char* firefoxbin_path = "/usr/lib64/firefox/firefox-bin"; - -class MainWindow : public KMainWindow -{ - public: - MainWindow(QString profile = "", QWidget *parent=0); - protected: - void saveProperties(KConfigGroup& ); - void readProperties(const KConfigGroup& ); - private: - QString firefoxprofile; - KProcess p; -}; - -MainWindow::MainWindow(QString profile, QWidget *parent) : KMainWindow(parent) -{ - setGeometry(100,100,200,100); - if (profile != "") { - firefoxprofile = profile; - QStringList createProfileCommand; - createProfileCommand << firefoxbin_path << "-CreateProfile" << firefoxprofile << "-no-remote"; - KProcess::execute(createProfileCommand); - p << firefoxbin_path << "-P" << firefoxprofile << "-no-remote"; - p.start(); - } else { - firefoxprofile = ""; - } -} - -void MainWindow::saveProperties(KConfigGroup& conf) { - conf.writeEntry("ffProfile", firefoxprofile); - p.kill(); -} - -void MainWindow::readProperties(const KConfigGroup& conf) { - firefoxprofile = conf.readEntry("ffProfile", QString()); - p << firefoxbin_path << "-P" << firefoxprofile << "-no-remote"; - p.start(); -} - -int main (int argc, char *argv[]) -{ - KAboutData aboutData( "activityfox", 0, - ki18n("ActivityFox"), "0.1", - ki18n("Help Firefox work with KDE activities."), - KAboutData::License_GPL, - ki18n("Copyright (c) 2012 Yuen Hoe (Jason moofang)") ); - KCmdLineArgs::init( argc, argv, &aboutData ); - KCmdLineOptions options; - options.add("+profilename", ki18n("Firefox profile name")); - KCmdLineArgs::addCmdLineOptions(options); - if (argc <= 1) { - std::cout << "Please specify firefox profile name" << std::endl; - return 0; - } - - KApplication app; - if ( app.isSessionRestored() ) { - kRestoreMainWindows< MainWindow >(); - } else { - // create default application as usual - // example: - MainWindow * window = new MainWindow(argv[1]); - window->setObjectName("MyWindow#"); - window->show(); - } - - return app.exec(); -} diff --git a/src/BrowserManager.cpp b/src/BrowserManager.cpp new file mode 100644 index 0000000..39a5c6a --- /dev/null +++ b/src/BrowserManager.cpp @@ -0,0 +1,72 @@ +#include "BrowserManager.h" + +BrowserManager::BrowserManager(const QString &startUpActivityId, const QString &binPath, + const QString &profileDir, const QString &templateProfileName, + const QString &profileName) { + this->startUpActivityId = startUpActivityId; + this->binPath = binPath; + this->profileDir = profileDir; + this->templateProfileName = templateProfileName; + this->profileName = profileName; +} + +void BrowserManager::init() { + if (binPath == nullptr || binPath == "") { + binPath = getDefaultBinPath(); + } + + if (profileDir == nullptr || profileDir == "") { + profileDir = getDefaultProfileDir(); + } + + // if profile name was not specified, use current activity id + if (profileName == nullptr || profileName == "") { + profileName = startUpActivityId; + } + + if (!checkIfProfileExists(profileDir, profileName)) { + qDebug() << "Profile doesn't exist, creating it, name is: " << profileName; + createProfile(binPath, profileDir, profileName, templateProfileName); + } + + qDebug() << "Starting the browser"; + startBrowser(process, binPath, profileDir, profileName); +} + +// expects a non-zero pid, so the process must be running +void BrowserManager::closeBrowser(KProcess &process) { + char pid[21]; + KProcess closeBrowser; + + sprintf(pid, "%d", process.pid()); + QString command = "wmctrl -i -c `wmctrl -lp | grep " + QString(pid) + " | cut -c -10`"; + qDebug() << command; + + // trying to gracefully terminate the browser by closing the window with wmctrl + closeBrowser.setShellCommand(command); + int exitcode = closeBrowser.execute(); + + // if wmctrl failed, terminate the browser normally + if (exitcode != 0) { + qDebug() << "wmctrl failed"; + process.terminate(); + } + + // wait until the browser is closed for at most 30 seconds, terminate if it wasn't closed after that + if (!process.waitForFinished()) { + qDebug() << "The browser hasn't finished in 30 seconds"; + process.terminate(); + } +} + + +void BrowserManager::onSaveProperties() { + qDebug() << "onSaveProperties() was called"; + + if (process.pid() > 0) { + closeBrowser(process); + } else { + // the browser was closed by user or unexpectedly, so we don't want to restore it automatically + exit(0); + } +} diff --git a/src/BrowserManager.h b/src/BrowserManager.h new file mode 100644 index 0000000..d83c36d --- /dev/null +++ b/src/BrowserManager.h @@ -0,0 +1,62 @@ +#ifndef ACTIVITYFOX_BROWSERMANAGER_H +#define ACTIVITYFOX_BROWSERMANAGER_H + +#ifdef NDEBUG +#define QT_NO_DEBUG_OUTPUT +#else + +#include + +#endif + +#include +#include +#include +#include + +class BrowserManager { +public: + BrowserManager(const QString &startUpActivityId, const QString &binPath, const QString &profileDir, + const QString &templateProfileName, const QString &profileName); + + virtual ~BrowserManager() = default; + + void init(); + + void onSaveProperties(); + + enum Browser { + CHROMIUM, GOOGLE_CHROME, FIREFOX + }; + +protected: + + +private: + QString startUpActivityId; + QString profileName; + QString binPath; + QString profileDir; + QString templateProfileName; + KProcess process; + + virtual bool checkIfProfileExists(const QString &profileDir, const QString &profileName) = 0; + + virtual void closeBrowser(KProcess &process); + + virtual void startBrowser(KProcess &process, const QString &binPath, const QString &profileDir, + const QString &profileName) = 0; + + virtual void createProfile(const QString &binPath, const QString &profileDir, const QString &profileName, + const QString &templateProfileName) = 0; + + virtual void copyProfile(const QString &profileDir, const QString &fromName, const QString &toName) = 0; + + virtual QString getDefaultBinPath() = 0; + + virtual QString getDefaultProfileDir() = 0; + +}; + + +#endif //ACTIVITYFOX_BROWSERMANAGER_H diff --git a/src/ChromiumManager.cpp b/src/ChromiumManager.cpp new file mode 100644 index 0000000..3177bbb --- /dev/null +++ b/src/ChromiumManager.cpp @@ -0,0 +1,52 @@ +#include "ChromiumManager.h" + +bool ChromiumManager::checkIfProfileExists(const QString &profileDir, const QString &profileName) { + DIR *dir = opendir((profileDir + '/' + profileName).toAscii()); + return dir != nullptr; +} + +void ChromiumManager::createProfile(const QString &, const QString &profileDir, const QString &profileName, + const QString &templateProfileName) { + // if a template profile name is specified, copy this profile into the new profile directory + if (templateProfileName != nullptr && templateProfileName != "") { + copyProfile(profileDir, templateProfileName, profileName); + } + + // no need to call Chromium to create the profile, Chromium creates it automatically on the first run +} + +void ChromiumManager::startBrowser(KProcess &browserProcess, const QString &binPath, const QString &profileDir, + const QString &profileName) { + browserProcess << binPath << "--user-data-dir=" + profileDir + '/' + profileName; + qDebug() << browserProcess.program(); + browserProcess.start(); +} + +void ChromiumManager::copyProfile(const QString &profileDir, const QString &fromName, const QString &toName) { + QStringList command; + + // make copy of a profile + command << "cp" << "-r" << profileDir + '/' + fromName << profileDir + '/' + toName; + qDebug() << command; + + KProcess::execute(command); +} + +QString ChromiumManager::getDefaultProfileDir() { + static const char *defaultProfileDirTail = ".config/chromium"; + + std::string defaultProfileDir = getenv("HOME"); + return (defaultProfileDir + '/' + defaultProfileDirTail).c_str(); +} + +QString ChromiumManager::getDefaultBinPath() { + static const char *defaultBinPath = "/usr/lib/chromium-browser/chromium-browser"; + + return defaultBinPath; +} + +ChromiumManager::ChromiumManager(const QString &startUpActivityId, const QString &binPath, const QString &profileDir, + const QString &templateProfileName, + const QString &profileName) : BrowserManager(startUpActivityId, binPath, profileDir, + templateProfileName, + profileName) {} \ No newline at end of file diff --git a/src/ChromiumManager.h b/src/ChromiumManager.h new file mode 100644 index 0000000..3a9f875 --- /dev/null +++ b/src/ChromiumManager.h @@ -0,0 +1,31 @@ +#ifndef ACTIVITYFOX_CHROMIUMMANAGER_H +#define ACTIVITYFOX_CHROMIUMMANAGER_H + +#include +#include "BrowserManager.h" + +class ChromiumManager : public BrowserManager { +public: + ChromiumManager(const QString &startUpActivityId, const QString &binPath, const QString &profileDir, + const QString &templateProfileName, + const QString &profileName); + +private: + + bool checkIfProfileExists(const QString &profileDir, const QString &profileName) override; + + void startBrowser(KProcess &browserProcess, const QString &binPath, const QString &profileDir, + const QString &profileName) override; + + void createProfile(const QString &binPath, const QString &profileDir, const QString &profileName, + const QString &templateProfileName) override; + + void copyProfile(const QString &profileDir, const QString &fromName, const QString &toName) override; + + QString getDefaultBinPath() override; + + QString getDefaultProfileDir() override; +}; + + +#endif //ACTIVITYFOX_CHROMIUMMANAGER_H diff --git a/src/FirefoxManager.cpp b/src/FirefoxManager.cpp new file mode 100644 index 0000000..bebe750 --- /dev/null +++ b/src/FirefoxManager.cpp @@ -0,0 +1,65 @@ +#include "FirefoxManager.h" + +bool FirefoxManager::checkIfProfileExists(const QString &profileDir, const QString &profileName) { + KProcess checkIfProfileExists; + + QString command = "cat " + profileDir + '/' + "profiles.ini" + " | grep " + profileName; + qDebug() << command; + + checkIfProfileExists.setShellCommand(command); + return (checkIfProfileExists.execute() == 0); +} + +void FirefoxManager::createProfile(const QString &binPath, const QString &profileDir, const QString &profileName, + const QString &templateProfileName) { + // if a template profile name is specified, copy this profile into the new profile directory + if (templateProfileName != nullptr && templateProfileName != "") { + copyProfile(profileDir, templateProfileName, profileName); + } + + QStringList command; + + // create a profile + command << binPath << "-CreateProfile" << profileName + ' ' + profileDir + '/' + profileName + << "-no-remote"; + qDebug() << command; + KProcess::execute(command); +} + +void FirefoxManager::startBrowser(KProcess &browserProcess, const QString &binPath, const QString &, + const QString &profileName) { + qDebug() << "FirefoxManager::startBrowser was called"; + browserProcess << binPath << "-p" << profileName << "-no-remote"; + qDebug() << browserProcess.program(); + browserProcess.start(); +} + +void FirefoxManager::copyProfile(const QString &profileDir, const QString &fromName, const QString &toName) { + QStringList command; + + // make copy of a profile + command << "cp" << "-r" << profileDir + '/' + fromName << profileDir + '/' + toName; + qDebug() << command; + + KProcess::execute(command); +} + +QString FirefoxManager::getDefaultProfileDir() { + static const char *defaultProfileDirTail = ".mozilla/firefox"; + + std::string defaultProfileDir = getenv("HOME"); + return (defaultProfileDir + '/' + defaultProfileDirTail).c_str(); +} + +QString FirefoxManager::getDefaultBinPath() { + static const char *defaultBinPath = "/usr/lib/firefox/firefox"; + + return defaultBinPath; +} + +FirefoxManager::FirefoxManager(const QString &startUpActivityId, const QString &binPath, const QString &profileDir, + const QString &templateProfileName, + const QString &profileName) : BrowserManager(startUpActivityId, binPath, + profileDir, + templateProfileName, + profileName) {} \ No newline at end of file diff --git a/src/FirefoxManager.h b/src/FirefoxManager.h new file mode 100644 index 0000000..055e372 --- /dev/null +++ b/src/FirefoxManager.h @@ -0,0 +1,30 @@ +#ifndef ACTIVITYFOX_FIREFOXMANAGER_H +#define ACTIVITYFOX_FIREFOXMANAGER_H + +#include "BrowserManager.h" + +class FirefoxManager : public BrowserManager { +public: + FirefoxManager(const QString &startUpActivityId, const QString &binPath, const QString &profileDir, + const QString &templateProfileName, + const QString &profileName); + +private: + + bool checkIfProfileExists(const QString &profileDir, const QString &profileName) override; + + void startBrowser(KProcess &browserProcess, const QString &binPath, const QString &profileDir, + const QString &profileName) override; + + void createProfile(const QString &binPath, const QString &profileDir, const QString &profileName, + const QString &templateProfileName) override; + + void copyProfile(const QString &profileDir, const QString &fromName, const QString &toName) override; + + QString getDefaultBinPath() override; + + QString getDefaultProfileDir() override; +}; + + +#endif //ACTIVITYFOX_FIREFOXMANAGER_H diff --git a/src/GoogleChromeManager.cpp b/src/GoogleChromeManager.cpp new file mode 100644 index 0000000..16f337a --- /dev/null +++ b/src/GoogleChromeManager.cpp @@ -0,0 +1,19 @@ +#include "GoogleChromeManager.h" + +QString GoogleChromeManager::getDefaultProfileDir() { + static const char *defaultProfileDirTail = ".config/google-chrome"; + + std::string defaultProfileDir = getenv("HOME"); + return (defaultProfileDir + '/' + defaultProfileDirTail).c_str(); +} + +QString GoogleChromeManager::getDefaultBinPath() { + static const char *defaultBinPath = "/opt/google/chrome/google-chrome"; + + return defaultBinPath; +} + +GoogleChromeManager::GoogleChromeManager(const QString &startUpActivityId, const QString &binPath, + const QString &profileDir, const QString &templateProfileName, + const QString &profileName) + : ChromiumManager(startUpActivityId, binPath, profileDir, templateProfileName, profileName) {} \ No newline at end of file diff --git a/src/GoogleChromeManager.h b/src/GoogleChromeManager.h new file mode 100644 index 0000000..50fc847 --- /dev/null +++ b/src/GoogleChromeManager.h @@ -0,0 +1,20 @@ +#ifndef ACTIVITYFOX_GOOGLECHROMEMANAGER_H +#define ACTIVITYFOX_GOOGLECHROMEMANAGER_H + +#include "ChromiumManager.h" + +class GoogleChromeManager : public ChromiumManager { +public: + GoogleChromeManager(const QString &startUpActivityId, const QString &binPath, const QString &profileDir, + const QString &templateProfileName, + const QString &profileName); + +private: + + QString getDefaultBinPath() override; + + QString getDefaultProfileDir() override; +}; + + +#endif //ACTIVITYFOX_GOOGLECHROMEMANAGER_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..df4cbc5 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,234 @@ +#ifdef NDEBUG +#define QT_NO_DEBUG_OUTPUT +#else + +#include +#endif + +#include +#include +#include +#include +#include + +#include "BrowserManager.h" +#include "FirefoxManager.h" +#include "ChromiumManager.h" +#include "GoogleChromeManager.h" + +#ifndef NDEBUG + +FILE *fp; + +void myMessageOutput(QtMsgType type, const char *msg) { + switch (type) { + case QtDebugMsg: + fprintf(fp, "Debug (%d): %s\n", getpid(), msg); + fflush(fp); + break; + case QtWarningMsg: + fprintf(fp, "Warning (%d): %s\n", getpid(), msg); + fflush(fp); + break; + case QtCriticalMsg: + fprintf(fp, "Critical (%d): %s\n", getpid(), msg); + fflush(fp); + break; + case QtFatalMsg: + fprintf(fp, "Fatal (%d): %s\n", getpid(), msg); + fflush(fp); + abort(); + } +} + +#endif + +QString getCurrentActivityId() { + // get current activity id with QDBus, also possible to get by executing: + // qdbus org.kde.ActivityManager /ActivityManager/Activities org.kde.ActivityManager.Activities.CurrentActivity + QDBusInterface interface("org.kde.ActivityManager", + "/ActivityManager/Activities", + "org.kde.ActivityManager.Activities", + QDBusConnection::sessionBus()); + + QDBusMessage result = interface.call("CurrentActivity"); + return result.arguments().at(0).value(); +} + +class MainWindow : public KMainWindow { +public: + MainWindow(QString &startUpActivityId, unsigned browser, const QString &binPath, + const QString &profileDir, const QString &templateProfileName, + const QString &profileName) : KMainWindow() { + this->startUpActivityId = startUpActivityId; + this->browser = browser; + this->binPath = binPath; + this->profileDir = profileDir; + this->templateProfileName = templateProfileName; + this->profileName = profileName; + + if (browser == BrowserManager::CHROMIUM) { + bm = new ChromiumManager(startUpActivityId, binPath, profileDir, templateProfileName, profileName); + } else if (browser == BrowserManager::GOOGLE_CHROME) { + bm = new GoogleChromeManager(startUpActivityId, binPath, profileDir, templateProfileName, profileName); + } else { + bm = new FirefoxManager(startUpActivityId, binPath, profileDir, templateProfileName, profileName); + } + + bm->init(); + } + + MainWindow() { + bm = nullptr; + browser = BrowserManager::FIREFOX; + } + +protected: + + // that's a bit of a hack: listen to X11 PropertyNotify (28) event to see if activity was changed + // when it's changed to our start up activity, we start the browser, so it is shown in the right activity + bool x11Event(XEvent *event) override { + if (initWhenVisible && *(int *) event == 28 && startUpActivityId == getCurrentActivityId()) { + initWhenVisible = false; + bm->init(); + } + + return QWidget::x11Event(event); + } + + void readProperties(const KConfigGroup &conf) override { + startUpActivityId = conf.readEntry("startUpActivityId"); + browser = conf.readEntry("browser", unsigned()); + binPath = conf.readEntry("binPath"); + profileDir = conf.readEntry("profileDir"); + profileName = conf.readEntry("profileName"); + templateProfileName = conf.readEntry("templateProfileName"); + + qDebug() << browser << startUpActivityId << binPath << profileDir << templateProfileName << profileName; + + if (browser == BrowserManager::CHROMIUM) { + bm = new ChromiumManager(startUpActivityId, binPath, profileDir, templateProfileName, profileName); + } else if (browser == BrowserManager::GOOGLE_CHROME) { + bm = new GoogleChromeManager(startUpActivityId, binPath, profileDir, templateProfileName, profileName); + } else { + bm = new FirefoxManager(startUpActivityId, binPath, profileDir, templateProfileName, profileName); + } + + // if current activity is not the activity that the browser was started in, we will wait until it is + if (startUpActivityId == getCurrentActivityId()) { + bm->init(); + } else { + initWhenVisible = true; + } + } + + void saveProperties(KConfigGroup &conf) override { + conf.writeEntry("startUpActivityId", startUpActivityId); + conf.writeEntry("browser", browser); + conf.writeEntry("binPath", binPath); + conf.writeEntry("profileDir", profileDir); + conf.writeEntry("templateProfileName", templateProfileName); + conf.writeEntry("profileName", profileName); + + qDebug() << browser << startUpActivityId << binPath << profileDir << templateProfileName << profileName; + + static bool firstCall = true; + if (firstCall) { + firstCall = false; + + // we don't need to close the browser if we haven't yet opened it + if (!initWhenVisible) { + bm->onSaveProperties(); + } + } + qDebug() << "returning from saveProperties"; + } + +private: + BrowserManager *bm; + unsigned browser; + QString binPath; + QString profileDir; + QString templateProfileName; + QString profileName; + QString startUpActivityId; + bool initWhenVisible = false; +}; + +KCmdLineArgs *parseCommandLineArguments(int argc, char **argv) { + static KAboutData aboutData("activityfox", nullptr, + ki18n("ActivityFox"), "0.2", + ki18n("Help Firefox and Chromium-based browsers work with KDE activities."), + KAboutData::License_GPL, + ki18n("Copyright (c) 2012 Yuen Hoe (Jason moofang), 2018 Leonid Kalichkin (hellishnoob)")); + + KCmdLineArgs::init(argc, argv, &aboutData); + + static KCmdLineOptions options; + options.add("c"); + options.add("chromium", ki18n("Use proxy for Chromium-based browsers")); + options.add("g"); + options.add("google-chrome", ki18n("Use proxy for Google Chrome")); + options.add("b"); + options.add("bin-path ", ki18n("Path to the browser executable"), nullptr); + options.add("p"); + options.add("profile-dir ", ki18n("Path to the browser profile directory"), nullptr); + options.add("t"); + options.add("template-profile-name ", ki18n("Profile name of a template to be used for profile creation")); + options.add("+[profile-name]", ki18n("Profile name")); + KCmdLineArgs::addCmdLineOptions(options); + + return KCmdLineArgs::parsedArgs(); +} + +int main(int argc, char *argv[]) { +#ifndef NDEBUG + fp = fopen("activityfox.log", "a"); + qInstallMsgHandler(myMessageOutput); +#endif + + KCmdLineArgs *args = parseCommandLineArguments(argc, argv); + + QString binPath = args->getOption("bin-path"); + QString profileDir = args->getOption("profile-dir"); + QString templateProfileName = args->getOption("template-profile-name"); + QString profileName = nullptr; + + bool useChromium = args->isSet("chromium"); + bool useGoogleChrome = args->isSet("google-chrome"); + + if (args->count() > 0) { + profileName = args->arg(0); + } + + BrowserManager::Browser browser; + + if (useChromium) { + browser = BrowserManager::CHROMIUM; + } else if (useGoogleChrome) { + browser = BrowserManager::GOOGLE_CHROME; + } else { + browser = BrowserManager::FIREFOX; + } + + KApplication app; + + QString startUpActivityId = getCurrentActivityId(); + + if (app.isSessionRestored()) { + kRestoreMainWindows(); + } else { + // having a visible window is necessary for session management + MainWindow *window = new MainWindow(startUpActivityId, browser, binPath, profileDir, templateProfileName, + profileName); + window->show(); + } + + int result = app.exec(); + +#ifndef NDEBUG + fclose(fp); +#endif + + return result; +} \ No newline at end of file