~novaburst-dev/io.kimiko.webapp

c1d8d698381fcb23de701d60f08988015ab5b732 — Nova 4 months ago
Add files to 'main'
A  => .gitignore +79 -0
@@ 1,79 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------

*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
build-dir
cmake-build-debug

# qtcreator generated files
*.pro.user*

# xemacs temporary files
*.flc

# Vim temporary files
.*.swp

# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*

# MinGW generated files
*.Debug
*.Release

# Python byte code
*.pyc

# Binaries
# --------
*.dll
*.exe

# Flatpak
.flatpak-builder/

.idea

A  => LICENSE +20 -0
@@ 1,20 @@
Copyright 2020 Tristan Lins
Copyright 2021 Nova <novaburst@tilde.team>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

A  => README.md +4 -0
@@ 1,4 @@
# io.kimiko.qtwebapp
This is a ThreemaQT based project for use in desktop web applications without the need for Electron / node.js

Licensed under the [MIT License](./COPYING) , just like the original project.

A  => desktop/share/applications/io.kimiko.qtwebapp.desktop +7 -0
@@ 1,7 @@
[Desktop Entry]
Name=%%APPLICATION%%
Icon=%%APPLICATION%%
Exec=%%APPLICATION%%
Description=QtWebEngine-powered desktop client for %%APPLICATION%%
Categories=Network;
Type=Application

A  => desktop/share/icons/hicolor/scalable/apps/io.kimiko.qtwebapp.png +0 -0
A  => qtwebapp.pro +49 -0
@@ 1,49 @@
QT += core widgets webengine webenginewidgets

CONFIG += c++11 console

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Refer to the documentation for the
# deprecated API to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
        src/main.cpp \
        src/mainwindow.cpp \
        src/singletonwebenginepage.cpp

RESOURCES += \
        qtwebapp.qrc

# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =

# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =


DISTFILES += \
    LICENSE \

HEADERS += \
    src/mainwindow.h \
    src/singletonwebenginepage.h

isEmpty(PREFIX):PREFIX = /usr/local

target.path = $$PREFIX/bin/
INSTALLS += target

icons.path = $$PREFIX/share/icons
icons.files += desktop/share/icons/*
INSTALLS += icons

desktop.path = $$PREFIX/share/applications
desktop.files += desktop/share/applications/*
INSTALLS += desktop

A  => qtwebapp.qrc +5 -0
@@ 1,5 @@
<RCC>
    <qresource prefix="/">
        <file>images/bloat.png</file>
    </qresource>
</RCC>

A  => src/main.cpp +26 -0
@@ 1,26 @@
#include <QApplication>
#include <QtWidgets>
#include <QtWebEngine>
#include <QWebEngineProfile>

#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QtWebEngine::initialize();
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QApplication app(argc, argv);
    QCoreApplication::setOrganizationName("");
    QCoreApplication::setApplicationName("");
    QCoreApplication::setApplicationVersion(QT_VERSION_STR);
    app.setWindowIcon(QIcon(""));

    QWebEngineProfile *profile = QWebEngineProfile::defaultProfile();
    profile->setHttpUserAgent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36");

    MainWindow window;
    window.show();

    return app.exec();
}

A  => src/mainwindow.cpp +229 -0
@@ 1,229 @@
#include "mainwindow.h"
#include "singletonwebenginepage.h"
#include <QApplication>
#include <QAction>
#include <QSettings>
#include <QWebEngineProfile>
#include <QFileDialog>
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      notificationsTitleRegExp("^\\([1-9]\\d*\\).*"),
      trayIconRead("/images/.png"),
      trayIconUnread("/images/.png")
{
    setWindowTitle("");
    setWindowIcon(QIcon(""));
    setMinimumWidth(800);
    setMinimumHeight(600);
    readSettings();

    createActions();
    createStatusBar();
    createTrayIcon();
    createWebEngine();

    // Connect signals and slots
    connect(webEngine, &QWebEngineView::titleChanged,
            this, &MainWindow::handleWebViewTitleChanged);
    connect(webEngine, &QWebEngineView::loadStarted,
            this, &MainWindow::handleLoadStarted);
    connect(webEngine, &QWebEngineView::loadProgress,
            this, &MainWindow::handleLoadProgress);
    connect(QWebEngineProfile::defaultProfile(), &QWebEngineProfile::downloadRequested,
            this, &MainWindow::handleDownloadRequested);
}

void MainWindow::readSettings()
{
    QSettings settings("io.kimiko", "");
    restoreGeometry(settings.value("main/geometry").toByteArray());
    restoreState(settings.value("main/windowState").toByteArray());
}

void MainWindow::showAbout()
{
    QString applicationVersion = QCoreApplication::applicationVersion();
    QMessageBox about(this);
    about.setWindowTitle(tr(""));
    about.setIconPixmap(QPixmap(""));
    about.setTextFormat(Qt::TextFormat::RichText);
    about.addButton(QMessageBox::StandardButton::Ok);
    about.exec();
}

void MainWindow::showAboutQt()
{
    QMessageBox::aboutQt(this, tr(""));
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    QSettings settings("io.kimiko", "");
    settings.setValue("main/geometry", saveGeometry());
    settings.setValue("main/windowState", saveState());
    QMainWindow::closeEvent(event);
}

void MainWindow::createActions()
{
    reloadAction = new QAction(tr("Re&load"), this);
    reloadAction->setShortcut(Qt::Key_F5);
    connect(reloadAction, &QAction::triggered, this, &MainWindow::doReload);
    addAction(reloadAction);

    minimizeAction = new QAction(tr("Mi&nimize"), this);
    connect(minimizeAction, &QAction::triggered, this, &QWidget::hide);
    addAction(minimizeAction);

    maximizeAction = new QAction(tr("Ma&ximize"), this);
    connect(maximizeAction, &QAction::triggered, this, &QWidget::showMaximized);
    addAction(maximizeAction);

    restoreAction = new QAction(tr("&Restore"), this);
    connect(restoreAction, &QAction::triggered, this, &QWidget::showNormal);
    addAction(restoreAction);

    aboutAction = new QAction(tr("About"), this);
    connect(aboutAction, &QAction::triggered, this, &MainWindow::showAbout);

    aboutAction = new QAction(tr("About"), this);
    connect(aboutAction, &QAction::triggered, this, &MainWindow::showAbout);

    aboutQtAction = new QAction(tr("About QT"), this);
    connect(aboutQtAction, &QAction::triggered, this, &MainWindow::showAboutQt);

    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;

    QProgressBar *progressBar = new QProgressBar(this->statusBar);
    statusBar->addWidget(progressBar, 1);
    progressBar->setTextVisible(false);
    progressBar->hide();
    this->progressBar = progressBar;
}

void MainWindow::createTrayIcon()
{
    trayIconMenu = new QMenu(this);
    trayIconMenu->addAction(reloadAction);
    trayIconMenu->addSeparator();
    trayIconMenu->addAction(minimizeAction);
    trayIconMenu->addAction(maximizeAction);
    trayIconMenu->addAction(restoreAction);
    trayIconMenu->addSeparator();
    trayIconMenu->addAction(aboutAction);
    trayIconMenu->addAction(aboutQtAction);
    trayIconMenu->addSeparator();
    trayIconMenu->addAction(quitAction);

    trayIcon = new QSystemTrayIcon(trayIconRead, this);
    trayIcon->setContextMenu(trayIconMenu);

    trayIcon->show();

    connect(trayIcon, &QSystemTrayIcon::messageClicked,
            this, &MainWindow::messageClicked);
    connect(trayIcon, &QSystemTrayIcon::activated,
            this, &MainWindow::iconActivated);
}

void MainWindow::createWebEngine()
{
    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;

    QWebEnginePage *page = new SingletonWebEnginePage(webEngine);
    webEngine->setPage(page);
    page->setUrl(QUrl(""));
}

void MainWindow::handleWebViewTitleChanged(QString title) {
    setWindowTitle(title);

    if (notificationsTitleRegExp.exactMatch(title))
    {
        trayIcon->setIcon(trayIconUnread);
    }
    else
    {
        trayIcon->setIcon(trayIconRead);
    }
}

void MainWindow::handleLoadStarted()
{
    statusBar->show();
    progressBar->show();
}

void MainWindow::handleLoadProgress(int progress)
{
    if (progress >= 100)
    {
        progressBar->hide();
        statusBar->hide();
    }
    else
    {
        progressBar->setValue(progress);
    }
}

void MainWindow::handleDownloadRequested(QWebEngineDownloadItem *download)
{
    QFileDialog dialog;
    dialog.setAcceptMode(QFileDialog::AcceptMode::AcceptSave);
    dialog.setFileMode(QFileDialog::FileMode::AnyFile);
    dialog.selectFile(download->suggestedFileName());

    if (dialog.exec() && dialog.selectedFiles().size() > 0)
    {
        download->setDownloadDirectory(dialog.directory().absolutePath());
        download->setDownloadFileName(dialog.selectedFiles().at(0));
        download->accept();
    }
}

void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason)
{
    if (isVisible()) {
        hide();
    } else {
        showNormal();
    }
}

void MainWindow::messageClicked()
{
    if (isVisible()) {
        hide();
    } else {
        showNormal();
    }
}

void MainWindow::doReload()
{
    this->webEngine->triggerPageAction(QWebEnginePage::ReloadAndBypassCache, false);
}

A  => src/mainwindow.h +64 -0
@@ 1,64 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QProgressBar>
#include <QStatusBar>
#include <QWebEngineView>
#include <QIcon>
#include <QMenu>
#include <QSystemTrayIcon>
#include <QRegExp>

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = nullptr);

protected:
    void closeEvent(QCloseEvent *event) override;

private:
    void createActions();
    void createStatusBar();
    void createTrayIcon();
    void createWebEngine();

    QRegExp notificationsTitleRegExp;
    QIcon trayIconRead;
    QIcon trayIconUnread;

    QAction *reloadAction;
    QAction *minimizeAction;
    QAction *maximizeAction;
    QAction *restoreAction;
    QAction *aboutAction;
    QAction *aboutQtAction;
    QAction *quitAction;

    QMenu *trayIconMenu;
    QSystemTrayIcon *trayIcon;

    QWebEngineView *webEngine;
    QStatusBar *statusBar;
    QProgressBar *progressBar;
    void reload();
    void readSettings();

public slots:
    void handleWebViewTitleChanged(QString title);
    void handleLoadStarted();
    void handleLoadProgress(int progress);
    void handleDownloadRequested(QWebEngineDownloadItem *download);
    void iconActivated(QSystemTrayIcon::ActivationReason reason);
    void messageClicked();
    void doReload();
    void showAbout();
    void showAboutQt();

signals:

};

#endif // MAINWINDOW_H

A  => src/singletonwebenginepage.cpp +52 -0
@@ 1,52 @@
#include "singletonwebenginepage.h"

#include <QDesktopServices>

SingletonWebEnginePage::SingletonWebEnginePage(QWidget *parent)
    : QWebEnginePage(parent)
{
    // Connect signals and slots
    connect(this, &QWebEnginePage::featurePermissionRequested,
            this, &SingletonWebEnginePage::handleFeaturePermissionRequested);
    connect(this, &QWebEnginePage::loadFinished,
            this, &SingletonWebEnginePage::handleLoadFinished);
}

bool SingletonWebEnginePage::acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame)
{
    qDebug() << "Navigation request: [" + url.toDisplayString() + "] " + type;

    if (QWebEnginePage::NavigationType::NavigationTypeLinkClicked == type)
    {
        QDesktopServices::openUrl(url);
        return false;
    }

    return QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame);
}

QWebEnginePage *SingletonWebEnginePage::createWindow(QWebEnginePage::WebWindowType type)
{
    return new SingletonWebEnginePage();
}

void SingletonWebEnginePage::handleFeaturePermissionRequested(const QUrl &securityOrigin, QWebEnginePage::Feature feature)
{
    if (QWebEnginePage::Feature::Notifications == feature)
    {
        setFeaturePermission(
                    securityOrigin,
                    feature,
                    QWebEnginePage::PermissionPolicy::PermissionGrantedByUser
        );
    }
}

void SingletonWebEnginePage::handleLoadFinished(bool ok)
{
    setFeaturePermission(
                QUrl("https://web.threema.ch/"),
                QWebEnginePage::Feature::Notifications,
                QWebEnginePage::PermissionPolicy::PermissionGrantedByUser
    );
}

A  => src/singletonwebenginepage.h +23 -0
@@ 1,23 @@
#ifndef SINGLETONWEBENGINEPAGE_H
#define SINGLETONWEBENGINEPAGE_H

#include <QWebEnginePage>



class SingletonWebEnginePage : public QWebEnginePage
{
    Q_OBJECT
public:
    SingletonWebEnginePage(QWidget *parent = nullptr);

protected:
    bool acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame) override;
    QWebEnginePage* createWindow(QWebEnginePage::WebWindowType type) override;

public slots:
    void handleFeaturePermissionRequested(const QUrl &securityOrigin, QWebEnginePage::Feature feature);
    void handleLoadFinished(bool ok);
};

#endif // SINGLETONWEBENGINEPAGE_H