Commit b2fa9ffc authored by ChangZhuo Chen's avatar ChangZhuo Chen

New upstream version 0.5.0

parent 94b704f8
cmake_minimum_required(VERSION 3.5.1)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
project(Zeal VERSION 0.4.0)
project(Zeal VERSION 0.5.0)
# Project information.
if(APPLE)
......
......@@ -2,19 +2,10 @@
[![Changelog](https://img.shields.io/github/release/zealdocs/zeal.svg?style=flat-square)](https://github.com/zealdocs/zeal/releases)
[![Gitter](https://img.shields.io/gitter/room/zealdocs/zeal.svg?style=flat-square)](https://gitter.im/zealdocs/zeal)
[![Telegram Chat](https://img.shields.io/badge/chat-on%20telegram-179cde.svg?style=flat-square)](https://telegram.me/zealdocschat)
[![IRC](https://img.shields.io/badge/chat-on%20irc-blue.svg?style=flat-square)](https://kiwiirc.com/client/irc.freenode.net/#zealdocs)
[![Telegram Channel](https://img.shields.io/badge/follow-on%20telegram-179cde.svg?style=flat-square)](https://telegram.me/zealdocs)
[![Twitter](https://img.shields.io/badge/follow-on%20twitter-1da1f2.svg?style=flat-square)](https://twitter.com/zealdocs)
> **zeal** *noun*
>
> 1. a feeling of strong eagerness (usually in favor of a person or cause)
> 2. excessive fervor to do something or accomplish some end
> 3. prompt willingness
>
> (from WordNet 3.0)
Zeal is a simple offline documentation browser inspired by [Dash](https://kapeli.com/dash).
![Screenshot](https://i.imgur.com/v63u1ZJ.png)
......@@ -36,16 +27,27 @@ Get binary builds for Windows and Linux from the [download page](https://zealdoc
## How to use
After installing Zeal, you need to download docsets. Go to *Tools->Docsets*, select the ones you want, and click the *Download* button.
After installing Zeal, go to *Tools->Docsets*, select the ones you want, and click the *Download* button.
## How to compile
### Requirements
* [Qt](https://www.qt.io/) version 5.5.1 or above. Required modules: Qt WebKit Widgets, Qt X11 Extras (X11 only).
### Required dependencies
* [CMake](https://cmake.org/).
* [Qt](https://www.qt.io/) version 5.5.1 or above. Required module: Qt WebKit Widgets.
* [libarchive](http://libarchive.org/).
* X11 only: `xcb-util-keysyms`.
* [SQLite](https://sqlite.org/).
* X11 platforms only: Qt X11 Extras and `xcb-util-keysyms`.
### Building instructions
```sh
mkdir build && cd build
cmake ..
make
```
To compile Zeal run `qmake` and then `make`. Linux users can install Zeal with `make install` command.
More detailed instructions are available in the [Wiki](https://github.com/zealdocs/zeal/wiki).
## Query & Filter docsets
......@@ -59,21 +61,21 @@ You can also search multiple docsets separating them with a comma:
## Command line
If you prefer, you can start Zeal with a query from command line:
If you prefer, you can start Zeal with a query from the command line:
`zeal python:bomb`
`zeal python:pprint`
## Creating your own docsets
You can use [Dash's instructions for generating docsets](https://kapeli.com/docsets).
Follow the [Dash Docset Generation Guide](https://kapeli.com/docsets).
## Contact and Support
We want your feedback! Here's a list of different ways to contact developers and request help:
* Report bugs and submit feature requests to [GitHub issues](https://github.com/zealdocs/zeal/issues).
* Reach developers and other Zeal users on [Gitter](https://gitter.im/zealdocs/zeal), or IRC channel #zealdocs on [Freenode](https://freenode.net/).
* Ask any questions in our [Google Group](https://groups.google.com/d/forum/zealdocs). You can simply send an email to zealdocs@googlegroups.com.
* For a quick chat with developers and other Zeal users use our IRC channel: #zealdocs on [Freenode](https://freenode.net/). Also available through Kiwi IRC [web interface](https://kiwiirc.com/client/irc.freenode.net/#zealdocs). Please, take into consideration possible time zone differences.
* Finally, for a private communications send us [email](mailto:zeal@zealdocs.org).
* Finally, for private communications email us at zeal@zealdocs.org.
* And do not forget to follow [@zealdocs](https://twitter.com/zealdocs) on Twitter!
## License
......
......@@ -26,8 +26,19 @@ find_library(SQLite_LIBRARY NAMES sqlite3
${PC_SQLITE_LIBRARY_DIRS}
)
# TODO: Set version when library is found not via pkg-config.
set(SQLite_VERSION_STRING ${PC_SQLITE_VERSION})
if(PC_SQLITE_VERSION)
set(SQLite_VERSION_STRING ${PC_SQLITE_VERSION})
elseif(SQLite_INCLUDE_DIR AND EXISTS "${SQLite_INCLUDE_DIR}/sqlite3.h")
# Parse sqlite3.h for the version macro:
# #define SQLITE_VERSION "3.20.1"
set(_SQLite_VERSION_REGEX "^#define[ \t]+SQLITE_VERSION[ \t]+\"([0-9]+)\\.([0-9]+)\\.([0-9]+)\".*$")
file(STRINGS "${SQLite_INCLUDE_DIR}/sqlite3.h" _SQLite_VERSION_MACRO LIMIT_COUNT 1 REGEX "${_SQLite_VERSION_REGEX}")
if(_SQLite_VERSION_MACRO)
string(REGEX REPLACE "${_SQLite_VERSION_REGEX}" "\\1.\\2.\\3" SQLite_VERSION_STRING "${_SQLite_VERSION_MACRO}")
endif()
unset(_SQLite_VERSION_MACRO)
unset(_SQLite_VERSION_REGEX)
endif()
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SQLite
......
......@@ -30,7 +30,7 @@ RCC_DIR = $$BUILD_ROOT/.rcc
UI_DIR = $$BUILD_ROOT/.ui
# Application version
VERSION = 0.4.0
VERSION = 0.5.0
DEFINES += ZEAL_VERSION=\\\"$${VERSION}\\\"
# Portable build
......
......@@ -16,8 +16,8 @@ add_executable(App WIN32
target_link_libraries(App Core Util)
find_package(Qt5 COMPONENTS Concurrent Widgets REQUIRED)
target_link_libraries(App Qt5::Concurrent Qt5::Widgets)
find_package(Qt5 COMPONENTS Widgets REQUIRED)
target_link_libraries(App Qt5::Widgets)
set_target_properties(App PROPERTIES
OUTPUT_NAME ${PROJECT_OUTPUT_NAME}
......
......@@ -2,7 +2,7 @@ include($$ZEAL_COMMON_PRI)
TEMPLATE = app
QT += gui widgets concurrent
QT += gui widgets
SOURCES += \
main.cpp
......
......@@ -22,11 +22,12 @@
****************************************************************************/
#include <core/application.h>
#include <core/localserver.h>
#include <core/applicationsingleton.h>
#include <registry/searchquery.h>
#include <QApplication>
#include <QCommandLineParser>
#include <QDataStream>
#include <QDesktopServices>
#include <QDir>
#include <QIcon>
......@@ -37,6 +38,8 @@
#ifdef Q_OS_WIN32
#include <QSettings>
#include <Windows.h>
#endif
#include <cstdlib>
......@@ -204,57 +207,41 @@ int main(int argc, char *argv[])
}
#endif
// Detect already running instance and optionally send the search query to it.
if (!clParams.force && Core::LocalServer::sendQuery(clParams.query, clParams.preventActivation))
return 0;
QScopedPointer<Core::ApplicationSingleton> appSingleton(new Core::ApplicationSingleton());
if (appSingleton->isSecondary()) {
#ifdef Q_OS_WIN32
::AllowSetForegroundWindow(appSingleton->primaryPid());
#endif
QByteArray ba;
QDataStream out(&ba, QIODevice::WriteOnly);
out << clParams.query << clParams.preventActivation;
// TODO: Check for a possible error.
appSingleton->sendMessage(ba);
return EXIT_SUCCESS;
}
// Set application-wide window icon. All message boxes and other windows will use it by default.
qapp->setWindowIcon(QIcon::fromTheme(QStringLiteral("zeal"),
QIcon(QStringLiteral(":/zeal.ico"))));
QScopedPointer<Core::LocalServer> localServer(new Core::LocalServer());
if (!localServer->start()) {
QScopedPointer<QMessageBox> msgBox(new QMessageBox());
msgBox->setWindowTitle(QStringLiteral("Zeal"));
msgBox->setIcon(QMessageBox::Warning);
msgBox->setText(QObject::tr("Another application instance can be still running, "
"or has crashed.<br>Make sure to start Zeal only once."));
msgBox->addButton(QMessageBox::Help);
msgBox->setDefaultButton(msgBox->addButton(QMessageBox::Retry));
msgBox->addButton(QObject::tr("&Quit"), QMessageBox::DestructiveRole);
switch (msgBox->exec()) {
case QMessageBox::Rejected:
return EXIT_SUCCESS;
case QMessageBox::Help:
QDesktopServices::openUrl(QUrl(contactUrl));
}
msgBox->removeButton(msgBox->button(QMessageBox::Retry));
if (!localServer->start(true)) {
msgBox->setIcon(QMessageBox::Critical);
msgBox->setText(QObject::tr("Zeal is unable to start. Please report the issue "
"providing the details below."));
msgBox->setDetailedText(localServer->errorString());
QDir::setSearchPaths(QStringLiteral("typeIcon"), {QStringLiteral(":/icons/type")});
if (msgBox->exec() == QMessageBox::Help)
QDesktopServices::openUrl(QUrl(contactUrl));
QScopedPointer<Core::Application> app(new Core::Application());
return EXIT_SUCCESS;
}
}
QObject::connect(appSingleton.data(), &Core::ApplicationSingleton::messageReceived,
[&app](const QByteArray &data) {
Registry::SearchQuery query;
bool preventActivation;
QDir::setSearchPaths(QStringLiteral("typeIcon"), {QStringLiteral(":/icons/type")});
QDataStream in(data);
in >> query >> preventActivation;
QScopedPointer<Core::Application> app(new Core::Application());
QObject::connect(localServer.data(), &Core::LocalServer::newQuery,
app.data(), &Core::Application::executeQuery);
app->executeQuery(query, preventActivation);
});
if (!clParams.query.isEmpty()) {
QTimer::singleShot(0, [clParams] {
Core::LocalServer::sendQuery(clParams.query, clParams.preventActivation);
QTimer::singleShot(0, [&app, clParams] {
app->executeQuery(clParams.query, clParams.preventActivation);
});
}
......
This source diff could not be displayed because it is too large. You can view the blob instead.
body {
color: #7a7a7a;
cursor: default;
display: -webkit-flex;
font-size: 11pt;
margin: 10;
text-align: center;
-webkit-flex-direction: column;
-webkit-user-select: none;
}
a {
color: #7a7a7a;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 100;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
#title {
font-size: 4em;
}
.container {
-webkit-flex: none;
}
.dynamic {
-webkit-flex: auto;
}
This diff is collapsed.
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Welcome</title>
<link rel="stylesheet" type="text/css" href="assets/css/bulma.min.css">
<link rel="stylesheet" type="text/css" href="assets/css/welcome.min.css">
<link rel="stylesheet" type="text/css" href="assets/css/font-awesome.min.css">
</head>
<body class="is-unselectable">
<section class="hero is-fullheight">
<div class="hero-head">
<header class="nav">
<div class="container">
<div class="nav-left nav-menu">
<a class="nav-item" href="https://zealdocs.org/">
zealdocs.org
</a>
</div>
<header class="navbar is-transparent" role="navigation">
<div class="navbar-brand">
<a class="navbar-item" href="https://zealdocs.org/">ZealDocs.org</a>
<a class="navbar-item" href="https://gitter.im/zealdocs/zeal">Gitter</a>
<a class="navbar-item" href="https://github.com/zealdocs/zeal">GitHub</a>
</div>
</header>
</div>
<div class="hero-body">
<div class="container has-text-centered">
<h1 class="title">
......@@ -43,12 +44,13 @@
</a>
</p>
<p>
<a class="is-size-7" href="https://www.kapeli.com/dash">Get Dash for OS X or iOS</a>
<a class="is-size-7" href="https://www.kapeli.com/dash">Get Dash for macOS or iOS</a>
</p>
</p></p>
<p></p>
</div>
</div>
</div>
</section>
</body>
</html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Welcome</title>
<link rel="stylesheet" type="text/css" href="assets/css/bulma.min.css">
<link rel="stylesheet" type="text/css" href="assets/css/welcome.min.css">
<link rel="stylesheet" type="text/css" href="assets/css/font-awesome.min.css">
</head>
<body class="is-unselectable">
<section class="hero is-fullheight">
<div class="hero-head">
<header class="nav">
<div class="container">
<div class="nav-left nav-menu">
<a class="nav-item" href="https://zealdocs.org/">
zealdocs.org
</a>
</div>
<header class="navbar is-transparent" role="navigation">
<div class="navbar-brand">
<a class="navbar-item" href="https://zealdocs.org/">ZealDocs.org</a>
<a class="navbar-item" href="https://gitter.im/zealdocs/zeal">Gitter</a>
<a class="navbar-item" href="https://github.com/zealdocs/zeal">GitHub</a>
</div>
</header>
</div>
<div class="hero-body">
<div class="container has-text-centered">
<h1 class="title">
......@@ -34,8 +35,11 @@
<div class="hero-foot">
<div class="container">
<div class="content has-text-centered">
<div id="carbon" class="box">
<script async type="text/javascript" src="https://cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=zealdocsforwindowsorg" id="_carbonads_js"></script>
<div id="carboncontainer">
<div id="carbon" class="box">
<script async type="text/javascript" src="https://cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=zealdocsforwindowsorg"
onerror="document.getElementById('carboncontainer').style.display = 'none';" id="_carbonads_js"></script>
</div>
</div>
<p>
<a class="icon" href="https://github.com/zealdocs/zeal">
......@@ -46,12 +50,13 @@
</a>
</p>
<p>
<a class="is-size-7" href="https://www.kapeli.com/dash">Get Dash for OS X or iOS</a>
<a class="is-size-7" href="https://www.kapeli.com/dash">Get Dash for macOS or iOS</a>
</p>
</p></p>
<p></p>
</div>
</div>
</div>
</section>
</body>
</html>
<RCC>
<qresource prefix="/">
<file>zeal.ico</file>
<file>browser/assets/css/bulma.min.css</file>
<file>browser/assets/css/darkmode.css</file>
<file>browser/assets/css/font-awesome.min.css</file>
<file>browser/assets/css/highlight.css</file>
<file>browser/assets/css/main.css</file>
<file>browser/assets/css/welcome.min.css</file>
<file>browser/assets/fonts/fontawesome-webfont.woff</file>
<file>browser/welcome.html</file>
<file>browser/welcome-noad.html</file>
......
add_library(Core
application.cpp
applicationsingleton.cpp
extractor.cpp
localserver.cpp
filemanager.cpp
networkaccessmanager.cpp
settings.cpp
)
......
......@@ -23,6 +23,7 @@
#include "application.h"
#include "extractor.h"
#include "filemanager.h"
#include "networkaccessmanager.h"
#include "settings.h"
......@@ -61,6 +62,8 @@ Application::Application(QObject *parent) :
m_settings = new Settings(this);
m_networkManager = new NetworkAccessManager(this);
m_fileManager = new FileManager(this);
// Extractor setup
m_extractorThread = new QThread(this);
m_extractor = new Extractor();
......@@ -121,6 +124,11 @@ Registry::DocsetRegistry *Application::docsetRegistry()
return m_docsetRegistry;
}
FileManager *Application::fileManager() const
{
return m_fileManager;
}
void Application::executeQuery(const Registry::SearchQuery &query, bool preventActivation)
{
m_mainWindow->search(query);
......
......@@ -20,8 +20,8 @@
**
****************************************************************************/
#ifndef APPLICATION_H
#define APPLICATION_H
#ifndef ZEAL_CORE_APPLICATION_H
#define ZEAL_CORE_APPLICATION_H
#include <QObject>
......@@ -29,7 +29,6 @@ class QNetworkAccessManager;
class QNetworkReply;
class QThread;
namespace Zeal {
namespace Registry {
......@@ -44,6 +43,7 @@ class MainWindow;
namespace Core {
class Extractor;
class FileManager;
class Settings;
class Application : public QObject
......@@ -59,6 +59,7 @@ public:
Settings *settings() const;
Registry::DocsetRegistry *docsetRegistry();
FileManager *fileManager() const;
public slots:
void executeQuery(const Registry::SearchQuery &query, bool preventActivation);
......@@ -86,6 +87,8 @@ private:
QNetworkAccessManager *m_networkManager = nullptr;
FileManager *m_fileManager = nullptr;
QThread *m_extractorThread = nullptr;
Extractor *m_extractor = nullptr;
......@@ -97,4 +100,4 @@ private:
} // namespace Core
} // namespace Zeal
#endif // APPLICATION_H
#endif // ZEAL_CORE_APPLICATION_H
/****************************************************************************
**
** Copyright (C) 2017 Oleg Shparber
** Contact: https://go.zealdocs.org/l/contact
**
** This file is part of Zeal.
**
** Zeal is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** Zeal is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with Zeal. If not, see <https://www.gnu.org/licenses/>.
**
****************************************************************************/
#include "applicationsingleton.h"
#include <QCoreApplication>
#include <QCryptographicHash>
#include <QDir>
#include <QLocalServer>
#include <QLocalSocket>
#include <QLoggingCategory>
#include <QScopedPointer>
#include <QSharedMemory>
using namespace Zeal::Core;
static Q_LOGGING_CATEGORY(log, "zeal.core.applicationsingleton")
struct SharedData {
qint64 primaryPid;
};
ApplicationSingleton::ApplicationSingleton(QObject *parent)
: QObject(parent)
{
if (QCoreApplication::instance() == nullptr) {
qFatal("QCoreApplication (or derived type) must be created before ApplicationSingleton.");
}
m_id = computeId();
qCDebug(log, "Singleton ID: %s", qPrintable(m_id));
m_sharedMemory = new QSharedMemory(m_id, this);
m_isPrimary = m_sharedMemory->create(sizeof(SharedData));
if (m_isPrimary) {
setupPrimary();
return;
}
#ifdef Q_OS_UNIX
// Verify it's not a segment that survived an application crash.
m_sharedMemory->attach();
m_sharedMemory->detach();
m_isPrimary = m_sharedMemory->create(sizeof(SharedData));
if (m_isPrimary) {
setupPrimary();
return;
}
#endif
if (!m_sharedMemory->attach(QSharedMemory::ReadOnly)) {
qCWarning(log) << "Cannot attach to the shared memory segment:"
<< m_sharedMemory->errorString();
return;
}
setupSecondary();
}
bool ApplicationSingleton::isPrimary() const
{
return m_isPrimary;
}
bool ApplicationSingleton::isSecondary() const
{
return !m_isPrimary;
}
qint64 ApplicationSingleton::primaryPid() const
{
return m_primaryPid;
}
bool ApplicationSingleton::sendMessage(QByteArray &data, int timeout)
{
// No support for primary to secondary communication.
if (m_isPrimary) {
return false;
}
QScopedPointer<QLocalSocket, QScopedPointerDeleteLater> socket(new QLocalSocket);
socket->connectToServer(m_id);
if (!socket->waitForConnected(timeout)) {
qCWarning(log) << "Cannot connect to the local service:"
<< socket->errorString();
return false;
}
socket->write(data);
socket->flush(); // Required for Linux.
return socket->waitForBytesWritten(timeout);
}
void ApplicationSingleton::setupPrimary()
{
m_primaryPid = QCoreApplication::applicationPid();
qCInfo(log, "Starting as a primary instance. (PID: %lld)", m_primaryPid);
m_sharedMemory->lock();
SharedData *sd = static_cast<SharedData *>(m_sharedMemory->data());
sd->primaryPid = m_primaryPid;
m_sharedMemory->unlock();
QLocalServer::removeServer(m_id);
m_localServer = new QLocalServer(this);
m_localServer->setSocketOptions(QLocalServer::UserAccessOption);
connect(m_localServer, &QLocalServer::newConnection, [this] {
QLocalSocket *socket = m_localServer->nextPendingConnection();
connect(socket, &QLocalSocket::readyRead, [this, socket] {
QByteArray data = socket->readAll();
emit messageReceived(data);
socket->deleteLater();
});
});
if (!m_localServer->listen(m_id)) {
qCWarning(log) << "Cannot start the local service:"
<< m_localServer->errorString();
return;
}
}
void ApplicationSingleton::setupSecondary()
{
m_sharedMemory->lock();
SharedData *sd = static_cast<SharedData *>(m_sharedMemory->data());
m_primaryPid = sd->primaryPid;
m_sharedMemory->unlock();
qCInfo(log, "Starting as a secondary instance. (Primary PID: %lld)", m_primaryPid);
}
QString ApplicationSingleton::computeId()
{
// Make sure the result can be used as a name for the local socket.
static const QByteArray::Base64Options base64Options
= QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals;
QCryptographicHash hash(QCryptographicHash::Sha256);
hash.addData(QCoreApplication::applicationName().toUtf8());
hash.addData(QCoreApplication::organizationName().toUtf8());
hash.addData(QCoreApplication::organizationDomain().toUtf8());
// Support multi-user setup.
hash.addData(QDir::homePath().toUtf8());
return QString::fromLatin1(hash.result().toBase64(base64Options));
}
/****************************************************************************
**
** Copyright (C) 2017 Oleg Shparber
** Contact: https://go.zealdocs.org/l/contact
**
** This file is part of Zeal.
**
** Zeal is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** Zeal is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with Zeal. If not, see <https://www.gnu.org/licenses/>.
**
****************************************************************************/
#ifndef ZEAL_CORE_APPLICATIONSINGLETON_H
#define ZEAL_CORE_APPLICATIONSINGLETON_H
#include <QObject>
class QLocalServer;
class QSharedMemory;
namespace Zeal {
namespace Core {
class ApplicationSingleton : public QObject
{
Q_OBJECT
public:
explicit ApplicationSingleton(QObject *parent = nullptr);
bool isPrimary() const;
bool isSecondary() const;
qint64 primaryPid() const;
bool sendMessage(QByteArray &data, int timeout = 500);
signals:
void messageReceived(const QByteArray &data);
private:
void setupPrimary();
void setupSecondary();
static QString computeId();
QString m_id;