#include "mainwindow.h"
extern QString defaultUserAgentStr;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent),
notificationsTitleRegExp("^\\([1-9]\\d*\\).*"),
trayIconRead(":/icons/app/whatsapp.svg"),
trayIconUnread(":/icons/app/whatsapp-message.svg")
{
setWindowTitle(QApplication::applicationName());
setWindowIcon(QIcon(":/icons/app/icon-256.png"));
setMinimumWidth(800);
setMinimumHeight(600);
readSettings();
createActions();
createStatusBar();
createTrayIcon();
createWebEngine();
init_settingWidget();
lightPalette = qApp->palette();
updateWindowTheme();
// quit application if the download manager window is the only remaining window
m_downloadManagerWidget.setAttribute(Qt::WA_QuitOnClose, false);
}
void MainWindow::updatePageTheme()
{
QString webPageTheme = "web"; //implies light
QString windowTheme = settings.value("windowTheme","light").toString();
if(windowTheme == "dark"){
webPageTheme = "web dark";
}
if(webEngine && webEngine->page()){
webEngine->page()->runJavaScript(
"document.querySelector('body').className='"+webPageTheme+"';",
[](const QVariant &result){
qDebug() << "Value is: " << result.toString() << endl;
}
);
}
}
void MainWindow::updateWindowTheme()
{
if(settings.value("windowTheme","light").toString() == "dark")
{
//TODO make dark palette match whatsapp dark theme
qApp->setStyle(QStyleFactory::create("fusion"));
QPalette palette;
palette.setColor(QPalette::Window,QColor("#131C21")); //whatsapp dark color
palette.setColor(QPalette::WindowText,Qt::white);
palette.setColor(QPalette::Disabled,QPalette::WindowText,QColor(127,127,127));
//palette.setColor(QPalette::Base,QColor(42,42,42));
palette.setColor(QPalette::Base,QColor(84,84,84));
palette.setColor(QPalette::AlternateBase,QColor(66,66,66));
palette.setColor(QPalette::ToolTipBase,Qt::white);
palette.setColor(QPalette::ToolTipText,QColor(53,53,53));
palette.setColor(QPalette::Text,Qt::white);
palette.setColor(QPalette::Disabled,QPalette::Text,QColor(127,127,127));
palette.setColor(QPalette::Dark,QColor(35,35,35));
palette.setColor(QPalette::Shadow,QColor(20,20,20));
palette.setColor(QPalette::Button,QColor(53,53,53));
palette.setColor(QPalette::ButtonText,Qt::white);
palette.setColor(QPalette::Disabled,QPalette::ButtonText,QColor(127,127,127));
palette.setColor(QPalette::BrightText,Qt::red);
palette.setColor(QPalette::Link,QColor("skyblue"));
palette.setColor(QPalette::Highlight,QColor(49,106,150));
palette.setColor(QPalette::Disabled,QPalette::Highlight,QColor(80,80,80));
palette.setColor(QPalette::HighlightedText,Qt::white);
palette.setColor(QPalette::Disabled,QPalette::HighlightedText,QColor(127,127,127));
qApp->setPalette(palette);
this->webEngine->setStyleSheet("QWebEngineView{background:#131C21;}"); //whatsapp dark color
}
else{
qApp->setPalette(lightPalette);
this->update();
}
}
void MainWindow::handleCookieAdded(const QNetworkCookie &cookie)
{
qDebug() << cookie.toRawForm() << "\n\n\n";
}
void MainWindow::readSettings()
{
restoreGeometry(settings.value("geometry").toByteArray());
restoreState(settings.value("windowState").toByteArray());
}
void MainWindow::init_settingWidget()
{
if(settingsWidget == nullptr)
{
settingsWidget = new SettingsWidget(this,webEngine->page()->profile()->cachePath()
,webEngine->page()->profile()->persistentStoragePath());
settingsWidget->setWindowTitle(QApplication::applicationName()+" | Settings");
settingsWidget->setWindowFlags(Qt::Dialog);
connect(settingsWidget,SIGNAL(updateWindowTheme()),this,SLOT(updateWindowTheme()));
connect(settingsWidget,SIGNAL(updatePageTheme()),this,SLOT(updatePageTheme()));
connect(settingsWidget,&SettingsWidget::muteToggled,[=](const bool checked)
{
this->toggleMute(checked);
});
connect(settingsWidget,&SettingsWidget::userAgentChanged,[=](QString userAgentStr)
{
if(webEngine->page()->profile()->httpUserAgent() != userAgentStr)
{
settings.setValue("useragent",userAgentStr);
this->updateSettingsUserAgentWidget();
this->askToReloadPage();
}
});
connect(settingsWidget,&SettingsWidget::autoPlayMediaToggled,[=](bool checked)
{ QWebEngineProfile *profile = QWebEngineProfile::defaultProfile();
auto* webSettings = profile->settings();
webSettings->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture,checked);
this->webEngine->page()->profile()->settings()->setAttribute(
QWebEngineSettings::PlaybackRequiresUserGesture,
checked);
});
}
}
void MainWindow::showSettings()
{
if(webEngine == nullptr){
QMessageBox::critical(this,QApplication::applicationName()+"| Error",
"Unable to initialize settings module.\nIs webengine initialized?");
return;
}
if(!settingsWidget->isVisible())
{
this->updateSettingsUserAgentWidget();
settingsWidget->refresh();
settingsWidget->showNormal();
}
}
void MainWindow::updateSettingsUserAgentWidget()
{
settingsWidget->updateDefaultUAButton(this->webEngine->page()->profile()->httpUserAgent());
}
void MainWindow::askToReloadPage()
{
QMessageBox msgBox;
msgBox.setWindowTitle(QApplication::applicationName()+" | Action required");
msgBox.setInformativeText("Page needs to be reloaded to continue.");
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.exec();
this->doAppReload();
}
void MainWindow::showAbout()
{
QDialog *aboutDialog = new QDialog(this,Qt::Dialog);
aboutDialog->setWindowModality(Qt::WindowModal);
QVBoxLayout *layout = new QVBoxLayout;
QLabel *message = new QLabel(aboutDialog);
layout->addWidget(message);
connect(message,&QLabel::linkActivated,[=](const QString linkStr){
if(linkStr.contains("about_qt")){
qApp->aboutQt();
}else{
QDesktopServices::openUrl(QUrl(linkStr));
}
});
aboutDialog->setLayout(layout);
aboutDialog->setAttribute(Qt::WA_DeleteOnClose,true);
aboutDialog->show();
QString mes =
"

"
"
"
"Designed and Developed
"
"by Keshav Bhatt <keshavnrj@gmail.com>
"
"Website: https://ktechpit.com
"
"Runtime: Qt Toolkit
"
"Version: "+QApplication::applicationVersion()+"
"
"
"
"More Apps
"
"
";
QGraphicsOpacityEffect *eff = new QGraphicsOpacityEffect(this);
message->setGraphicsEffect(eff);
QPropertyAnimation *a = new QPropertyAnimation(eff,"opacity");
a->setDuration(1000);
a->setStartValue(0);
a->setEndValue(1);
a->setEasingCurve(QEasingCurve::InCurve);
a->start(QPropertyAnimation::DeleteWhenStopped);
message->setText(mes);
message->show();
}
void MainWindow::closeEvent(QCloseEvent *event)
{
settings.setValue("geometry", saveGeometry());
settings.setValue("windowState", saveState());
if(QSystemTrayIcon::isSystemTrayAvailable() && settings.value("closeButtonActionCombo",0).toInt() == 0){
this->hide();
event->ignore();
notify(QApplication::applicationName(),"Application is minimized to system tray.");
return;
}
event->accept();
qApp->quit();
QMainWindow::closeEvent(event);
}
void MainWindow::notify(QString title,QString message)
{
if(settings.value("disableNotificationPopups",false).toBool() == true){
return;
}
auto popup = new NotificationPopup(webEngine);
connect(popup,&NotificationPopup::notification_clicked,[=](){
if(windowState()==Qt::WindowMinimized || windowState()!=Qt::WindowActive){
activateWindow();
raise();
show();
}
});
popup->adjustSize();
popup->present(title,message,QPixmap(":/icons/app/icon-64.png"));
}
void MainWindow::createActions()
{
minimizeAction = new QAction(tr("Mi&nimize to tray"), this);
connect(minimizeAction, &QAction::triggered, this, &QWidget::hide);
addAction(minimizeAction);
restoreAction = new QAction(tr("&Restore"), this);
connect(restoreAction, &QAction::triggered, this, &QWidget::show);
addAction(restoreAction);
reloadAction = new QAction(tr("Re&load"), this);
reloadAction->setShortcut(Qt::Key_F5);
connect(reloadAction, &QAction::triggered, this, &MainWindow::doReload);
addAction(reloadAction);
settingsAction = new QAction(tr("Settings"), this);
connect(settingsAction, &QAction::triggered, this, &MainWindow::showSettings);
aboutAction = new QAction(tr("About"), this);
connect(aboutAction, &QAction::triggered, this, &MainWindow::showAbout);
quitAction = new QAction(tr("&Quit"), this);
quitAction->setShortcut(QKeySequence(Qt::Modifier::CTRL + Qt::Key_Q));
connect(quitAction, &QAction::triggered, qApp, &QCoreApplication::quit);
addAction(quitAction);
}
void MainWindow::createStatusBar()
{
QStatusBar *statusBar = new QStatusBar(this);
setStatusBar(statusBar);
statusBar->hide();
this->statusBar = statusBar;
}
void MainWindow::createTrayIcon()
{
trayIconMenu = new QMenu(this);
trayIconMenu->setObjectName("trayIconMenu");
trayIconMenu->addAction(minimizeAction);
trayIconMenu->addAction(restoreAction);
trayIconMenu->addSeparator();
trayIconMenu->addAction(reloadAction);
trayIconMenu->addAction(settingsAction);
trayIconMenu->addAction(aboutAction);
trayIconMenu->addSeparator();
trayIconMenu->addAction(quitAction);
trayIcon = new QSystemTrayIcon(trayIconRead, this);
trayIcon->setContextMenu(trayIconMenu);
connect(trayIconMenu,SIGNAL(aboutToShow()),this,SLOT(check_window_state()));
trayIcon->show();
connect(trayIcon, &QSystemTrayIcon::messageClicked,
this, &MainWindow::messageClicked);
connect(trayIcon, &QSystemTrayIcon::activated,
this, &MainWindow::iconActivated);
}
void MainWindow::init_lock()
{
// if(lockWidget==nullptr){
// lockWidget = new Lock(this);
// lockWidget->setObjectName("lockWidget");
// }
// lockWidget->setWindowFlags(Qt::Widget);
// lockWidget->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
// lockWidget->setGeometry(this->rect());
// connect(lockWidget,&Lock::passwordNotSet,[=](){
// settings.setValue("lockscreen",false);
// ui->applock_checkbox->setChecked(false);
// });
// connect(lockWidget,&Lock::unLocked,[=]()
// {
// //unlock event
// });
// connect(lockWidget,&Lock::passwordSet,[=](){
// //enable disable lock screen
// if(settings.value("asdfg").isValid()){
// ui->current_password->setText("Current Password: "+QByteArray::fromBase64(settings.value("asdfg").toString().toUtf8())+"");
// }else{
// ui->current_password->setText("Current Password: Require setup");
// }
// ui->applock_checkbox->setChecked(settings.value("lockscreen",false).toBool());
// });
// lockWidget->show();
// if(settings.value("asdfg").isValid() && settings.value("lockscreen").toBool()==true){
// lockWidget->lock_app();
// }
}
//check window state and set tray menus
void MainWindow::check_window_state()
{
QObject *tray_icon_menu = this->findChild("trayIconMenu");
if(tray_icon_menu != nullptr){
if(this->isVisible()){
((QMenu*)(tray_icon_menu))->actions().at(0)->setDisabled(false);
((QMenu*)(tray_icon_menu))->actions().at(1)->setDisabled(true);
}else{
((QMenu*)(tray_icon_menu))->actions().at(0)->setDisabled(true);
((QMenu*)(tray_icon_menu))->actions().at(1)->setDisabled(false);
}
}
}
void MainWindow::init_globalWebProfile()
{
QWebEngineProfile *profile = QWebEngineProfile::defaultProfile();
auto* webSettings = profile->settings();
webSettings->setAttribute(QWebEngineSettings::AutoLoadImages, true);
webSettings->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
webSettings->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true);
webSettings->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
webSettings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
webSettings->setAttribute(QWebEngineSettings::XSSAuditingEnabled, true);
webSettings->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true);
webSettings->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, false);
webSettings->setAttribute(QWebEngineSettings::DnsPrefetchEnabled,true);
webSettings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled ,true);
webSettings->setAttribute(QWebEngineSettings::LinksIncludedInFocusChain, false);
webSettings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false);
//webSettings->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, true);
// QObject::connect(
// QWebEngineProfile::defaultProfile(), &QWebEngineProfile::downloadRequested,
// &m_downloadManagerWidget, &DownloadManagerWidget::downloadRequested);
QWebEngineSettings::defaultSettings()->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture,
true);
}
void MainWindow::createWebEngine()
{
init_globalWebProfile();
QSizePolicy widgetSize;
widgetSize.setHorizontalPolicy(QSizePolicy::Expanding);
widgetSize.setVerticalPolicy(QSizePolicy::Expanding);
widgetSize.setHorizontalStretch(1);
widgetSize.setVerticalStretch(1);
QWebEngineView *webEngine = new QWebEngineView(this);
setCentralWidget(webEngine);
webEngine->setSizePolicy(widgetSize);
webEngine->show();
this->webEngine = webEngine;
connect(webEngine, &QWebEngineView::titleChanged,
this, &MainWindow::handleWebViewTitleChanged);
connect(webEngine, &QWebEngineView::loadStarted,
this, &MainWindow::handleLoadStarted);
connect(webEngine, &QWebEngineView::loadProgress,
this, &MainWindow::handleLoadProgress);
connect(webEngine, &QWebEngineView::loadFinished,
this, &MainWindow::handleLoadFinished);
connect(webEngine, &QWebEngineView::renderProcessTerminated,
[this](QWebEnginePage::RenderProcessTerminationStatus termStatus, int statusCode) {
QString status;
switch (termStatus) {
case QWebEnginePage::NormalTerminationStatus:
status = tr("Render process normal exit");
break;
case QWebEnginePage::AbnormalTerminationStatus:
status = tr("Render process abnormal exit");
break;
case QWebEnginePage::CrashedTerminationStatus:
status = tr("Render process crashed");
break;
case QWebEnginePage::KilledTerminationStatus:
status = tr("Render process killed");
break;
}
QMessageBox::StandardButton btn = QMessageBox::question(window(), status,
tr("Render process exited with code: %1\n"
"Do you want to reload the page ?").arg(statusCode));
if (btn == QMessageBox::Yes)
QTimer::singleShot(0, [this] { this->webEngine->reload(); });
});
QWebEngineCookieStore *browser_cookie_store = webEngine->page()->profile()->cookieStore();
connect( browser_cookie_store, &QWebEngineCookieStore::cookieAdded, this, &MainWindow::handleCookieAdded );
createWebPage(false);
}
void MainWindow::createWebPage(bool offTheRecord)
{
if (offTheRecord && !m_otrProfile) {
m_otrProfile.reset(new QWebEngineProfile);
}
auto profile = offTheRecord ? m_otrProfile.get() : QWebEngineProfile::defaultProfile();
profile->setHttpUserAgent(settings.value("useragent",defaultUserAgentStr).toString());
auto popup = new NotificationPopup(webEngine);
connect(popup,&NotificationPopup::notification_clicked,[=](){
if(windowState()==Qt::WindowMinimized || windowState()!=Qt::WindowActive){
activateWindow();
raise();
show();
}
});
profile->setNotificationPresenter([=] (std::unique_ptr notification)
{
if(settings.value("disableNotificationPopups",false).toBool() == true){
return;
}
popup->present(notification);
});
QWebEnginePage *page = new WebEnginePage(profile,webEngine);
if(settings.value("windowTheme","light").toString() == "dark"){
page->setBackgroundColor(QColor("#131C21")); //whatsapp bg color
}
webEngine->setPage(page);
//page should be set parent of profile to prevent
//Release of profile requested but WebEnginePage still not deleted. Expect troubles !
profile->setParent(page);
//RequestInterceptor *interceptor = new RequestInterceptor(profile);
//profile->setUrlRequestInterceptor(interceptor);
page->setUrl(QUrl("https://web.whatsapp.com/"));
connect(profile, &QWebEngineProfile::downloadRequested,
&m_downloadManagerWidget, &DownloadManagerWidget::downloadRequested);
connect(webEngine->page(), SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)),
this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest)));
}
void MainWindow::fullScreenRequested(QWebEngineFullScreenRequest request)
{
if (request.toggleOn())
{
webEngine->showFullScreen();
this->showFullScreen();
request.accept();
} else {
webEngine->showNormal();
this->showNormal();
request.accept();
}
}
void MainWindow::handleWebViewTitleChanged(QString title)
{
setWindowTitle(title);
if (notificationsTitleRegExp.exactMatch(title))
{
trayIcon->setIcon(trayIconUnread);
setWindowIcon(trayIconUnread);
}
else
{
trayIcon->setIcon(trayIconRead);
setWindowIcon(trayIconRead);
}
}
void MainWindow::handleLoadStarted()
{
statusBar->show();
}
void MainWindow::handleLoadFinished(bool loaded)
{
if(loaded){
updatePageTheme();
}
}
void MainWindow::handleLoadProgress(int progress)
{
if (progress >= 100)
{
statusBar->hide();
}
else
{
statusBar->showMessage("Loading "+QString::number(progress)+"%");
}
}
//unused direct method to download file without having entry in download manager
void MainWindow::handleDownloadRequested(QWebEngineDownloadItem *download)
{
QFileDialog dialog;
dialog.setParent(this);
dialog.setAcceptMode(QFileDialog::AcceptMode::AcceptSave);
dialog.setFileMode(QFileDialog::FileMode::AnyFile);
QString suggestedFileName = QUrl(download->path()).fileName();
dialog.selectFile(suggestedFileName);
if (dialog.exec() && dialog.selectedFiles().size() > 0)
{
download->setPath(dialog.selectedFiles().at(0));
download->accept();
}
}
void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason)
{
Q_UNUSED(reason);
if (isVisible()) {
hide();
} else {
showNormal();
}
}
void MainWindow::messageClicked()
{
if (isVisible()) {
hide();
} else {
showNormal();
}
}
void MainWindow::doAppReload()
{
if(this->webEngine->page()){
this->webEngine->page()->disconnect();
}
createWebPage(false);
}
void MainWindow::doReload()
{
this->webEngine->triggerPageAction(QWebEnginePage::ReloadAndBypassCache, false);
}
void MainWindow::toggleMute(const bool checked)
{
this->webEngine->page()->setAudioMuted(checked);
}
// get value of page theme when page is loaded
QString MainWindow::getPageTheme()
{
static QString theme = "web"; //implies light
if(webEngine && webEngine->page())
{
webEngine->page()->runJavaScript(
"document.querySelector('body').className;",
[](const QVariant &result){
theme = result.toString();
}
);
}
return theme;
}