add multiple windows support with shared session, add shortcuts

This commit is contained in:
2026-10-15 23:57:13 +02:00
parent b23e838957
commit b6020a4354
7 changed files with 194 additions and 41 deletions
+61 -19
View File
@@ -4,6 +4,7 @@
#include "commandline.h"
#include "completionview.h"
#include "hints.h"
#include "history.h"
#include "statusbar.h"
#include "urlutil.h"
@@ -28,24 +29,25 @@ constexpr int kScrollStep = 60;
constexpr double kZoomStep = 0.1;
constexpr int kCompletionLimit = 100;
// ":open <query>" or ":o <query>"; captures the prefix and the query.
// ":open [-w|-t] <query>" or ":o ..."; captures the prefix, flag included,
// and the query.
const QRegularExpression &openCommand()
{
static const QRegularExpression re(QStringLiteral("^(:(?:open|o) +)(.*)$"));
static const QRegularExpression re(QStringLiteral("^(:(?:open|o) +(?:-[wt] +)?)(.*)$"));
return re;
}
} // namespace
BrowserWindow::BrowserWindow(QWebEngineProfile *profile, AdBlocker *adBlocker,
const QUrl &startUrl, QWidget *parent)
BrowserWindow::BrowserWindow(const Session &session, const QUrl &startUrl, QWidget *parent)
: QMainWindow(parent)
, m_adBlocker(adBlocker)
, m_view(new QWebEngineView(profile, this))
, m_session(session)
, m_view(new QWebEngineView(session.profile, this))
, m_status(new StatusBar(this))
, m_commandLine(new CommandLine(this))
, m_completion(new CompletionView(this))
{
setAttribute(Qt::WA_DeleteOnClose);
setupUi();
connectView();
@@ -58,7 +60,7 @@ BrowserWindow::BrowserWindow(QWebEngineProfile *profile, AdBlocker *adBlocker,
});
connect(m_commandLine, &QLineEdit::textEdited, this, &BrowserWindow::updateCompletion);
connect(m_commandLine, &CommandLine::completionCycled, this, &BrowserWindow::cycleCompletion);
connect(m_adBlocker, &AdBlocker::message, m_status, &StatusBar::showMessage);
connect(m_session.adBlocker, &AdBlocker::message, m_status, &StatusBar::showMessage);
qApp->installEventFilter(this);
@@ -106,7 +108,7 @@ void BrowserWindow::connectView()
connect(m_view, &QWebEngineView::titleChanged, this, [this](const QString &title) {
setWindowTitle(title.isEmpty() ? QStringLiteral("att")
: QStringLiteral("%1 - att").arg(title));
m_history.updateTitle(m_view->url(), title);
m_session.history->updateTitle(m_view->url(), title);
});
connect(m_view, &QWebEngineView::loadFinished, this, [this](bool ok) {
m_status->setProgress(100);
@@ -116,7 +118,7 @@ void BrowserWindow::connectView()
}
// Here rather than on urlChanged, which also fires for every redirect
// hop and in-page #anchor jump.
m_history.addVisit(m_view->url(), m_view->title());
m_session.history->addVisit(m_view->url(), m_view->title());
});
}
@@ -246,6 +248,18 @@ bool BrowserWindow::handleNormalModeKey(QKeyEvent *event)
scrollToEnd(false);
return true;
}
if (sequence == QLatin1String("gi")) {
focusInput();
return true;
}
if (sequence == QLatin1String("go") || sequence == QLatin1String("gO")) {
// Edit the current URL, to open it here or in a new window.
m_commandLine->openWith((sequence == QLatin1String("go") ? QStringLiteral(":open ")
: QStringLiteral(":open -w "))
+ m_view->url().toString());
setMode(Mode::Command);
return true;
}
return true; // Swallow the unknown sequence rather than leaking it.
}
@@ -260,11 +274,14 @@ bool BrowserWindow::handleNormalModeKey(QKeyEvent *event)
return true;
}
if (text == QLatin1String("O")) {
// Edit the current URL.
m_commandLine->openWith(QStringLiteral(":open ") + m_view->url().toString());
m_commandLine->openWith(QStringLiteral(":open -w "));
setMode(Mode::Command);
return true;
}
if (text == QLatin1String("d")) {
close();
return true;
}
if (text == QLatin1String(":")) {
m_commandLine->openWith(QStringLiteral(":"));
setMode(Mode::Command);
@@ -355,6 +372,21 @@ void BrowserWindow::startHints()
runHintScript(hints::showScript());
}
void BrowserWindow::focusInput()
{
m_view->page()->runJavaScript(hints::focusInputScript(), QWebEngineScript::ApplicationWorld,
[this](const QVariant &focused) {
// Ignore a result that arrives after the
// user already moved on, e.g. to ":".
if (m_mode != Mode::Normal)
return;
if (focused.toBool())
setMode(Mode::Insert);
else
m_status->showMessage(QStringLiteral("No input field found"), true);
});
}
void BrowserWindow::runHintScript(const QString &script)
{
m_view->page()->runJavaScript(script, QWebEngineScript::ApplicationWorld,
@@ -416,9 +448,15 @@ void BrowserWindow::runCommand(const QString &command)
const QString args = command.section(QLatin1Char(' '), 1).trimmed();
if (name == QLatin1String("open") || name == QLatin1String("o")) {
openUrl(args);
} else if (name == QLatin1String("quit") || name == QLatin1String("q")) {
// qutebrowser's -t (new tab) means the same as -w here: a new window.
const QString flag = args.section(QLatin1Char(' '), 0, 0);
const bool newWindow = flag == QLatin1String("-w") || flag == QLatin1String("-t");
openUrl(newWindow ? args.section(QLatin1Char(' '), 1).trimmed() : args, newWindow);
} else if (name == QLatin1String("close") || name == QLatin1String("tab-close")) {
close();
} else if (name == QLatin1String("quit") || name == QLatin1String("q")) {
// Like qutebrowser: quit the browser, not just this window.
QApplication::closeAllWindows();
} else if (name == QLatin1String("reload")) {
m_view->reload();
} else if (name == QLatin1String("back")) {
@@ -428,7 +466,7 @@ void BrowserWindow::runCommand(const QString &command)
} else if (name == QLatin1String("stop")) {
m_view->stop();
} else if (name == QLatin1String("history-clear")) {
if (m_history.clear()) {
if (m_session.history->clear()) {
// Also forget Chromium's own record, which colours visited links.
m_view->page()->profile()->clearAllVisitedLinks();
m_status->showMessage(QStringLiteral("History cleared"));
@@ -436,10 +474,10 @@ void BrowserWindow::runCommand(const QString &command)
m_status->showMessage(QStringLiteral("Could not clear history"), true);
}
} else if (name == QLatin1String("adblock-update")) {
m_adBlocker->update();
m_session.adBlocker->update();
} else if (name == QLatin1String("adblock-toggle")) {
m_adBlocker->setEnabled(!m_adBlocker->isEnabled());
m_status->showMessage(m_adBlocker->isEnabled() ? QStringLiteral("Adblock enabled")
m_session.adBlocker->setEnabled(!m_session.adBlocker->isEnabled());
m_status->showMessage(m_session.adBlocker->isEnabled() ? QStringLiteral("Adblock enabled")
: QStringLiteral("Adblock disabled"));
} else {
m_status->showMessage(QStringLiteral("Unknown command: %1").arg(name), true);
@@ -454,7 +492,7 @@ void BrowserWindow::updateCompletion()
return;
}
const QStringList words = match.captured(2).split(QLatin1Char(' '), Qt::SkipEmptyParts);
m_completion->setEntries(m_history.search(words, kCompletionLimit));
m_completion->setEntries(m_session.history->search(words, kCompletionLimit));
}
void BrowserWindow::cycleCompletion(int step)
@@ -470,13 +508,17 @@ void BrowserWindow::cycleCompletion(int step)
m_commandLine->setCommandText(prefix + url);
}
void BrowserWindow::openUrl(const QString &input)
void BrowserWindow::openUrl(const QString &input, bool newWindow)
{
const QUrl url = urlutil::fromUserInput(input);
if (!url.isValid() || url.isEmpty()) {
m_status->showMessage(QStringLiteral("Invalid URL: %1").arg(input), true);
return;
}
if (newWindow) {
(new BrowserWindow(m_session, url))->show();
return;
}
m_view->setUrl(url);
focusView();
}