Add ukui-search-service.
This commit is contained in:
parent
2962c9086e
commit
02bdc54669
|
@ -0,0 +1,224 @@
|
||||||
|
Changelog
|
||||||
|
=========
|
||||||
|
|
||||||
|
__3.0.18__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Fallback to standard QApplication class on iOS and Android systems where
|
||||||
|
the library is not supported.
|
||||||
|
|
||||||
|
__3.0.17__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Fixed compilation warning/error caused by `geteuid()` on unix based systems.
|
||||||
|
|
||||||
|
_Iakov Kirilenko_
|
||||||
|
|
||||||
|
* Added CMake support
|
||||||
|
|
||||||
|
_Hennadii Chernyshchyk_
|
||||||
|
|
||||||
|
__3.0.16__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Use geteuid and getpwuid to get username on Unix, fallback to environment variable.
|
||||||
|
|
||||||
|
_Jonas Kvinge_
|
||||||
|
|
||||||
|
__3.0.15__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Bug Fix: sendMessage() might return false even though data was actually written.
|
||||||
|
|
||||||
|
_Jonas Kvinge_
|
||||||
|
|
||||||
|
__3.0.14__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Fixed uninitialised variables in the `SingleApplicationPrivate` constructor.
|
||||||
|
|
||||||
|
__3.0.13a__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Process socket events asynchronously
|
||||||
|
* Fix undefined variable error on Windows
|
||||||
|
|
||||||
|
_Francis Giraldeau_
|
||||||
|
|
||||||
|
__3.0.12a__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Removed signal handling.
|
||||||
|
|
||||||
|
__3.0.11a__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Fixed bug where the message sent by the second process was not received
|
||||||
|
correctly when the message is sent immediately following a connection.
|
||||||
|
|
||||||
|
_Francis Giraldeau_
|
||||||
|
|
||||||
|
* Refactored code and implemented shared memory block consistency checks
|
||||||
|
via `qChecksum()` (CRC-16).
|
||||||
|
* Explicit `qWarning` and `qCritical` when the library is unable to initialise
|
||||||
|
correctly.
|
||||||
|
|
||||||
|
__3.0.10__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Removed C style casts and eliminated all clang warnings. Fixed `instanceId`
|
||||||
|
reading from only one byte in the message deserialization. Cleaned up
|
||||||
|
serialization code using `QDataStream`. Changed connection type to use
|
||||||
|
`quint8 enum` rather than `char`.
|
||||||
|
* Renamed `SingleAppConnectionType` to `ConnectionType`. Added initialization
|
||||||
|
values to all `ConnectionType` enum cases.
|
||||||
|
|
||||||
|
_Jedidiah Buck McCready_
|
||||||
|
|
||||||
|
__3.0.9__
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Added SingleApplicationPrivate::primaryPid() as a solution to allow
|
||||||
|
bringing the primary window of an application to the foreground on
|
||||||
|
Windows.
|
||||||
|
|
||||||
|
_Eelco van Dam from Peacs BV_
|
||||||
|
|
||||||
|
__3.0.8__
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Bug fix - changed QApplication::instance() to QCoreApplication::instance()
|
||||||
|
|
||||||
|
_Evgeniy Bazhenov_
|
||||||
|
|
||||||
|
__3.0.7a__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Fixed compilation error with Mingw32 in MXE thanks to Vitaly Tonkacheyev.
|
||||||
|
* Removed QMutex used for thread safe behaviour. The implementation now uses
|
||||||
|
QCoreApplication::instance() to get an instance to SingleApplication for
|
||||||
|
memory deallocation.
|
||||||
|
|
||||||
|
__3.0.6a__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Reverted GetUserName API usage on Windows. Fixed bug with missing library.
|
||||||
|
* Fixed bug in the Calculator example, preventing it's window to be raised
|
||||||
|
on Windows.
|
||||||
|
|
||||||
|
Special thanks to Charles Gunawan.
|
||||||
|
|
||||||
|
__3.0.5a__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Fixed a memory leak in the SingleApplicationPrivate destructor.
|
||||||
|
|
||||||
|
_Sergei Moiseev_
|
||||||
|
|
||||||
|
__3.0.4a__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Fixed shadow and uninitialised variable warnings.
|
||||||
|
|
||||||
|
_Paul Walmsley_
|
||||||
|
|
||||||
|
__3.0.3a__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Removed Microsoft Windows specific code for getting username due to
|
||||||
|
multiple problems and compiler differences on Windows platforms. On
|
||||||
|
Windows the shared memory block in User mode now includes the user's
|
||||||
|
home path (which contains the user's username).
|
||||||
|
|
||||||
|
* Explicitly getting absolute path of the user's home directory as on Unix
|
||||||
|
a relative path (`~`) may be returned.
|
||||||
|
|
||||||
|
__3.0.2a__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Fixed bug on Windows when username containing wide characters causes the
|
||||||
|
library to crash.
|
||||||
|
|
||||||
|
_Le Liu_
|
||||||
|
|
||||||
|
__3.0.1a__
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Allows the application path and version to be excluded from the server name
|
||||||
|
hash. The following flags were added for this purpose:
|
||||||
|
* `SingleApplication::Mode::ExcludeAppVersion`
|
||||||
|
* `SingleApplication::Mode::ExcludeAppPath`
|
||||||
|
* Allow a non elevated process to connect to a local server created by an
|
||||||
|
elevated process run by the same user on Windows
|
||||||
|
* Fixes a problem with upper case letters in paths on Windows
|
||||||
|
|
||||||
|
_Le Liu_
|
||||||
|
|
||||||
|
__v3.0a__
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Deprecated secondary instances count.
|
||||||
|
* Added a sendMessage() method to send a message to the primary instance.
|
||||||
|
* Added a receivedMessage() signal, emitted when a message is received from a
|
||||||
|
secondary instance.
|
||||||
|
* The SingleApplication constructor's third parameter is now a bool
|
||||||
|
specifying if the current instance should be allowed to run as a secondary
|
||||||
|
instance if there is already a primary instance.
|
||||||
|
* The SingleApplication constructor accept a fourth parameter specifying if
|
||||||
|
the SingleApplication block should be User-wide or System-wide.
|
||||||
|
* SingleApplication no longer relies on `applicationName` and
|
||||||
|
`organizationName` to be set. It instead concatenates all of the following
|
||||||
|
data and computes a `SHA256` hash which is used as the key of the
|
||||||
|
`QSharedMemory` block and the `QLocalServer`. Since at least
|
||||||
|
`applicationFilePath` is always present there is no need to explicitly set
|
||||||
|
any of the following prior to initialising `SingleApplication`.
|
||||||
|
* `QCoreApplication::applicationName`
|
||||||
|
* `QCoreApplication::applicationVersion`
|
||||||
|
* `QCoreApplication::applicationFilePath`
|
||||||
|
* `QCoreApplication::organizationName`
|
||||||
|
* `QCoreApplication::organizationDomain`
|
||||||
|
* User name or home directory path if in User mode
|
||||||
|
* The primary instance is no longer notified when a secondary instance had
|
||||||
|
been started by default. A `Mode` flag for this feature exists.
|
||||||
|
* Added `instanceNumber()` which represents a unique identifier for each
|
||||||
|
secondary instance started. When called from the primary instance will
|
||||||
|
return `0`.
|
||||||
|
|
||||||
|
__v2.4__
|
||||||
|
--------
|
||||||
|
|
||||||
|
* Stability improvements
|
||||||
|
* Support for secondary instances.
|
||||||
|
* The library now recovers safely after the primary process has crashed
|
||||||
|
and the shared memory had not been deleted.
|
||||||
|
|
||||||
|
__v2.3__
|
||||||
|
--------
|
||||||
|
|
||||||
|
* Improved pimpl design and inheritance safety.
|
||||||
|
|
||||||
|
_Vladislav Pyatnichenko_
|
||||||
|
|
||||||
|
__v2.2__
|
||||||
|
--------
|
||||||
|
|
||||||
|
* The `QAPPLICATION_CLASS` macro can now be defined in the file including the
|
||||||
|
Single Application header or with a `DEFINES+=` statement in the project file.
|
||||||
|
|
||||||
|
__v2.1__
|
||||||
|
--------
|
||||||
|
|
||||||
|
* A race condition can no longer occur when starting two processes nearly
|
||||||
|
simultaneously.
|
||||||
|
|
||||||
|
Fix issue [#3](https://github.com/itay-grudev/SingleApplication/issues/3)
|
||||||
|
|
||||||
|
__v2.0__
|
||||||
|
--------
|
||||||
|
|
||||||
|
* SingleApplication is now being passed a reference to `argc` instead of a
|
||||||
|
copy.
|
||||||
|
|
||||||
|
Fix issue [#1](https://github.com/itay-grudev/SingleApplication/issues/1)
|
||||||
|
|
||||||
|
* Improved documentation.
|
|
@ -0,0 +1,43 @@
|
||||||
|
cmake_minimum_required(VERSION 3.1.0)
|
||||||
|
|
||||||
|
project(SingleApplication)
|
||||||
|
|
||||||
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
|
# SingleApplication base class
|
||||||
|
set(QAPPLICATION_CLASS QCoreApplication CACHE STRING "Inheritance class for SingleApplication")
|
||||||
|
set_property(CACHE QAPPLICATION_CLASS PROPERTY STRINGS QApplication QGuiApplication QCoreApplication)
|
||||||
|
|
||||||
|
# Libary target
|
||||||
|
add_library(${PROJECT_NAME} STATIC
|
||||||
|
singleapplication.cpp
|
||||||
|
singleapplication_p.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Find dependencies
|
||||||
|
find_package(Qt5Network)
|
||||||
|
if(QAPPLICATION_CLASS STREQUAL QApplication)
|
||||||
|
find_package(Qt5 COMPONENTS Widgets REQUIRED)
|
||||||
|
elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication)
|
||||||
|
find_package(Qt5 COMPONENTS Gui REQUIRED)
|
||||||
|
else()
|
||||||
|
find_package(Qt5 COMPONENTS Core REQUIRED)
|
||||||
|
endif()
|
||||||
|
add_compile_definitions(QAPPLICATION_CLASS=${QAPPLICATION_CLASS})
|
||||||
|
|
||||||
|
# Link dependencies
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Network)
|
||||||
|
if(QAPPLICATION_CLASS STREQUAL QApplication)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets)
|
||||||
|
elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Gui)
|
||||||
|
else()
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE advapi32)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
|
@ -0,0 +1,24 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) Itay Grudev 2015 - 2016
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Note: Some of the examples include code not distributed under the terms of the
|
||||||
|
MIT License.
|
|
@ -0,0 +1,277 @@
|
||||||
|
SingleApplication
|
||||||
|
=================
|
||||||
|
|
||||||
|
This is a replacement of the QtSingleApplication for `Qt5`.
|
||||||
|
|
||||||
|
Keeps the Primary Instance of your Application and kills each subsequent
|
||||||
|
instances. It can (if enabled) spawn secondary (non-related to the primary)
|
||||||
|
instances and can send data to the primary instance from secondary instances.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
The `SingleApplication` class inherits from whatever `Q[Core|Gui]Application`
|
||||||
|
class you specify via the `QAPPLICATION_CLASS` macro (`QCoreApplication` is the
|
||||||
|
default). Further usage is similar to the use of the `Q[Core|Gui]Application`
|
||||||
|
classes.
|
||||||
|
|
||||||
|
The library sets up a `QLocalServer` and a `QSharedMemory` block. The first
|
||||||
|
instance of your Application is your Primary Instance. It would check if the
|
||||||
|
shared memory block exists and if not it will start a `QLocalServer` and listen
|
||||||
|
for connections. Each subsequent instance of your application would check if the
|
||||||
|
shared memory block exists and if it does, it will connect to the QLocalServer
|
||||||
|
to notify the primary instance that a new instance had been started, after which
|
||||||
|
it would terminate with status code `0`. In the Primary Instance
|
||||||
|
`SingleApplication` would emit the `instanceStarted()` signal upon detecting
|
||||||
|
that a new instance had been started.
|
||||||
|
|
||||||
|
The library uses `stdlib` to terminate the program with the `exit()` function.
|
||||||
|
|
||||||
|
You can use the library as if you use any other `QCoreApplication` derived
|
||||||
|
class:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <QApplication>
|
||||||
|
#include <SingleApplication.h>
|
||||||
|
|
||||||
|
int main( int argc, char* argv[] )
|
||||||
|
{
|
||||||
|
SingleApplication app( argc, argv );
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To include the library files I would recommend that you add it as a git
|
||||||
|
submodule to your project and include it's contents with a `.pri` file. Here is
|
||||||
|
how:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication
|
||||||
|
```
|
||||||
|
|
||||||
|
**Qmake:**
|
||||||
|
|
||||||
|
Then include the `singleapplication.pri` file in your `.pro` project file.
|
||||||
|
|
||||||
|
```qmake
|
||||||
|
include(singleapplication/singleapplication.pri)
|
||||||
|
DEFINES += QAPPLICATION_CLASS=QApplication
|
||||||
|
```
|
||||||
|
|
||||||
|
**CMake:**
|
||||||
|
|
||||||
|
Then include the subdirectory in your `CMakeLists.txt` project file.
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
|
||||||
|
add_subdirectory(src/third-party/singleapplication)
|
||||||
|
```
|
||||||
|
|
||||||
|
Also don't forget to specify which `QCoreApplication` class your app is using if it
|
||||||
|
is not `QCoreApplication` as in examples above.
|
||||||
|
|
||||||
|
The `Instance Started` signal
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
The SingleApplication class implements a `instanceStarted()` signal. You can
|
||||||
|
bind to that signal to raise your application's window when a new instance had
|
||||||
|
been started, for example.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// window is a QWindow instance
|
||||||
|
QObject::connect(
|
||||||
|
&app,
|
||||||
|
&SingleApplication::instanceStarted,
|
||||||
|
&window,
|
||||||
|
&QWindow::raise
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Using `SingleApplication::instance()` is a neat way to get the
|
||||||
|
`SingleApplication` instance for binding to it's signals anywhere in your
|
||||||
|
program.
|
||||||
|
|
||||||
|
__Note:__ On Windows the ability to bring the application windows to the
|
||||||
|
foreground is restricted. See [Windows specific implementations](Windows.md)
|
||||||
|
for a workaround and an example implementation.
|
||||||
|
|
||||||
|
|
||||||
|
Secondary Instances
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
If you want to be able to launch additional Secondary Instances (not related to
|
||||||
|
your Primary Instance) you have to enable that with the third parameter of the
|
||||||
|
`SingleApplication` constructor. The default is `false` meaning no Secondary
|
||||||
|
Instances. Here is an example of how you would start a Secondary Instance send
|
||||||
|
a message with the command line arguments to the primary instance and then shut
|
||||||
|
down.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
SingleApplication app( argc, argv, true );
|
||||||
|
|
||||||
|
if( app.isSecondary() ) {
|
||||||
|
app.sendMessage( app.arguments().join(' ')).toUtf8() );
|
||||||
|
app.exit( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*__Note:__ A secondary instance won't cause the emission of the
|
||||||
|
`instanceStarted()` signal by default. See `SingleApplication::Mode` for more
|
||||||
|
details.*
|
||||||
|
|
||||||
|
You can check whether your instance is a primary or secondary with the following
|
||||||
|
methods:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
app.isPrimary();
|
||||||
|
// or
|
||||||
|
app.isSecondary();
|
||||||
|
```
|
||||||
|
|
||||||
|
*__Note:__ If your Primary Instance is terminated a newly launched instance
|
||||||
|
will replace the Primary one even if the Secondary flag has been set.*
|
||||||
|
|
||||||
|
API
|
||||||
|
---
|
||||||
|
|
||||||
|
### Members
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100 )
|
||||||
|
```
|
||||||
|
|
||||||
|
Depending on whether `allowSecondary` is set, this constructor may terminate
|
||||||
|
your app if there is already a primary instance running. Additional `Options`
|
||||||
|
can be specified to set whether the SingleApplication block should work
|
||||||
|
user-wide or system-wide. Additionally the `Mode::SecondaryNotification` may be
|
||||||
|
used to notify the primary instance whenever a secondary instance had been
|
||||||
|
started (disabled by default). `timeout` specifies the maximum time in
|
||||||
|
milliseconds to wait for blocking operations.
|
||||||
|
|
||||||
|
*__Note:__ `argc` and `argv` may be changed as Qt removes arguments that it
|
||||||
|
recognizes.*
|
||||||
|
|
||||||
|
*__Note:__ `Mode::SecondaryNotification` only works if set on both the primary
|
||||||
|
and the secondary instance.*
|
||||||
|
|
||||||
|
*__Note:__ Operating system can restrict the shared memory blocks to the same
|
||||||
|
user, in which case the User/System modes will have no effect and the block will
|
||||||
|
be user wide.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
bool SingleApplication::sendMessage( QByteArray message, int timeout = 100 )
|
||||||
|
```
|
||||||
|
|
||||||
|
Sends `message` to the Primary Instance. Uses `timeout` as a the maximum timeout
|
||||||
|
in milliseconds for blocking functions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
bool SingleApplication::isPrimary()
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns if the instance is the primary instance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
bool SingleApplication::isSecondary()
|
||||||
|
```
|
||||||
|
Returns if the instance is a secondary instance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
quint32 SingleApplication::instanceId()
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns a unique identifier for the current instance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
qint64 SingleApplication::primaryPid()
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns the process ID (PID) of the primary instance.
|
||||||
|
|
||||||
|
### Signals
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void SingleApplication::instanceStarted()
|
||||||
|
```
|
||||||
|
|
||||||
|
Triggered whenever a new instance had been started, except for secondary
|
||||||
|
instances if the `Mode::SecondaryNotification` flag is not specified.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void SingleApplication::receivedMessage( quint32 instanceId, QByteArray message )
|
||||||
|
```
|
||||||
|
|
||||||
|
Triggered whenever there is a message received from a secondary instance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Flags
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
enum SingleApplication::Mode
|
||||||
|
```
|
||||||
|
|
||||||
|
* `Mode::User` - The SingleApplication block should apply user wide. This adds
|
||||||
|
user specific data to the key used for the shared memory and server name.
|
||||||
|
This is the default functionality.
|
||||||
|
* `Mode::System` – The SingleApplication block applies system-wide.
|
||||||
|
* `Mode::SecondaryNotification` – Whether to trigger `instanceStarted()` even
|
||||||
|
whenever secondary instances are started.
|
||||||
|
* `Mode::ExcludeAppPath` – Excludes the application path from the server name
|
||||||
|
(and memory block) hash.
|
||||||
|
* `Mode::ExcludeAppVersion` – Excludes the application version from the server
|
||||||
|
name (and memory block) hash.
|
||||||
|
|
||||||
|
*__Note:__ `Mode::SecondaryNotification` only works if set on both the primary
|
||||||
|
and the secondary instance.*
|
||||||
|
|
||||||
|
*__Note:__ Operating system can restrict the shared memory blocks to the same
|
||||||
|
user, in which case the User/System modes will have no effect and the block will
|
||||||
|
be user wide.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Versioning
|
||||||
|
----------
|
||||||
|
|
||||||
|
Each major version introduces either very significant changes or is not
|
||||||
|
backwards compatible with the previous version. Minor versions only add
|
||||||
|
additional features, bug fixes or performance improvements and are backwards
|
||||||
|
compatible with the previous release. See [`CHANGELOG.md`](CHANGELOG.md) for
|
||||||
|
more details.
|
||||||
|
|
||||||
|
Implementation
|
||||||
|
--------------
|
||||||
|
|
||||||
|
The library is implemented with a QSharedMemory block which is thread safe and
|
||||||
|
guarantees a race condition will not occur. It also uses a QLocalSocket to
|
||||||
|
notify the main process that a new instance had been spawned and thus invoke the
|
||||||
|
`instanceStarted()` signal and for messaging the primary instance.
|
||||||
|
|
||||||
|
Additionally the library can recover from being forcefully killed on *nix
|
||||||
|
systems and will reset the memory block given that there are no other
|
||||||
|
instances running.
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
This library and it's supporting documentation are released under
|
||||||
|
`The MIT License (MIT)` with the exception of the Qt calculator examples which
|
||||||
|
is distributed under the BSD license.
|
|
@ -0,0 +1,46 @@
|
||||||
|
Windows Specific Implementations
|
||||||
|
================================
|
||||||
|
|
||||||
|
Setting the foreground window
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
In the `instanceStarted()` example in the `README` we demonstrated how an
|
||||||
|
application can bring it's primary instance window whenever a second copy
|
||||||
|
of the application is started.
|
||||||
|
|
||||||
|
On Windows the ability to bring the application windows to the foreground is
|
||||||
|
restricted, see [`AllowSetForegroundWindow()`][AllowSetForegroundWindow] for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
The background process (the primary instance) can bring its windows to the
|
||||||
|
foreground if it is allowed by the current foreground process (the secondary
|
||||||
|
instance). To bypass this `SingleApplication` must be initialized with the
|
||||||
|
`allowSecondary` parameter set to `true` and the `options` parameter must
|
||||||
|
include `Mode::SecondaryNotification`, See `SingleApplication::Mode` for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
Here is an example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if( app.isSecondary() ) {
|
||||||
|
// This API requires LIBS += User32.lib to be added to the project
|
||||||
|
AllowSetForegroundWindow( DWORD( app.primaryPid() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( app.isPrimary() ) {
|
||||||
|
QObject::connect(
|
||||||
|
&app,
|
||||||
|
&SingleApplication::instanceStarted,
|
||||||
|
this,
|
||||||
|
&App::instanceStarted
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void App::instanceStarted() {
|
||||||
|
QApplication::setActiveWindow( [window/widget to set to the foreground] );
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[AllowSetForegroundWindow]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms632668.aspx
|
|
@ -0,0 +1,181 @@
|
||||||
|
// The MIT License (MIT)
|
||||||
|
//
|
||||||
|
// Copyright (c) Itay Grudev 2015 - 2018
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include <QtCore/QTime>
|
||||||
|
#include <QtCore/QThread>
|
||||||
|
#include <QtCore/QDateTime>
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
#include <QtCore/QSharedMemory>
|
||||||
|
|
||||||
|
#include "singleapplication.h"
|
||||||
|
#include "singleapplication_p.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructor. Checks and fires up LocalServer or closes the program
|
||||||
|
* if another instance already exists
|
||||||
|
* @param argc
|
||||||
|
* @param argv
|
||||||
|
* @param {bool} allowSecondaryInstances
|
||||||
|
*/
|
||||||
|
SingleApplication::SingleApplication(int &argc, char *argv[], const char* appName, bool allowSecondary, Options options, int timeout )
|
||||||
|
: app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
|
||||||
|
{
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
|
||||||
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
|
// On Android and iOS since the library is not supported fallback to
|
||||||
|
// standard QApplication behaviour by simply returning at this point.
|
||||||
|
qWarning() << "SingleApplication is not supported on Android and iOS systems.";
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Store the current mode of the program
|
||||||
|
d->options = options;
|
||||||
|
|
||||||
|
// Generating an application ID used for identifying the shared memory
|
||||||
|
// block and QLocalServer
|
||||||
|
d->genBlockServerName(appName);
|
||||||
|
|
||||||
|
#ifdef Q_OS_UNIX
|
||||||
|
// By explicitly attaching it and then deleting it we make sure that the
|
||||||
|
// memory is deleted even after the process has crashed on Unix.
|
||||||
|
d->memory = new QSharedMemory( d->blockServerName );
|
||||||
|
d->memory->attach();
|
||||||
|
delete d->memory;
|
||||||
|
#endif
|
||||||
|
// Guarantee thread safe behaviour with a shared memory block.
|
||||||
|
d->memory = new QSharedMemory( d->blockServerName );
|
||||||
|
|
||||||
|
// Create a shared memory block
|
||||||
|
if( d->memory->create( sizeof( InstancesInfo ) ) ) {
|
||||||
|
// Initialize the shared memory block
|
||||||
|
d->memory->lock();
|
||||||
|
d->initializeMemoryBlock();
|
||||||
|
d->memory->unlock();
|
||||||
|
} else {
|
||||||
|
// Attempt to attach to the memory segment
|
||||||
|
if( ! d->memory->attach() ) {
|
||||||
|
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
||||||
|
qCritical() << d->memory->errorString();
|
||||||
|
delete d;
|
||||||
|
::exit( EXIT_FAILURE );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InstancesInfo* inst = static_cast<InstancesInfo*>( d->memory->data() );
|
||||||
|
QTime time;
|
||||||
|
time.start();
|
||||||
|
|
||||||
|
// Make sure the shared memory block is initialised and in consistent state
|
||||||
|
while( true ) {
|
||||||
|
d->memory->lock();
|
||||||
|
|
||||||
|
if( d->blockChecksum() == inst->checksum ) break;
|
||||||
|
|
||||||
|
if( time.elapsed() > 5000 ) {
|
||||||
|
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
||||||
|
d->initializeMemoryBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
d->memory->unlock();
|
||||||
|
|
||||||
|
// Random sleep here limits the probability of a collision between two racing apps
|
||||||
|
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
|
||||||
|
QThread::sleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( inst->primary == false) {
|
||||||
|
d->startPrimary();
|
||||||
|
d->memory->unlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if another instance can be started
|
||||||
|
if( allowSecondary ) {
|
||||||
|
inst->secondary += 1;
|
||||||
|
inst->checksum = d->blockChecksum();
|
||||||
|
d->instanceNumber = inst->secondary;
|
||||||
|
d->startSecondary();
|
||||||
|
if( d->options & Mode::SecondaryNotification ) {
|
||||||
|
d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance );
|
||||||
|
}
|
||||||
|
d->memory->unlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
d->memory->unlock();
|
||||||
|
|
||||||
|
d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance );
|
||||||
|
|
||||||
|
delete d;
|
||||||
|
|
||||||
|
::exit( EXIT_SUCCESS );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructor
|
||||||
|
*/
|
||||||
|
SingleApplication::~SingleApplication()
|
||||||
|
{
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleApplication::isPrimary()
|
||||||
|
{
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
return d->server != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleApplication::isSecondary()
|
||||||
|
{
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
return d->server == nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
quint32 SingleApplication::instanceId()
|
||||||
|
{
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
return d->instanceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 SingleApplication::primaryPid()
|
||||||
|
{
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
return d->primaryPid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleApplication::sendMessage( QByteArray message, int timeout )
|
||||||
|
{
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
|
||||||
|
// Nobody to connect to
|
||||||
|
if( isPrimary() ) return false;
|
||||||
|
|
||||||
|
// Make sure the socket is connected
|
||||||
|
d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect );
|
||||||
|
|
||||||
|
d->socket->write( message );
|
||||||
|
bool dataWritten = d->socket->waitForBytesWritten( timeout );
|
||||||
|
d->socket->flush();
|
||||||
|
return dataWritten;
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
// The MIT License (MIT)
|
||||||
|
//
|
||||||
|
// Copyright (c) Itay Grudev 2015 - 2018
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef SINGLE_APPLICATION_H
|
||||||
|
#define SINGLE_APPLICATION_H
|
||||||
|
|
||||||
|
#include <QtCore/QtGlobal>
|
||||||
|
#include <QtNetwork/QLocalSocket>
|
||||||
|
|
||||||
|
#ifndef QAPPLICATION_CLASS
|
||||||
|
#define QAPPLICATION_CLASS QCoreApplication
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include QT_STRINGIFY(QAPPLICATION_CLASS)
|
||||||
|
|
||||||
|
class SingleApplicationPrivate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The SingleApplication class handles multiple instances of the same
|
||||||
|
* Application
|
||||||
|
* @see QCoreApplication
|
||||||
|
*/
|
||||||
|
class SingleApplication : public QAPPLICATION_CLASS
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
typedef QAPPLICATION_CLASS app_t;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Mode of operation of SingleApplication.
|
||||||
|
* Whether the block should be user-wide or system-wide and whether the
|
||||||
|
* primary instance should be notified when a secondary instance had been
|
||||||
|
* started.
|
||||||
|
* @note Operating system can restrict the shared memory blocks to the same
|
||||||
|
* user, in which case the User/System modes will have no effect and the
|
||||||
|
* block will be user wide.
|
||||||
|
* @enum
|
||||||
|
*/
|
||||||
|
enum Mode {
|
||||||
|
User = 1 << 0,
|
||||||
|
System = 1 << 1,
|
||||||
|
SecondaryNotification = 1 << 2,
|
||||||
|
ExcludeAppVersion = 1 << 3,
|
||||||
|
ExcludeAppPath = 1 << 4
|
||||||
|
};
|
||||||
|
Q_DECLARE_FLAGS(Options, Mode)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Intitializes a SingleApplication instance with argc command line
|
||||||
|
* arguments in argv
|
||||||
|
* @arg {int &} argc - Number of arguments in argv
|
||||||
|
* @arg {const char *[]} argv - Supplied command line arguments
|
||||||
|
* @arg {bool} allowSecondary - Whether to start the instance as secondary
|
||||||
|
* if there is already a primary instance.
|
||||||
|
* @arg {Mode} mode - Whether for the SingleApplication block to be applied
|
||||||
|
* User wide or System wide.
|
||||||
|
* @arg {int} timeout - Timeout to wait in milliseconds.
|
||||||
|
* @note argc and argv may be changed as Qt removes arguments that it
|
||||||
|
* recognizes
|
||||||
|
* @note Mode::SecondaryNotification only works if set on both the primary
|
||||||
|
* instance and the secondary instance.
|
||||||
|
* @note The timeout is just a hint for the maximum time of blocking
|
||||||
|
* operations. It does not guarantee that the SingleApplication
|
||||||
|
* initialisation will be completed in given time, though is a good hint.
|
||||||
|
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||||
|
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
|
||||||
|
*/
|
||||||
|
explicit SingleApplication(int &argc, char *argv[], const char *appName = "SingleApplication", bool allowSecondary = false, Options options = Mode::User, int timeout = 1000 );
|
||||||
|
~SingleApplication();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns if the instance is the primary instance
|
||||||
|
* @returns {bool}
|
||||||
|
*/
|
||||||
|
bool isPrimary();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns if the instance is a secondary instance
|
||||||
|
* @returns {bool}
|
||||||
|
*/
|
||||||
|
bool isSecondary();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a unique identifier for the current instance
|
||||||
|
* @returns {qint32}
|
||||||
|
*/
|
||||||
|
quint32 instanceId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the process ID (PID) of the primary instance
|
||||||
|
* @returns {qint64}
|
||||||
|
*/
|
||||||
|
qint64 primaryPid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sends a message to the primary instance. Returns true on success.
|
||||||
|
* @param {int} timeout - Timeout for connecting
|
||||||
|
* @returns {bool}
|
||||||
|
* @note sendMessage() will return false if invoked from the primary
|
||||||
|
* instance.
|
||||||
|
*/
|
||||||
|
bool sendMessage( QByteArray message, int timeout = 100 );
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void instanceStarted();
|
||||||
|
void receivedMessage( quint32 instanceId, QByteArray message );
|
||||||
|
|
||||||
|
private:
|
||||||
|
SingleApplicationPrivate *d_ptr;
|
||||||
|
Q_DECLARE_PRIVATE(SingleApplication)
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
||||||
|
|
||||||
|
#endif // SINGLE_APPLICATION_H
|
|
@ -0,0 +1,19 @@
|
||||||
|
QT += core network
|
||||||
|
CONFIG += c++11
|
||||||
|
|
||||||
|
HEADERS += $$PWD/singleapplication.h \
|
||||||
|
$$PWD/singleapplication_p.h
|
||||||
|
SOURCES += $$PWD/singleapplication.cpp \
|
||||||
|
$$PWD/singleapplication_p.cpp
|
||||||
|
|
||||||
|
INCLUDEPATH += $$PWD
|
||||||
|
|
||||||
|
win32 {
|
||||||
|
msvc:LIBS += Advapi32.lib
|
||||||
|
gcc:LIBS += -ladvapi32
|
||||||
|
}
|
||||||
|
|
||||||
|
DISTFILES += \
|
||||||
|
$$PWD/README.md \
|
||||||
|
$$PWD/CHANGELOG.md \
|
||||||
|
$$PWD/Windows.md
|
|
@ -0,0 +1,407 @@
|
||||||
|
// The MIT License (MIT)
|
||||||
|
//
|
||||||
|
// Copyright (c) Itay Grudev 2015 - 2018
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//
|
||||||
|
// W A R N I N G !!!
|
||||||
|
// -----------------
|
||||||
|
//
|
||||||
|
// This file is not part of the SingleApplication API. It is used purely as an
|
||||||
|
// implementation detail. This header file may change from version to
|
||||||
|
// version without notice, or may even be removed.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include <QtCore/QDir>
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
#include <QtCore/QDataStream>
|
||||||
|
#include <QtCore/QCryptographicHash>
|
||||||
|
#include <QtNetwork/QLocalServer>
|
||||||
|
#include <QtNetwork/QLocalSocket>
|
||||||
|
|
||||||
|
#include "singleapplication.h"
|
||||||
|
#include "singleapplication_p.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_UNIX
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <lmcons.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
|
||||||
|
: q_ptr( q_ptr )
|
||||||
|
{
|
||||||
|
server = nullptr;
|
||||||
|
socket = nullptr;
|
||||||
|
memory = nullptr;
|
||||||
|
instanceNumber = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleApplicationPrivate::~SingleApplicationPrivate()
|
||||||
|
{
|
||||||
|
if( socket != nullptr ) {
|
||||||
|
socket->close();
|
||||||
|
delete socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
memory->lock();
|
||||||
|
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
|
||||||
|
if( server != nullptr ) {
|
||||||
|
server->close();
|
||||||
|
delete server;
|
||||||
|
inst->primary = false;
|
||||||
|
inst->primaryPid = -1;
|
||||||
|
inst->checksum = blockChecksum();
|
||||||
|
}
|
||||||
|
memory->unlock();
|
||||||
|
|
||||||
|
delete memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleApplicationPrivate::genBlockServerName(const char* appName)
|
||||||
|
{
|
||||||
|
QCryptographicHash appData( QCryptographicHash::Sha256 );
|
||||||
|
appData.addData( appName, 17 );
|
||||||
|
appData.addData( SingleApplication::app_t::applicationName().toUtf8() );
|
||||||
|
appData.addData( SingleApplication::app_t::organizationName().toUtf8() );
|
||||||
|
appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() );
|
||||||
|
|
||||||
|
if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ) {
|
||||||
|
appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ! (options & SingleApplication::Mode::ExcludeAppPath) ) {
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() );
|
||||||
|
#else
|
||||||
|
appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// User level block requires a user specific data in the hash
|
||||||
|
if( options & SingleApplication::Mode::User ) {
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
wchar_t username [ UNLEN + 1 ];
|
||||||
|
// Specifies size of the buffer on input
|
||||||
|
DWORD usernameLength = UNLEN + 1;
|
||||||
|
if( GetUserNameW( username, &usernameLength ) ) {
|
||||||
|
appData.addData( QString::fromWCharArray(username).toUtf8() );
|
||||||
|
} else {
|
||||||
|
appData.addData( qgetenv("USERNAME") );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef Q_OS_UNIX
|
||||||
|
QByteArray username;
|
||||||
|
uid_t uid = geteuid();
|
||||||
|
struct passwd *pw = getpwuid(uid);
|
||||||
|
if( pw ) {
|
||||||
|
username = pw->pw_name;
|
||||||
|
}
|
||||||
|
if( username.isEmpty() ) {
|
||||||
|
username = qgetenv("USER");
|
||||||
|
}
|
||||||
|
appData.addData(username);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
appData.addData(qgetenv("DISPLAY"));
|
||||||
|
|
||||||
|
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
|
||||||
|
// server naming requirements.
|
||||||
|
blockServerName = appData.result().toBase64().replace("/", "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleApplicationPrivate::initializeMemoryBlock()
|
||||||
|
{
|
||||||
|
InstancesInfo* inst = static_cast<InstancesInfo*>( memory->data() );
|
||||||
|
inst->primary = false;
|
||||||
|
inst->secondary = 0;
|
||||||
|
inst->primaryPid = -1;
|
||||||
|
inst->checksum = blockChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleApplicationPrivate::startPrimary()
|
||||||
|
{
|
||||||
|
Q_Q(SingleApplication);
|
||||||
|
|
||||||
|
// Successful creation means that no main process exists
|
||||||
|
// So we start a QLocalServer to listen for connections
|
||||||
|
QLocalServer::removeServer( blockServerName );
|
||||||
|
server = new QLocalServer();
|
||||||
|
|
||||||
|
// Restrict access to the socket according to the
|
||||||
|
// SingleApplication::Mode::User flag on User level or no restrictions
|
||||||
|
if( options & SingleApplication::Mode::User ) {
|
||||||
|
server->setSocketOptions( QLocalServer::UserAccessOption );
|
||||||
|
} else {
|
||||||
|
server->setSocketOptions( QLocalServer::WorldAccessOption );
|
||||||
|
}
|
||||||
|
|
||||||
|
server->listen( blockServerName );
|
||||||
|
QObject::connect(
|
||||||
|
server,
|
||||||
|
&QLocalServer::newConnection,
|
||||||
|
this,
|
||||||
|
&SingleApplicationPrivate::slotConnectionEstablished
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset the number of connections
|
||||||
|
InstancesInfo* inst = static_cast <InstancesInfo*>( memory->data() );
|
||||||
|
|
||||||
|
inst->primary = true;
|
||||||
|
inst->primaryPid = q->applicationPid();
|
||||||
|
inst->checksum = blockChecksum();
|
||||||
|
|
||||||
|
instanceNumber = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleApplicationPrivate::startSecondary()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
|
||||||
|
{
|
||||||
|
// Connect to the Local Server of the Primary Instance if not already
|
||||||
|
// connected.
|
||||||
|
if( socket == nullptr ) {
|
||||||
|
socket = new QLocalSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If already connected - we are done;
|
||||||
|
if( socket->state() == QLocalSocket::ConnectedState )
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If not connect
|
||||||
|
if( socket->state() == QLocalSocket::UnconnectedState ||
|
||||||
|
socket->state() == QLocalSocket::ClosingState ) {
|
||||||
|
socket->connectToServer( blockServerName );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for being connected
|
||||||
|
if( socket->state() == QLocalSocket::ConnectingState ) {
|
||||||
|
socket->waitForConnected( msecs );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialisation message according to the SingleApplication protocol
|
||||||
|
if( socket->state() == QLocalSocket::ConnectedState ) {
|
||||||
|
// Notify the parent that a new instance had been started;
|
||||||
|
QByteArray initMsg;
|
||||||
|
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||||
|
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||||
|
writeStream.setVersion(QDataStream::Qt_5_6);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
writeStream << blockServerName.toLatin1();
|
||||||
|
writeStream << static_cast<quint8>(connectionType);
|
||||||
|
writeStream << instanceNumber;
|
||||||
|
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||||
|
writeStream << checksum;
|
||||||
|
|
||||||
|
// The header indicates the message length that follows
|
||||||
|
QByteArray header;
|
||||||
|
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
||||||
|
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||||
|
headerStream.setVersion(QDataStream::Qt_5_6);
|
||||||
|
#endif
|
||||||
|
headerStream << static_cast <quint64>( initMsg.length() );
|
||||||
|
|
||||||
|
socket->write( header );
|
||||||
|
socket->write( initMsg );
|
||||||
|
socket->flush();
|
||||||
|
socket->waitForBytesWritten( msecs );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quint16 SingleApplicationPrivate::blockChecksum()
|
||||||
|
{
|
||||||
|
return qChecksum(
|
||||||
|
static_cast <const char *>( memory->data() ),
|
||||||
|
offsetof( InstancesInfo, checksum )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 SingleApplicationPrivate::primaryPid()
|
||||||
|
{
|
||||||
|
qint64 pid;
|
||||||
|
|
||||||
|
memory->lock();
|
||||||
|
InstancesInfo* inst = static_cast<InstancesInfo*>( memory->data() );
|
||||||
|
pid = inst->primaryPid;
|
||||||
|
memory->unlock();
|
||||||
|
|
||||||
|
return pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Executed when a connection has been made to the LocalServer
|
||||||
|
*/
|
||||||
|
void SingleApplicationPrivate::slotConnectionEstablished()
|
||||||
|
{
|
||||||
|
QLocalSocket *nextConnSocket = server->nextPendingConnection();
|
||||||
|
connectionMap.insert(nextConnSocket, ConnectionInfo());
|
||||||
|
|
||||||
|
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
|
||||||
|
[nextConnSocket, this]() {
|
||||||
|
auto &info = connectionMap[nextConnSocket];
|
||||||
|
Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
QObject::connect(nextConnSocket, &QLocalSocket::disconnected,
|
||||||
|
[nextConnSocket, this]() {
|
||||||
|
connectionMap.remove(nextConnSocket);
|
||||||
|
nextConnSocket->deleteLater();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
|
||||||
|
[nextConnSocket, this]() {
|
||||||
|
auto &info = connectionMap[nextConnSocket];
|
||||||
|
switch(info.stage) {
|
||||||
|
case StageHeader:
|
||||||
|
readInitMessageHeader(nextConnSocket);
|
||||||
|
break;
|
||||||
|
case StageBody:
|
||||||
|
readInitMessageBody(nextConnSocket);
|
||||||
|
break;
|
||||||
|
case StageConnected:
|
||||||
|
Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId );
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
|
||||||
|
{
|
||||||
|
if (!connectionMap.contains( sock )) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDataStream headerStream( sock );
|
||||||
|
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||||
|
headerStream.setVersion( QDataStream::Qt_5_6 );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Read the header to know the message length
|
||||||
|
quint64 msgLen = 0;
|
||||||
|
headerStream >> msgLen;
|
||||||
|
ConnectionInfo &info = connectionMap[sock];
|
||||||
|
info.stage = StageBody;
|
||||||
|
info.msgLen = msgLen;
|
||||||
|
|
||||||
|
if ( sock->bytesAvailable() >= (qint64) msgLen ) {
|
||||||
|
readInitMessageBody( sock );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
|
||||||
|
{
|
||||||
|
Q_Q(SingleApplication);
|
||||||
|
|
||||||
|
if (!connectionMap.contains( sock )) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectionInfo &info = connectionMap[sock];
|
||||||
|
if( sock->bytesAvailable() < ( qint64 )info.msgLen ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the message body
|
||||||
|
QByteArray msgBytes = sock->read(info.msgLen);
|
||||||
|
QDataStream readStream(msgBytes);
|
||||||
|
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||||
|
readStream.setVersion( QDataStream::Qt_5_6 );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// server name
|
||||||
|
QByteArray latin1Name;
|
||||||
|
readStream >> latin1Name;
|
||||||
|
|
||||||
|
// connection type
|
||||||
|
ConnectionType connectionType = InvalidConnection;
|
||||||
|
quint8 connTypeVal = InvalidConnection;
|
||||||
|
readStream >> connTypeVal;
|
||||||
|
connectionType = static_cast <ConnectionType>( connTypeVal );
|
||||||
|
|
||||||
|
// instance id
|
||||||
|
quint32 instanceId = 0;
|
||||||
|
readStream >> instanceId;
|
||||||
|
|
||||||
|
// checksum
|
||||||
|
quint16 msgChecksum = 0;
|
||||||
|
readStream >> msgChecksum;
|
||||||
|
|
||||||
|
const quint16 actualChecksum = qChecksum( msgBytes.constData(), static_cast<quint32>( msgBytes.length() - sizeof( quint16 ) ) );
|
||||||
|
|
||||||
|
bool isValid = readStream.status() == QDataStream::Ok &&
|
||||||
|
QLatin1String(latin1Name) == blockServerName &&
|
||||||
|
msgChecksum == actualChecksum;
|
||||||
|
|
||||||
|
if( !isValid ) {
|
||||||
|
sock->close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.instanceId = instanceId;
|
||||||
|
info.stage = StageConnected;
|
||||||
|
|
||||||
|
if( connectionType == NewInstance ||
|
||||||
|
( connectionType == SecondaryInstance &&
|
||||||
|
options & SingleApplication::Mode::SecondaryNotification ) )
|
||||||
|
{
|
||||||
|
Q_EMIT q->instanceStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sock->bytesAvailable() > 0) {
|
||||||
|
Q_EMIT this->slotDataAvailable( sock, instanceId );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
|
||||||
|
{
|
||||||
|
Q_Q(SingleApplication);
|
||||||
|
Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
|
||||||
|
{
|
||||||
|
if( closedSocket->bytesAvailable() > 0 )
|
||||||
|
Q_EMIT slotDataAvailable( closedSocket, instanceId );
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
// The MIT License (MIT)
|
||||||
|
//
|
||||||
|
// Copyright (c) Itay Grudev 2015 - 2016
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//
|
||||||
|
// W A R N I N G !!!
|
||||||
|
// -----------------
|
||||||
|
//
|
||||||
|
// This file is not part of the SingleApplication API. It is used purely as an
|
||||||
|
// implementation detail. This header file may change from version to
|
||||||
|
// version without notice, or may even be removed.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef SINGLEAPPLICATION_P_H
|
||||||
|
#define SINGLEAPPLICATION_P_H
|
||||||
|
|
||||||
|
#include <QtCore/QSharedMemory>
|
||||||
|
#include <QtNetwork/QLocalServer>
|
||||||
|
#include <QtNetwork/QLocalSocket>
|
||||||
|
#include "singleapplication.h"
|
||||||
|
|
||||||
|
struct InstancesInfo {
|
||||||
|
bool primary;
|
||||||
|
quint32 secondary;
|
||||||
|
qint64 primaryPid;
|
||||||
|
quint16 checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConnectionInfo {
|
||||||
|
explicit ConnectionInfo() :
|
||||||
|
msgLen(0), instanceId(0), stage(0) {}
|
||||||
|
qint64 msgLen;
|
||||||
|
quint32 instanceId;
|
||||||
|
quint8 stage;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SingleApplicationPrivate : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum ConnectionType : quint8 {
|
||||||
|
InvalidConnection = 0,
|
||||||
|
NewInstance = 1,
|
||||||
|
SecondaryInstance = 2,
|
||||||
|
Reconnect = 3
|
||||||
|
};
|
||||||
|
enum ConnectionStage : quint8 {
|
||||||
|
StageHeader = 0,
|
||||||
|
StageBody = 1,
|
||||||
|
StageConnected = 2,
|
||||||
|
};
|
||||||
|
Q_DECLARE_PUBLIC(SingleApplication)
|
||||||
|
|
||||||
|
SingleApplicationPrivate( SingleApplication *q_ptr );
|
||||||
|
~SingleApplicationPrivate();
|
||||||
|
|
||||||
|
void genBlockServerName(const char *appName);
|
||||||
|
void initializeMemoryBlock();
|
||||||
|
void startPrimary();
|
||||||
|
void startSecondary();
|
||||||
|
void connectToPrimary(int msecs, ConnectionType connectionType );
|
||||||
|
quint16 blockChecksum();
|
||||||
|
qint64 primaryPid();
|
||||||
|
void readInitMessageHeader(QLocalSocket *socket);
|
||||||
|
void readInitMessageBody(QLocalSocket *socket);
|
||||||
|
|
||||||
|
SingleApplication *q_ptr;
|
||||||
|
QSharedMemory *memory;
|
||||||
|
QLocalSocket *socket;
|
||||||
|
QLocalServer *server;
|
||||||
|
quint32 instanceNumber;
|
||||||
|
QString blockServerName;
|
||||||
|
SingleApplication::Options options;
|
||||||
|
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void slotConnectionEstablished();
|
||||||
|
void slotDataAvailable( QLocalSocket*, quint32 );
|
||||||
|
void slotClientConnectionClosed( QLocalSocket*, quint32 );
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SINGLEAPPLICATION_P_H
|
|
@ -0,0 +1 @@
|
||||||
|
#include "qtlockedfile.h"
|
|
@ -0,0 +1 @@
|
||||||
|
#include "qtsingleapplication.h"
|
|
@ -0,0 +1,216 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||||
|
** Contact: http://www.qt-project.org/legal
|
||||||
|
**
|
||||||
|
** This file is part of the Qt Solutions component.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** You may use this file under the terms of the BSD license as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
|
||||||
|
** of its contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include "qtlocalpeer.h"
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDataStream>
|
||||||
|
#include <QTime>
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
#include <QLibrary>
|
||||||
|
#include <qt_windows.h>
|
||||||
|
typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*);
|
||||||
|
static PProcessIdToSessionId pProcessIdToSessionId = 0;
|
||||||
|
#endif
|
||||||
|
#if defined(Q_OS_UNIX)
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace QtLP_Private {
|
||||||
|
#include "qtlockedfile.cpp"
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
#include "qtlockedfile_win.cpp"
|
||||||
|
#else
|
||||||
|
#include "qtlockedfile_unix.cpp"
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* QtLocalPeer::ack = "ack";
|
||||||
|
|
||||||
|
QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
|
||||||
|
: QObject(parent), id(appId)
|
||||||
|
{
|
||||||
|
QString prefix = id;
|
||||||
|
if (id.isEmpty()) {
|
||||||
|
id = QCoreApplication::applicationFilePath();
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
id = id.toLower();
|
||||||
|
#endif
|
||||||
|
prefix = id.section(QLatin1Char('/'), -1);
|
||||||
|
}
|
||||||
|
prefix.remove(QRegExp("[^a-zA-Z]"));
|
||||||
|
prefix.truncate(6);
|
||||||
|
|
||||||
|
QByteArray idc = id.toUtf8();
|
||||||
|
quint16 idNum = qChecksum(idc.constData(), idc.size());
|
||||||
|
socketName = QLatin1String("qtsingleapp-") + prefix
|
||||||
|
+ QLatin1Char('-') + QString::number(idNum, 16);
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
if (!pProcessIdToSessionId) {
|
||||||
|
QLibrary lib("kernel32");
|
||||||
|
pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId");
|
||||||
|
}
|
||||||
|
if (pProcessIdToSessionId) {
|
||||||
|
DWORD sessionId = 0;
|
||||||
|
pProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
|
||||||
|
socketName += QLatin1Char('-') + QString::number(sessionId, 16);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
socketName += QLatin1Char('-') + QString::number(::getuid(), 16);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
server = new QLocalServer(this);
|
||||||
|
QString lockName = QDir(QDir::tempPath()).absolutePath()
|
||||||
|
+ QLatin1Char('/') + socketName
|
||||||
|
+ QLatin1String("-lockfile");
|
||||||
|
lockFile.setFileName(lockName);
|
||||||
|
lockFile.open(QIODevice::ReadWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool QtLocalPeer::isClient()
|
||||||
|
{
|
||||||
|
if (lockFile.isLocked())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
bool res = server->listen(socketName);
|
||||||
|
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0))
|
||||||
|
// ### Workaround
|
||||||
|
if (!res && server->serverError() == QAbstractSocket::AddressInUseError) {
|
||||||
|
QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName);
|
||||||
|
res = server->listen(socketName);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (!res)
|
||||||
|
qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString()));
|
||||||
|
QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool QtLocalPeer::sendMessage(const QString &message, int timeout)
|
||||||
|
{
|
||||||
|
if (!isClient())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QLocalSocket socket;
|
||||||
|
bool connOk = false;
|
||||||
|
for(int i = 0; i < 2; i++) {
|
||||||
|
// Try twice, in case the other instance is just starting up
|
||||||
|
socket.connectToServer(socketName);
|
||||||
|
connOk = socket.waitForConnected(timeout/2);
|
||||||
|
if (connOk || i)
|
||||||
|
break;
|
||||||
|
int ms = 250;
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
Sleep(DWORD(ms));
|
||||||
|
#else
|
||||||
|
struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
|
||||||
|
nanosleep(&ts, NULL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if (!connOk)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QByteArray uMsg(message.toUtf8());
|
||||||
|
QDataStream ds(&socket);
|
||||||
|
ds.writeBytes(uMsg.constData(), uMsg.size());
|
||||||
|
bool res = socket.waitForBytesWritten(timeout);
|
||||||
|
if (res) {
|
||||||
|
res &= socket.waitForReadyRead(timeout); // wait for ack
|
||||||
|
if (res)
|
||||||
|
res &= (socket.read(qstrlen(ack)) == ack);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void QtLocalPeer::receiveConnection()
|
||||||
|
{
|
||||||
|
QLocalSocket* socket = server->nextPendingConnection();
|
||||||
|
if (!socket)
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (socket->state() == QLocalSocket::UnconnectedState) {
|
||||||
|
qWarning("QtLocalPeer: Peer disconnected");
|
||||||
|
delete socket;
|
||||||
|
socket = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (socket->bytesAvailable() >= qint64(sizeof(quint32)))
|
||||||
|
break;
|
||||||
|
socket->waitForReadyRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
QDataStream ds(socket);
|
||||||
|
QByteArray uMsg;
|
||||||
|
quint32 remaining;
|
||||||
|
ds >> remaining;
|
||||||
|
uMsg.resize(remaining);
|
||||||
|
int got = 0;
|
||||||
|
char* uMsgBuf = uMsg.data();
|
||||||
|
do {
|
||||||
|
got = ds.readRawData(uMsgBuf, remaining);
|
||||||
|
remaining -= got;
|
||||||
|
uMsgBuf += got;
|
||||||
|
} while (remaining && got >= 0 && socket->waitForReadyRead(2000));
|
||||||
|
if (got < 0) {
|
||||||
|
qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData());
|
||||||
|
delete socket;
|
||||||
|
socket = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QString message(QString::fromUtf8(uMsg));
|
||||||
|
socket->write(ack, qstrlen(ack));
|
||||||
|
socket->waitForBytesWritten(1000);
|
||||||
|
socket->waitForDisconnected(1000); // make sure client reads ack
|
||||||
|
delete socket;
|
||||||
|
socket = nullptr;
|
||||||
|
Q_EMIT messageReceived(message); //### (might take a long time to return)
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||||
|
** Contact: http://www.qt-project.org/legal
|
||||||
|
**
|
||||||
|
** This file is part of the Qt Solutions component.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** You may use this file under the terms of the BSD license as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
|
||||||
|
** of its contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef QTLOCALPEER_H
|
||||||
|
#define QTLOCALPEER_H
|
||||||
|
|
||||||
|
#include <QLocalServer>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QDir>
|
||||||
|
|
||||||
|
#include "qtlockedfile.h"
|
||||||
|
|
||||||
|
class QtLocalPeer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
QtLocalPeer(QObject *parent = 0, const QString &appId = QString());
|
||||||
|
bool isClient();
|
||||||
|
bool sendMessage(const QString &message, int timeout);
|
||||||
|
QString applicationId() const
|
||||||
|
{ return id; }
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void messageReceived(const QString &message);
|
||||||
|
|
||||||
|
protected Q_SLOTS:
|
||||||
|
void receiveConnection();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QString id;
|
||||||
|
QString socketName;
|
||||||
|
QLocalServer* server;
|
||||||
|
QtLP_Private::QtLockedFile lockFile;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const char* ack;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QTLOCALPEER_H
|
|
@ -0,0 +1,193 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||||
|
** Contact: http://www.qt-project.org/legal
|
||||||
|
**
|
||||||
|
** This file is part of the Qt Solutions component.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** You may use this file under the terms of the BSD license as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
|
||||||
|
** of its contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "qtlockedfile.h"
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\class QtLockedFile
|
||||||
|
|
||||||
|
\brief The QtLockedFile class extends QFile with advisory locking
|
||||||
|
functions.
|
||||||
|
|
||||||
|
A file may be locked in read or write mode. Multiple instances of
|
||||||
|
\e QtLockedFile, created in multiple processes running on the same
|
||||||
|
machine, may have a file locked in read mode. Exactly one instance
|
||||||
|
may have it locked in write mode. A read and a write lock cannot
|
||||||
|
exist simultaneously on the same file.
|
||||||
|
|
||||||
|
The file locks are advisory. This means that nothing prevents
|
||||||
|
another process from manipulating a locked file using QFile or
|
||||||
|
file system functions offered by the OS. Serialization is only
|
||||||
|
guaranteed if all processes that access the file use
|
||||||
|
QLockedFile. Also, while holding a lock on a file, a process
|
||||||
|
must not open the same file again (through any API), or locks
|
||||||
|
can be unexpectedly lost.
|
||||||
|
|
||||||
|
The lock provided by an instance of \e QtLockedFile is released
|
||||||
|
whenever the program terminates. This is true even when the
|
||||||
|
program crashes and no destructors are called.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \enum QtLockedFile::LockMode
|
||||||
|
|
||||||
|
This enum describes the available lock modes.
|
||||||
|
|
||||||
|
\value ReadLock A read lock.
|
||||||
|
\value WriteLock A write lock.
|
||||||
|
\value NoLock Neither a read lock nor a write lock.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Constructs an unlocked \e QtLockedFile object. This constructor
|
||||||
|
behaves in the same way as \e QFile::QFile().
|
||||||
|
|
||||||
|
\sa QFile::QFile()
|
||||||
|
*/
|
||||||
|
QtLockedFile::QtLockedFile()
|
||||||
|
: QFile()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
wmutex = 0;
|
||||||
|
rmutex = 0;
|
||||||
|
#endif
|
||||||
|
m_lock_mode = NoLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Constructs an unlocked QtLockedFile object with file \a name. This
|
||||||
|
constructor behaves in the same way as \e QFile::QFile(const
|
||||||
|
QString&).
|
||||||
|
|
||||||
|
\sa QFile::QFile()
|
||||||
|
*/
|
||||||
|
QtLockedFile::QtLockedFile(const QString &name)
|
||||||
|
: QFile(name)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
wmutex = 0;
|
||||||
|
rmutex = 0;
|
||||||
|
#endif
|
||||||
|
m_lock_mode = NoLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Opens the file in OpenMode \a mode.
|
||||||
|
|
||||||
|
This is identical to QFile::open(), with the one exception that the
|
||||||
|
Truncate mode flag is disallowed. Truncation would conflict with the
|
||||||
|
advisory file locking, since the file would be modified before the
|
||||||
|
write lock is obtained. If truncation is required, use resize(0)
|
||||||
|
after obtaining the write lock.
|
||||||
|
|
||||||
|
Returns true if successful; otherwise false.
|
||||||
|
|
||||||
|
\sa QFile::open(), QFile::resize()
|
||||||
|
*/
|
||||||
|
bool QtLockedFile::open(OpenMode mode)
|
||||||
|
{
|
||||||
|
if (mode & QIODevice::Truncate) {
|
||||||
|
qWarning("QtLockedFile::open(): Truncate mode not allowed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return QFile::open(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns \e true if this object has a in read or write lock;
|
||||||
|
otherwise returns \e false.
|
||||||
|
|
||||||
|
\sa lockMode()
|
||||||
|
*/
|
||||||
|
bool QtLockedFile::isLocked() const
|
||||||
|
{
|
||||||
|
return m_lock_mode != NoLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the type of lock currently held by this object, or \e
|
||||||
|
QtLockedFile::NoLock.
|
||||||
|
|
||||||
|
\sa isLocked()
|
||||||
|
*/
|
||||||
|
QtLockedFile::LockMode QtLockedFile::lockMode() const
|
||||||
|
{
|
||||||
|
return m_lock_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\fn bool QtLockedFile::lock(LockMode mode, bool block = true)
|
||||||
|
|
||||||
|
Obtains a lock of type \a mode. The file must be opened before it
|
||||||
|
can be locked.
|
||||||
|
|
||||||
|
If \a block is true, this function will block until the lock is
|
||||||
|
aquired. If \a block is false, this function returns \e false
|
||||||
|
immediately if the lock cannot be aquired.
|
||||||
|
|
||||||
|
If this object already has a lock of type \a mode, this function
|
||||||
|
returns \e true immediately. If this object has a lock of a
|
||||||
|
different type than \a mode, the lock is first released and then a
|
||||||
|
new lock is obtained.
|
||||||
|
|
||||||
|
This function returns \e true if, after it executes, the file is
|
||||||
|
locked by this object, and \e false otherwise.
|
||||||
|
|
||||||
|
\sa unlock(), isLocked(), lockMode()
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\fn bool QtLockedFile::unlock()
|
||||||
|
|
||||||
|
Releases a lock.
|
||||||
|
|
||||||
|
If the object has no lock, this function returns immediately.
|
||||||
|
|
||||||
|
This function returns \e true if, after it executes, the file is
|
||||||
|
not locked by this object, and \e false otherwise.
|
||||||
|
|
||||||
|
\sa lock(), isLocked(), lockMode()
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\fn QtLockedFile::~QtLockedFile()
|
||||||
|
|
||||||
|
Destroys the \e QtLockedFile object. If any locks were held, they
|
||||||
|
are released.
|
||||||
|
*/
|
|
@ -0,0 +1,97 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||||
|
** Contact: http://www.qt-project.org/legal
|
||||||
|
**
|
||||||
|
** This file is part of the Qt Solutions component.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** You may use this file under the terms of the BSD license as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
|
||||||
|
** of its contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef QTLOCKEDFILE_H
|
||||||
|
#define QTLOCKEDFILE_H
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <QVector>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT)
|
||||||
|
# define QT_QTLOCKEDFILE_EXPORT
|
||||||
|
# elif defined(QT_QTLOCKEDFILE_IMPORT)
|
||||||
|
# if defined(QT_QTLOCKEDFILE_EXPORT)
|
||||||
|
# undef QT_QTLOCKEDFILE_EXPORT
|
||||||
|
# endif
|
||||||
|
# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport)
|
||||||
|
# elif defined(QT_QTLOCKEDFILE_EXPORT)
|
||||||
|
# undef QT_QTLOCKEDFILE_EXPORT
|
||||||
|
# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport)
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
# define QT_QTLOCKEDFILE_EXPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace QtLP_Private {
|
||||||
|
|
||||||
|
class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum LockMode { NoLock = 0, ReadLock, WriteLock };
|
||||||
|
|
||||||
|
QtLockedFile();
|
||||||
|
QtLockedFile(const QString &name);
|
||||||
|
~QtLockedFile();
|
||||||
|
|
||||||
|
bool open(OpenMode mode);
|
||||||
|
|
||||||
|
bool lock(LockMode mode, bool block = true);
|
||||||
|
bool unlock();
|
||||||
|
bool isLocked() const;
|
||||||
|
LockMode lockMode() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
Qt::HANDLE wmutex;
|
||||||
|
Qt::HANDLE rmutex;
|
||||||
|
QVector<Qt::HANDLE> rmutexes;
|
||||||
|
QString mutexname;
|
||||||
|
|
||||||
|
Qt::HANDLE getMutexHandle(int idx, bool doCreate);
|
||||||
|
bool waitMutex(Qt::HANDLE mutex, bool doBlock);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
LockMode m_lock_mode;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,115 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||||
|
** Contact: http://www.qt-project.org/legal
|
||||||
|
**
|
||||||
|
** This file is part of the Qt Solutions component.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** You may use this file under the terms of the BSD license as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
|
||||||
|
** of its contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include "qtlockedfile.h"
|
||||||
|
|
||||||
|
bool QtLockedFile::lock(LockMode mode, bool block)
|
||||||
|
{
|
||||||
|
if (!isOpen()) {
|
||||||
|
qWarning("QtLockedFile::lock(): file is not opened");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == NoLock)
|
||||||
|
return unlock();
|
||||||
|
|
||||||
|
if (mode == m_lock_mode)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (m_lock_mode != NoLock)
|
||||||
|
unlock();
|
||||||
|
|
||||||
|
struct flock fl;
|
||||||
|
fl.l_whence = SEEK_SET;
|
||||||
|
fl.l_start = 0;
|
||||||
|
fl.l_len = 0;
|
||||||
|
fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK;
|
||||||
|
int cmd = block ? F_SETLKW : F_SETLK;
|
||||||
|
int ret = fcntl(handle(), cmd, &fl);
|
||||||
|
|
||||||
|
if (ret == -1) {
|
||||||
|
if (errno != EINTR && errno != EAGAIN)
|
||||||
|
qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
m_lock_mode = mode;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool QtLockedFile::unlock()
|
||||||
|
{
|
||||||
|
if (!isOpen()) {
|
||||||
|
qWarning("QtLockedFile::unlock(): file is not opened");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLocked())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
struct flock fl;
|
||||||
|
fl.l_whence = SEEK_SET;
|
||||||
|
fl.l_start = 0;
|
||||||
|
fl.l_len = 0;
|
||||||
|
fl.l_type = F_UNLCK;
|
||||||
|
int ret = fcntl(handle(), F_SETLKW, &fl);
|
||||||
|
|
||||||
|
if (ret == -1) {
|
||||||
|
qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lock_mode = NoLock;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QtLockedFile::~QtLockedFile()
|
||||||
|
{
|
||||||
|
if (isOpen())
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||||
|
** Contact: http://www.qt-project.org/legal
|
||||||
|
**
|
||||||
|
** This file is part of the Qt Solutions component.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** You may use this file under the terms of the BSD license as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
|
||||||
|
** of its contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "qtlockedfile.h"
|
||||||
|
#include <qt_windows.h>
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
|
#define MUTEX_PREFIX "QtLockedFile mutex "
|
||||||
|
// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS
|
||||||
|
#define MAX_READERS MAXIMUM_WAIT_OBJECTS
|
||||||
|
|
||||||
|
#if QT_VERSION >= 0x050000
|
||||||
|
#define QT_WA(unicode, ansi) unicode
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate)
|
||||||
|
{
|
||||||
|
if (mutexname.isEmpty()) {
|
||||||
|
QFileInfo fi(*this);
|
||||||
|
mutexname = QString::fromLatin1(MUTEX_PREFIX)
|
||||||
|
+ fi.absoluteFilePath().toLower();
|
||||||
|
}
|
||||||
|
QString mname(mutexname);
|
||||||
|
if (idx >= 0)
|
||||||
|
mname += QString::number(idx);
|
||||||
|
|
||||||
|
Qt::HANDLE mutex;
|
||||||
|
if (doCreate) {
|
||||||
|
QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); },
|
||||||
|
{ mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } );
|
||||||
|
if (!mutex) {
|
||||||
|
qErrnoWarning("QtLockedFile::lock(): CreateMutex failed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); },
|
||||||
|
{ mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } );
|
||||||
|
if (!mutex) {
|
||||||
|
if (GetLastError() != ERROR_FILE_NOT_FOUND)
|
||||||
|
qErrnoWarning("QtLockedFile::lock(): OpenMutex failed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mutex;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock)
|
||||||
|
{
|
||||||
|
Q_ASSERT(mutex);
|
||||||
|
DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0);
|
||||||
|
switch (res) {
|
||||||
|
case WAIT_OBJECT_0:
|
||||||
|
case WAIT_ABANDONED:
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
case WAIT_TIMEOUT:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool QtLockedFile::lock(LockMode mode, bool block)
|
||||||
|
{
|
||||||
|
if (!isOpen()) {
|
||||||
|
qWarning("QtLockedFile::lock(): file is not opened");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == NoLock)
|
||||||
|
return unlock();
|
||||||
|
|
||||||
|
if (mode == m_lock_mode)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (m_lock_mode != NoLock)
|
||||||
|
unlock();
|
||||||
|
|
||||||
|
if (!wmutex && !(wmutex = getMutexHandle(-1, true)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!waitMutex(wmutex, block))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (mode == ReadLock) {
|
||||||
|
int idx = 0;
|
||||||
|
for (; idx < MAX_READERS; idx++) {
|
||||||
|
rmutex = getMutexHandle(idx, false);
|
||||||
|
if (!rmutex || waitMutex(rmutex, false))
|
||||||
|
break;
|
||||||
|
CloseHandle(rmutex);
|
||||||
|
}
|
||||||
|
bool ok = true;
|
||||||
|
if (idx >= MAX_READERS) {
|
||||||
|
qWarning("QtLockedFile::lock(): too many readers");
|
||||||
|
rmutex = 0;
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
else if (!rmutex) {
|
||||||
|
rmutex = getMutexHandle(idx, true);
|
||||||
|
if (!rmutex || !waitMutex(rmutex, false))
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
if (!ok && rmutex) {
|
||||||
|
CloseHandle(rmutex);
|
||||||
|
rmutex = 0;
|
||||||
|
}
|
||||||
|
ReleaseMutex(wmutex);
|
||||||
|
if (!ok)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Q_ASSERT(rmutexes.isEmpty());
|
||||||
|
for (int i = 0; i < MAX_READERS; i++) {
|
||||||
|
Qt::HANDLE mutex = getMutexHandle(i, false);
|
||||||
|
if (mutex)
|
||||||
|
rmutexes.append(mutex);
|
||||||
|
}
|
||||||
|
if (rmutexes.size()) {
|
||||||
|
DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(),
|
||||||
|
TRUE, block ? INFINITE : 0);
|
||||||
|
if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) {
|
||||||
|
if (res != WAIT_TIMEOUT)
|
||||||
|
qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed");
|
||||||
|
m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky
|
||||||
|
unlock();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lock_mode = mode;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QtLockedFile::unlock()
|
||||||
|
{
|
||||||
|
if (!isOpen()) {
|
||||||
|
qWarning("QtLockedFile::unlock(): file is not opened");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLocked())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (m_lock_mode == ReadLock) {
|
||||||
|
ReleaseMutex(rmutex);
|
||||||
|
CloseHandle(rmutex);
|
||||||
|
rmutex = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
foreach(Qt::HANDLE mutex, rmutexes) {
|
||||||
|
ReleaseMutex(mutex);
|
||||||
|
CloseHandle(mutex);
|
||||||
|
}
|
||||||
|
rmutexes.clear();
|
||||||
|
ReleaseMutex(wmutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lock_mode = QtLockedFile::NoLock;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QtLockedFile::~QtLockedFile()
|
||||||
|
{
|
||||||
|
if (isOpen())
|
||||||
|
unlock();
|
||||||
|
if (wmutex)
|
||||||
|
CloseHandle(wmutex);
|
||||||
|
}
|
|
@ -0,0 +1,347 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||||
|
** Contact: http://www.qt-project.org/legal
|
||||||
|
**
|
||||||
|
** This file is part of the Qt Solutions component.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** You may use this file under the terms of the BSD license as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
|
||||||
|
** of its contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include "qtsingleapplication.h"
|
||||||
|
#include "qtlocalpeer.h"
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\class QtSingleApplication qtsingleapplication.h
|
||||||
|
\brief The QtSingleApplication class provides an API to detect and
|
||||||
|
communicate with running instances of an application.
|
||||||
|
|
||||||
|
This class allows you to create applications where only one
|
||||||
|
instance should be running at a time. I.e., if the user tries to
|
||||||
|
launch another instance, the already running instance will be
|
||||||
|
activated instead. Another usecase is a client-server system,
|
||||||
|
where the first started instance will assume the role of server,
|
||||||
|
and the later instances will act as clients of that server.
|
||||||
|
|
||||||
|
By default, the full path of the executable file is used to
|
||||||
|
determine whether two processes are instances of the same
|
||||||
|
application. You can also provide an explicit identifier string
|
||||||
|
that will be compared instead.
|
||||||
|
|
||||||
|
The application should create the QtSingleApplication object early
|
||||||
|
in the startup phase, and call isRunning() to find out if another
|
||||||
|
instance of this application is already running. If isRunning()
|
||||||
|
returns false, it means that no other instance is running, and
|
||||||
|
this instance has assumed the role as the running instance. In
|
||||||
|
this case, the application should continue with the initialization
|
||||||
|
of the application user interface before entering the event loop
|
||||||
|
with exec(), as normal.
|
||||||
|
|
||||||
|
The messageReceived() signal will be emitted when the running
|
||||||
|
application receives messages from another instance of the same
|
||||||
|
application. When a message is received it might be helpful to the
|
||||||
|
user to raise the application so that it becomes visible. To
|
||||||
|
facilitate this, QtSingleApplication provides the
|
||||||
|
setActivationWindow() function and the activateWindow() slot.
|
||||||
|
|
||||||
|
If isRunning() returns true, another instance is already
|
||||||
|
running. It may be alerted to the fact that another instance has
|
||||||
|
started by using the sendMessage() function. Also data such as
|
||||||
|
startup parameters (e.g. the name of the file the user wanted this
|
||||||
|
new instance to open) can be passed to the running instance with
|
||||||
|
this function. Then, the application should terminate (or enter
|
||||||
|
client mode).
|
||||||
|
|
||||||
|
If isRunning() returns true, but sendMessage() fails, that is an
|
||||||
|
indication that the running instance is frozen.
|
||||||
|
|
||||||
|
Here's an example that shows how to convert an existing
|
||||||
|
application to use QtSingleApplication. It is very simple and does
|
||||||
|
not make use of all QtSingleApplication's functionality (see the
|
||||||
|
examples for that).
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Original
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
|
MyMainWidget mmw;
|
||||||
|
mmw.show();
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single instance
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
QtSingleApplication app(argc, argv);
|
||||||
|
|
||||||
|
if (app.isRunning())
|
||||||
|
return !app.sendMessage(someDataString);
|
||||||
|
|
||||||
|
MyMainWidget mmw;
|
||||||
|
app.setActivationWindow(&mmw);
|
||||||
|
mmw.show();
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
Once this QtSingleApplication instance is destroyed (normally when
|
||||||
|
the process exits or crashes), when the user next attempts to run the
|
||||||
|
application this instance will not, of course, be encountered. The
|
||||||
|
next instance to call isRunning() or sendMessage() will assume the
|
||||||
|
role as the new running instance.
|
||||||
|
|
||||||
|
For console (non-GUI) applications, QtSingleCoreApplication may be
|
||||||
|
used instead of this class, to avoid the dependency on the QtGui
|
||||||
|
library.
|
||||||
|
|
||||||
|
\sa QtSingleCoreApplication
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
void QtSingleApplication::sysInit(const QString &appId)
|
||||||
|
{
|
||||||
|
actWin = 0;
|
||||||
|
peer = new QtLocalPeer(this, appId);
|
||||||
|
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Creates a QtSingleApplication object. The application identifier
|
||||||
|
will be QCoreApplication::applicationFilePath(). \a argc, \a
|
||||||
|
argv, and \a GUIenabled are passed on to the QAppliation constructor.
|
||||||
|
|
||||||
|
If you are creating a console application (i.e. setting \a
|
||||||
|
GUIenabled to false), you may consider using
|
||||||
|
QtSingleCoreApplication instead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled)
|
||||||
|
: QApplication(argc, argv, GUIenabled)
|
||||||
|
{
|
||||||
|
sysInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Creates a QtSingleApplication object with the application
|
||||||
|
identifier \a appId. \a argc and \a argv are passed on to the
|
||||||
|
QAppliation constructor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
|
||||||
|
: QApplication(argc, argv)
|
||||||
|
{
|
||||||
|
sysInit(appId);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if QT_VERSION < 0x050000
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Creates a QtSingleApplication object. The application identifier
|
||||||
|
will be QCoreApplication::applicationFilePath(). \a argc, \a
|
||||||
|
argv, and \a type are passed on to the QAppliation constructor.
|
||||||
|
*/
|
||||||
|
QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type)
|
||||||
|
: QApplication(argc, argv, type)
|
||||||
|
{
|
||||||
|
sysInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# if defined(Q_WS_X11)
|
||||||
|
/*!
|
||||||
|
Special constructor for X11, ref. the documentation of
|
||||||
|
QApplication's corresponding constructor. The application identifier
|
||||||
|
will be QCoreApplication::applicationFilePath(). \a dpy, \a visual,
|
||||||
|
and \a cmap are passed on to the QApplication constructor.
|
||||||
|
*/
|
||||||
|
QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap)
|
||||||
|
: QApplication(dpy, visual, cmap)
|
||||||
|
{
|
||||||
|
sysInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Special constructor for X11, ref. the documentation of
|
||||||
|
QApplication's corresponding constructor. The application identifier
|
||||||
|
will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a
|
||||||
|
argv, \a visual, and \a cmap are passed on to the QApplication
|
||||||
|
constructor.
|
||||||
|
*/
|
||||||
|
QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
|
||||||
|
: QApplication(dpy, argc, argv, visual, cmap)
|
||||||
|
{
|
||||||
|
sysInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Special constructor for X11, ref. the documentation of
|
||||||
|
QApplication's corresponding constructor. The application identifier
|
||||||
|
will be \a appId. \a dpy, \a argc, \a
|
||||||
|
argv, \a visual, and \a cmap are passed on to the QApplication
|
||||||
|
constructor.
|
||||||
|
*/
|
||||||
|
QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
|
||||||
|
: QApplication(dpy, argc, argv, visual, cmap)
|
||||||
|
{
|
||||||
|
sysInit(appId);
|
||||||
|
}
|
||||||
|
# endif // Q_WS_X11
|
||||||
|
#endif // QT_VERSION < 0x050000
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns true if another instance of this application is running;
|
||||||
|
otherwise false.
|
||||||
|
|
||||||
|
This function does not find instances of this application that are
|
||||||
|
being run by a different user (on Windows: that are running in
|
||||||
|
another session).
|
||||||
|
|
||||||
|
\sa sendMessage()
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool QtSingleApplication::isRunning()
|
||||||
|
{
|
||||||
|
return peer->isClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Tries to send the text \a message to the currently running
|
||||||
|
instance. The QtSingleApplication object in the running instance
|
||||||
|
will emit the messageReceived() signal when it receives the
|
||||||
|
message.
|
||||||
|
|
||||||
|
This function returns true if the message has been sent to, and
|
||||||
|
processed by, the current instance. If there is no instance
|
||||||
|
currently running, or if the running instance fails to process the
|
||||||
|
message within \a timeout milliseconds, this function return false.
|
||||||
|
|
||||||
|
\sa isRunning(), messageReceived()
|
||||||
|
*/
|
||||||
|
bool QtSingleApplication::sendMessage(const QString &message, int timeout)
|
||||||
|
{
|
||||||
|
return peer->sendMessage(message, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the application identifier. Two processes with the same
|
||||||
|
identifier will be regarded as instances of the same application.
|
||||||
|
*/
|
||||||
|
QString QtSingleApplication::id() const
|
||||||
|
{
|
||||||
|
return peer->applicationId();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Sets the activation window of this application to \a aw. The
|
||||||
|
activation window is the widget that will be activated by
|
||||||
|
activateWindow(). This is typically the application's main window.
|
||||||
|
|
||||||
|
If \a activateOnMessage is true (the default), the window will be
|
||||||
|
activated automatically every time a message is received, just prior
|
||||||
|
to the messageReceived() signal being emitted.
|
||||||
|
|
||||||
|
\sa activateWindow(), messageReceived()
|
||||||
|
*/
|
||||||
|
|
||||||
|
void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage)
|
||||||
|
{
|
||||||
|
actWin = aw;
|
||||||
|
if (activateOnMessage)
|
||||||
|
connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
|
||||||
|
else
|
||||||
|
disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the applications activation window if one has been set by
|
||||||
|
calling setActivationWindow(), otherwise returns 0.
|
||||||
|
|
||||||
|
\sa setActivationWindow()
|
||||||
|
*/
|
||||||
|
QWidget* QtSingleApplication::activationWindow() const
|
||||||
|
{
|
||||||
|
return actWin;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
De-minimizes, raises, and activates this application's activation window.
|
||||||
|
This function does nothing if no activation window has been set.
|
||||||
|
|
||||||
|
This is a convenience function to show the user that this
|
||||||
|
application instance has been activated when he has tried to start
|
||||||
|
another instance.
|
||||||
|
|
||||||
|
This function should typically be called in response to the
|
||||||
|
messageReceived() signal. By default, that will happen
|
||||||
|
automatically, if an activation window has been set.
|
||||||
|
|
||||||
|
\sa setActivationWindow(), messageReceived(), initialize()
|
||||||
|
*/
|
||||||
|
void QtSingleApplication::activateWindow()
|
||||||
|
{
|
||||||
|
if (actWin) {
|
||||||
|
//actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized);
|
||||||
|
actWin->raise();
|
||||||
|
actWin->activateWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\fn void QtSingleApplication::messageReceived(const QString& message)
|
||||||
|
|
||||||
|
This signal is emitted when the current instance receives a \a
|
||||||
|
message from another instance of this application.
|
||||||
|
|
||||||
|
\sa sendMessage(), setActivationWindow(), activateWindow()
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\fn void QtSingleApplication::initialize(bool dummy = true)
|
||||||
|
|
||||||
|
\obsolete
|
||||||
|
*/
|
|
@ -0,0 +1,105 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||||
|
** Contact: http://www.qt-project.org/legal
|
||||||
|
**
|
||||||
|
** This file is part of the Qt Solutions component.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** You may use this file under the terms of the BSD license as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
|
||||||
|
** of its contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef QTSINGLEAPPLICATION_H
|
||||||
|
#define QTSINGLEAPPLICATION_H
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
|
class QtLocalPeer;
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
# if !defined(QT_QTSINGLEAPPLICATION_EXPORT) && !defined(QT_QTSINGLEAPPLICATION_IMPORT)
|
||||||
|
# define QT_QTSINGLEAPPLICATION_EXPORT
|
||||||
|
# elif defined(QT_QTSINGLEAPPLICATION_IMPORT)
|
||||||
|
# if defined(QT_QTSINGLEAPPLICATION_EXPORT)
|
||||||
|
# undef QT_QTSINGLEAPPLICATION_EXPORT
|
||||||
|
# endif
|
||||||
|
# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllimport)
|
||||||
|
# elif defined(QT_QTSINGLEAPPLICATION_EXPORT)
|
||||||
|
# undef QT_QTSINGLEAPPLICATION_EXPORT
|
||||||
|
# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllexport)
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
# define QT_QTSINGLEAPPLICATION_EXPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
QtSingleApplication(int &argc, char **argv, bool GUIenabled = true);
|
||||||
|
QtSingleApplication(const QString &id, int &argc, char **argv);
|
||||||
|
#if QT_VERSION < 0x050000
|
||||||
|
QtSingleApplication(int &argc, char **argv, Type type);
|
||||||
|
# if defined(Q_WS_X11)
|
||||||
|
QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
|
||||||
|
QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0);
|
||||||
|
QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
|
||||||
|
# endif // Q_WS_X11
|
||||||
|
#endif // QT_VERSION < 0x050000
|
||||||
|
|
||||||
|
bool isRunning();
|
||||||
|
QString id() const;
|
||||||
|
|
||||||
|
void setActivationWindow(QWidget* aw, bool activateOnMessage = true);
|
||||||
|
QWidget* activationWindow() const;
|
||||||
|
|
||||||
|
// Obsolete:
|
||||||
|
void initialize(bool dummy = true)
|
||||||
|
{ isRunning(); Q_UNUSED(dummy) }
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
bool sendMessage(const QString &message, int timeout = 5000);
|
||||||
|
void activateWindow();
|
||||||
|
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void messageReceived(const QString &message);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
void sysInit(const QString &appId = QString());
|
||||||
|
QtLocalPeer *peer;
|
||||||
|
QWidget *actWin;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QTSINGLEAPPLICATION_H
|
|
@ -0,0 +1,16 @@
|
||||||
|
INCLUDEPATH += $$PWD
|
||||||
|
DEPENDPATH += $$PWD
|
||||||
|
QT *= network
|
||||||
|
greaterThan(QT_MAJOR_VERSION, 4): QT *= widgets
|
||||||
|
|
||||||
|
qtsingleapplication-uselib:!qtsingleapplication-buildlib {
|
||||||
|
LIBS += -L$$QTSINGLEAPPLICATION_LIBDIR -l$$QTSINGLEAPPLICATION_LIBNAME
|
||||||
|
} else {
|
||||||
|
SOURCES += $$PWD/qtsingleapplication.cpp $$PWD/qtlocalpeer.cpp
|
||||||
|
HEADERS += $$PWD/qtsingleapplication.h $$PWD/qtlocalpeer.h
|
||||||
|
}
|
||||||
|
|
||||||
|
win32 {
|
||||||
|
contains(TEMPLATE, lib):contains(CONFIG, shared):DEFINES += QT_QTSINGLEAPPLICATION_EXPORT
|
||||||
|
else:qtsingleapplication-uselib:DEFINES += QT_QTSINGLEAPPLICATION_IMPORT
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||||
|
** Contact: http://www.qt-project.org/legal
|
||||||
|
**
|
||||||
|
** This file is part of the Qt Solutions component.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** You may use this file under the terms of the BSD license as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
|
||||||
|
** of its contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include "qtsinglecoreapplication.h"
|
||||||
|
#include "qtlocalpeer.h"
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\class QtSingleCoreApplication qtsinglecoreapplication.h
|
||||||
|
\brief A variant of the QtSingleApplication class for non-GUI applications.
|
||||||
|
|
||||||
|
This class is a variant of QtSingleApplication suited for use in
|
||||||
|
console (non-GUI) applications. It is an extension of
|
||||||
|
QCoreApplication (instead of QApplication). It does not require
|
||||||
|
the QtGui library.
|
||||||
|
|
||||||
|
The API and usage is identical to QtSingleApplication, except that
|
||||||
|
functions relating to the "activation window" are not present, for
|
||||||
|
obvious reasons. Please refer to the QtSingleApplication
|
||||||
|
documentation for explanation of the usage.
|
||||||
|
|
||||||
|
A QtSingleCoreApplication instance can communicate to a
|
||||||
|
QtSingleApplication instance if they share the same application
|
||||||
|
id. Hence, this class can be used to create a light-weight
|
||||||
|
command-line tool that sends commands to a GUI application.
|
||||||
|
|
||||||
|
\sa QtSingleApplication
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Creates a QtSingleCoreApplication object. The application identifier
|
||||||
|
will be QCoreApplication::applicationFilePath(). \a argc and \a
|
||||||
|
argv are passed on to the QCoreAppliation constructor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv)
|
||||||
|
: QCoreApplication(argc, argv)
|
||||||
|
{
|
||||||
|
peer = new QtLocalPeer(this);
|
||||||
|
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Creates a QtSingleCoreApplication object with the application
|
||||||
|
identifier \a appId. \a argc and \a argv are passed on to the
|
||||||
|
QCoreAppliation constructor.
|
||||||
|
*/
|
||||||
|
QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv)
|
||||||
|
: QCoreApplication(argc, argv)
|
||||||
|
{
|
||||||
|
peer = new QtLocalPeer(this, appId);
|
||||||
|
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns true if another instance of this application is running;
|
||||||
|
otherwise false.
|
||||||
|
|
||||||
|
This function does not find instances of this application that are
|
||||||
|
being run by a different user (on Windows: that are running in
|
||||||
|
another session).
|
||||||
|
|
||||||
|
\sa sendMessage()
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool QtSingleCoreApplication::isRunning()
|
||||||
|
{
|
||||||
|
return peer->isClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Tries to send the text \a message to the currently running
|
||||||
|
instance. The QtSingleCoreApplication object in the running instance
|
||||||
|
will emit the messageReceived() signal when it receives the
|
||||||
|
message.
|
||||||
|
|
||||||
|
This function returns true if the message has been sent to, and
|
||||||
|
processed by, the current instance. If there is no instance
|
||||||
|
currently running, or if the running instance fails to process the
|
||||||
|
message within \a timeout milliseconds, this function return false.
|
||||||
|
|
||||||
|
\sa isRunning(), messageReceived()
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout)
|
||||||
|
{
|
||||||
|
return peer->sendMessage(message, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the application identifier. Two processes with the same
|
||||||
|
identifier will be regarded as instances of the same application.
|
||||||
|
*/
|
||||||
|
|
||||||
|
QString QtSingleCoreApplication::id() const
|
||||||
|
{
|
||||||
|
return peer->applicationId();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\fn void QtSingleCoreApplication::messageReceived(const QString& message)
|
||||||
|
|
||||||
|
This signal is emitted when the current instance receives a \a
|
||||||
|
message from another instance of this application.
|
||||||
|
|
||||||
|
\sa sendMessage()
|
||||||
|
*/
|
|
@ -0,0 +1,71 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||||
|
** Contact: http://www.qt-project.org/legal
|
||||||
|
**
|
||||||
|
** This file is part of the Qt Solutions component.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** You may use this file under the terms of the BSD license as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
|
||||||
|
** of its contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef QTSINGLECOREAPPLICATION_H
|
||||||
|
#define QTSINGLECOREAPPLICATION_H
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
class QtLocalPeer;
|
||||||
|
|
||||||
|
class QtSingleCoreApplication : public QCoreApplication
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
QtSingleCoreApplication(int &argc, char **argv);
|
||||||
|
QtSingleCoreApplication(const QString &id, int &argc, char **argv);
|
||||||
|
|
||||||
|
bool isRunning();
|
||||||
|
QString id() const;
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
bool sendMessage(const QString &message, int timeout = 5000);
|
||||||
|
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void messageReceived(const QString &message);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
QtLocalPeer* peer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QTSINGLECOREAPPLICATION_H
|
|
@ -0,0 +1,10 @@
|
||||||
|
INCLUDEPATH += $$PWD
|
||||||
|
DEPENDPATH += $$PWD
|
||||||
|
HEADERS += $$PWD/qtsinglecoreapplication.h $$PWD/qtlocalpeer.h
|
||||||
|
SOURCES += $$PWD/qtsinglecoreapplication.cpp $$PWD/qtlocalpeer.cpp
|
||||||
|
|
||||||
|
QT *= network
|
||||||
|
|
||||||
|
win32:contains(TEMPLATE, lib):contains(CONFIG, shared) {
|
||||||
|
DEFINES += QT_QTSINGLECOREAPPLICATION_EXPORT=__declspec(dllexport)
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=ukui-search-service
|
||||||
|
Name[zh_CN]=搜索服务
|
||||||
|
GenericName=ukui-search-service
|
||||||
|
GenericName[zh_CN]=搜索服务
|
||||||
|
Comment=ukui-search-service
|
||||||
|
Comment[zh_CN]=搜索服务
|
||||||
|
Exec=/usr/bin/ukui-search-service %U
|
||||||
|
Type=Application
|
||||||
|
Icon=kylin-search
|
||||||
|
X-UKUI-AutoRestart=true
|
||||||
|
OnlyShowIn=UKUI
|
||||||
|
NoDisplay=true
|
||||||
|
X-UKUI-Autostart-Phase=Application
|
||||||
|
Terminal=false
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
usr/bin/ukui-search
|
usr/bin/ukui-search
|
||||||
|
usr/bin/ukui-search-service
|
||||||
etc/xdg/autostart/*.desktop
|
etc/xdg/autostart/*.desktop
|
||||||
usr/share/applications/*.desktop
|
usr/share/applications/*.desktop
|
||||||
frontend/.qm/*.qm usr/share/ukui-search/translations
|
frontend/.qm/*.qm usr/share/ukui-search/translations
|
||||||
|
|
|
@ -9,7 +9,7 @@ TEMPLATE = app
|
||||||
PKGCONFIG += gio-2.0 glib-2.0 gio-unix-2.0
|
PKGCONFIG += gio-2.0 glib-2.0 gio-unix-2.0
|
||||||
CONFIG += c++11 link_pkgconfig no_keywords lrelease
|
CONFIG += c++11 link_pkgconfig no_keywords lrelease
|
||||||
LIBS += -lxapian -lgsettings-qt -lquazip5 -lX11
|
LIBS += -lxapian -lgsettings-qt -lquazip5 -lX11
|
||||||
LIBS += -lukui-log4qt
|
#LIBS += -lukui-log4qt
|
||||||
# The following define makes your compiler emit warnings if you use
|
# The following define makes your compiler emit warnings if you use
|
||||||
# any Qt feature that has been marked deprecated (the exact warnings
|
# any Qt feature that has been marked deprecated (the exact warnings
|
||||||
# depend on your compiler). Please consult the documentation of the
|
# depend on your compiler). Please consult the documentation of the
|
||||||
|
@ -62,7 +62,7 @@ qm_files.path = /usr/share/ukui-search/translations/
|
||||||
qm_files.files = $$OUT_PWD/.qm/*.qm
|
qm_files.files = $$OUT_PWD/.qm/*.qm
|
||||||
|
|
||||||
schemes.path = /usr/share/glib-2.0/schemas/
|
schemes.path = /usr/share/glib-2.0/schemas/
|
||||||
schemes.files += ../data/org.ukui.search.data.gschema.xml ../data/org.ukui.log4qt.ukui-search.gschema.xml
|
schemes.files += ../data/org.ukui.log4qt.ukui-search.gschema.xml
|
||||||
|
|
||||||
INSTALLS += qm_files schemes
|
INSTALLS += qm_files schemes
|
||||||
|
|
||||||
|
@ -72,15 +72,5 @@ LIBS += -L$$OUT_PWD/../libchinese-segmentation -lchinese-segmentation \
|
||||||
INCLUDEPATH += $$PWD/../libchinese-segmentation
|
INCLUDEPATH += $$PWD/../libchinese-segmentation
|
||||||
DEPENDPATH += $$PWD/../libchinese-segmentation
|
DEPENDPATH += $$PWD/../libchinese-segmentation
|
||||||
|
|
||||||
INCLUDEPATH += $$PWD/../libsearch
|
|
||||||
DEPENDPATH += $$PWD/../libsearch
|
|
||||||
|
|
||||||
#DISTFILES += \
|
|
||||||
# ../data/ukui-search-menu.desktop \
|
|
||||||
# $$OUT_PWD/.qm/bo.qm \
|
|
||||||
# $$OUT_PWD/.qm/tr.qm \
|
|
||||||
# $$OUT_PWD/.qm/zh_CN.qm
|
|
||||||
|
|
||||||
DISTFILES += \
|
DISTFILES += \
|
||||||
../data/org.ukui.log4qt.ukui-search.gschema.xml \
|
../data/org.ukui.log4qt.ukui-search.gschema.xml \
|
||||||
../data/org.ukui.search.data.gschema.xml
|
|
||||||
|
|
|
@ -190,53 +190,12 @@ int main(int argc, char *argv[]) {
|
||||||
parser.addOptions({debugOption, showsearch});
|
parser.addOptions({debugOption, showsearch});
|
||||||
parser.process(app);
|
parser.process(app);
|
||||||
}*/
|
}*/
|
||||||
/*
|
|
||||||
// Create a fifo at ~/.config/org.ukui/ukui-search, the fifo is used to control the order of child processes' running.
|
|
||||||
QDir fifoDir = QDir(QDir::homePath()+"/.config/org.ukui/ukui-search");
|
|
||||||
if(!fifoDir.exists())
|
|
||||||
qDebug()<<"create fifo path"<<fifoDir.mkpath(fifoDir.absolutePath());
|
|
||||||
|
|
||||||
unlink(UKUI_SEARCH_PIPE_PATH);
|
|
||||||
int retval = mkfifo(UKUI_SEARCH_PIPE_PATH, 0777);
|
|
||||||
if(retval == -1)
|
|
||||||
{
|
|
||||||
qCritical()<<"creat fifo error!!";
|
|
||||||
syslog(LOG_ERR,"creat fifo error!!\n");
|
|
||||||
assert(false);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
qDebug()<<"create fifo success\n";
|
|
||||||
*/
|
|
||||||
// Set max_user_watches to a number which is enough big.
|
|
||||||
UkuiSearchQDBus usQDBus;
|
UkuiSearchQDBus usQDBus;
|
||||||
usQDBus.setInotifyMaxUserWatches();
|
usQDBus.setInotifyMaxUserWatches();
|
||||||
|
|
||||||
// load chinese character and pinyin file to a Map
|
// load chinese character and pinyin file to a Map
|
||||||
FileUtils::loadHanziTable("://index/pinyinWithoutTone.txt");
|
FileUtils::loadHanziTable("://index/pinyinWithoutTone.txt");
|
||||||
/*-------------InotyifyRefact Test Start---------------*/
|
|
||||||
// QTime t1 = QTime::currentTime();
|
|
||||||
// InotifyManagerRefact* imr = new InotifyManagerRefact("/home");
|
|
||||||
// imr->start();
|
|
||||||
// QTime t2 = QTime::currentTime();
|
|
||||||
// qDebug() << t1;
|
|
||||||
// qDebug() << t2;
|
|
||||||
/*-------------InotyifyRefact Test End-----------------*/
|
|
||||||
|
|
||||||
/*-------------content index Test Start---------------*/
|
|
||||||
// QTime t3 = QTime::currentTime();
|
|
||||||
// FileTypeFilter* ftf = new FileTypeFilter("/home");
|
|
||||||
// ftf->Test();
|
|
||||||
// QTime t4 = QTime::currentTime();
|
|
||||||
// delete ftf;
|
|
||||||
// ftf = nullptr;
|
|
||||||
// qDebug() << t3;
|
|
||||||
// qDebug() << t4;
|
|
||||||
/*-------------content index Test End-----------------*/
|
|
||||||
/*-------------文本搜索 Test start-----------------*/
|
|
||||||
// FileSearcher *search = new FileSearcher();
|
|
||||||
// search->onKeywordSearchContent("重要器官移植⑤白血病");
|
|
||||||
// search->onKeywordSearchContent("g,e,x");
|
|
||||||
/*-------------文本搜索 Test End-----------------*/
|
|
||||||
|
|
||||||
// Load translations
|
// Load translations
|
||||||
QTranslator translator;
|
QTranslator translator;
|
||||||
|
@ -268,10 +227,6 @@ int main(int argc, char *argv[]) {
|
||||||
MainWindow *w = new MainWindow;
|
MainWindow *w = new MainWindow;
|
||||||
UkuiSearchDbusServices dbusService(w);
|
UkuiSearchDbusServices dbusService(w);
|
||||||
qApp->setWindowIcon(QIcon::fromTheme("kylin-search"));
|
qApp->setWindowIcon(QIcon::fromTheme("kylin-search"));
|
||||||
centerToScreen(w);
|
|
||||||
|
|
||||||
//请务必在connect之后初始化mainwindow的Gsettings,为了保证gsettings第一次读取到的配置值能成功应用
|
|
||||||
w->initGsettings();
|
|
||||||
|
|
||||||
app.setActivationWindow(w);
|
app.setActivationWindow(w);
|
||||||
|
|
||||||
|
@ -283,22 +238,9 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
QObject::connect(&app, &QtSingleApplication::messageReceived, w, &MainWindow::bootOptionsFilter);
|
QObject::connect(&app, &QtSingleApplication::messageReceived, w, &MainWindow::bootOptionsFilter);
|
||||||
|
|
||||||
// Start app search thread
|
|
||||||
// AppMatch::getAppMatch()->start();
|
|
||||||
|
|
||||||
// NEW_TODO
|
// NEW_TODO
|
||||||
// Set threads which in global thread pool expiry time in 5ms, some prolems here
|
// Set threads which in global thread pool expiry time in 5ms, some prolems here
|
||||||
QThreadPool::globalInstance()->setExpiryTimeout(5);
|
QThreadPool::globalInstance()->setExpiryTimeout(5);
|
||||||
|
|
||||||
// NEW_TODO
|
|
||||||
// First insdex start, the parameter us useless, should remove the parameter
|
|
||||||
// FirstIndex fi("/home/zhangzihao/Desktop");
|
|
||||||
// fi.start();
|
|
||||||
|
|
||||||
// NEW_TODO
|
|
||||||
// Inotify index start, the parameter us useless, should remove the parameter
|
|
||||||
// InotifyIndex* ii = InotifyIndex::getInstance("/home");
|
|
||||||
// ii->start();
|
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||||
m_sys_tray_icon->show();
|
m_sys_tray_icon->show();
|
||||||
installEventFilter(this);
|
installEventFilter(this);
|
||||||
initConnections();
|
initConnections();
|
||||||
|
initGsettings();
|
||||||
//NEW_TODO, register plugins
|
//NEW_TODO, register plugins
|
||||||
// SearchPluginManager::getInstance()->registerPlugin(\\);
|
// SearchPluginManager::getInstance()->registerPlugin(\\);
|
||||||
// m_stackedWidget->setPlugins(SearchPluginManager::getInstance()->getPluginIds());
|
// m_stackedWidget->setPlugins(SearchPluginManager::getInstance()->getPluginIds());
|
||||||
|
@ -154,7 +155,7 @@ void MainWindow::initConnections()
|
||||||
{
|
{
|
||||||
connect(m_sys_tray_icon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconActivatedSlot);
|
connect(m_sys_tray_icon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconActivatedSlot);
|
||||||
QObject::connect(this, &MainWindow::searchMethodChanged, this, [ = ](FileUtils::SearchMethod sm) {
|
QObject::connect(this, &MainWindow::searchMethodChanged, this, [ = ](FileUtils::SearchMethod sm) {
|
||||||
this->m_searchMethodManager.searchMethod(sm);
|
FileUtils::searchMethod = sm;
|
||||||
});
|
});
|
||||||
connect(QApplication::primaryScreen(), &QScreen::geometryChanged, this, &MainWindow::monitorResolutionChange);
|
connect(QApplication::primaryScreen(), &QScreen::geometryChanged, this, &MainWindow::monitorResolutionChange);
|
||||||
connect(qApp, &QApplication::primaryScreenChanged, this, &MainWindow::primaryScreenChangedSlot);
|
connect(qApp, &QApplication::primaryScreenChanged, this, &MainWindow::primaryScreenChangedSlot);
|
||||||
|
@ -234,14 +235,12 @@ void MainWindow::setSearchMethodConfig(const bool &create_index, const bool &no_
|
||||||
if(create_index) {
|
if(create_index) {
|
||||||
if(m_search_gsettings && m_search_gsettings->keys().contains(SEARCH_METHOD_KEY)) {
|
if(m_search_gsettings && m_search_gsettings->keys().contains(SEARCH_METHOD_KEY)) {
|
||||||
m_search_gsettings->set(SEARCH_METHOD_KEY, true);
|
m_search_gsettings->set(SEARCH_METHOD_KEY, true);
|
||||||
} else {
|
}
|
||||||
//调用创建索引接口
|
|
||||||
Q_EMIT this->searchMethodChanged(FileUtils::SearchMethod::INDEXSEARCH);
|
Q_EMIT this->searchMethodChanged(FileUtils::SearchMethod::INDEXSEARCH);
|
||||||
//创建索引十秒后重新搜索一次(如果用户十秒内没有退出搜索界面且没有重新搜索)
|
//创建索引十秒后重新搜索一次(如果用户十秒内没有退出搜索界面且没有重新搜索)
|
||||||
m_researchTimer->start();
|
m_researchTimer->start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief MainWindow::settingsBtnClickedSlot 点击设置按钮的槽函数
|
* @brief MainWindow::settingsBtnClickedSlot 点击设置按钮的槽函数
|
||||||
|
@ -503,10 +502,7 @@ bool MainWindow::tryHideMainwindow()
|
||||||
*/
|
*/
|
||||||
void MainWindow::setSearchMethod(const bool &is_index_search) {
|
void MainWindow::setSearchMethod(const bool &is_index_search) {
|
||||||
if(is_index_search) {
|
if(is_index_search) {
|
||||||
//调用创建索引接口
|
|
||||||
Q_EMIT this->searchMethodChanged(FileUtils::SearchMethod::INDEXSEARCH);
|
Q_EMIT this->searchMethodChanged(FileUtils::SearchMethod::INDEXSEARCH);
|
||||||
//创建索引十秒后重新搜索一次(如果用户十秒内没有退出搜索界面且没有重新搜索)
|
|
||||||
m_researchTimer->start();
|
|
||||||
} else {
|
} else {
|
||||||
Q_EMIT this->searchMethodChanged(FileUtils::SearchMethod::DIRECTSEARCH);
|
Q_EMIT this->searchMethodChanged(FileUtils::SearchMethod::DIRECTSEARCH);
|
||||||
m_researchTimer->stop();
|
m_researchTimer->stop();
|
||||||
|
|
|
@ -141,8 +141,6 @@ private:
|
||||||
bool m_currentSearchAsked = false; //本次搜索是否已经询问过是否创建索引了
|
bool m_currentSearchAsked = false; //本次搜索是否已经询问过是否创建索引了
|
||||||
QGSettings * m_search_gsettings = nullptr;
|
QGSettings * m_search_gsettings = nullptr;
|
||||||
|
|
||||||
SearchMethodManager m_searchMethodManager;
|
|
||||||
|
|
||||||
void setSearchMethod(const bool&);
|
void setSearchMethod(const bool&);
|
||||||
double getTransparentData();
|
double getTransparentData();
|
||||||
void initTimer();
|
void initTimer();
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
#include <gio/gdesktopappinfo.h>
|
#include <gio/gdesktopappinfo.h>
|
||||||
#include <QDBusMessage>
|
#include <QDBusMessage>
|
||||||
#include <QDBusConnection>
|
#include <QDBusConnection>
|
||||||
|
#include <QDomDocument>
|
||||||
|
#include "gobject-template.h"
|
||||||
|
|
||||||
using namespace Zeeker;
|
using namespace Zeeker;
|
||||||
size_t FileUtils::_max_index_count = 0;
|
size_t FileUtils::_max_index_count = 0;
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QDomDocument>
|
|
||||||
#include <QQueue>
|
#include <QQueue>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
@ -55,7 +54,6 @@
|
||||||
#include <common.h>
|
#include <common.h>
|
||||||
|
|
||||||
#include "libsearch_global.h"
|
#include "libsearch_global.h"
|
||||||
#include "gobject-template.h"
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
//#define INITIAL_STATE 0
|
//#define INITIAL_STATE 0
|
||||||
//#define CREATING_INDEX 1
|
//#define CREATING_INDEX 1
|
||||||
|
|
|
@ -12,7 +12,7 @@ HEADERS += \
|
||||||
$$PWD/pending-file-queue.h \
|
$$PWD/pending-file-queue.h \
|
||||||
$$PWD/pending-file.h \
|
$$PWD/pending-file.h \
|
||||||
$$PWD/search-manager.h \
|
$$PWD/search-manager.h \
|
||||||
$$PWD/searchmethodmanager.h \
|
$$PWD/search-method-manager.h \
|
||||||
$$PWD/traverse_bfs.h \
|
$$PWD/traverse_bfs.h \
|
||||||
$$PWD/ukui-search-qdbus.h
|
$$PWD/ukui-search-qdbus.h
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ SOURCES += \
|
||||||
$$PWD/pending-file-queue.cpp \
|
$$PWD/pending-file-queue.cpp \
|
||||||
$$PWD/pending-file.cpp \
|
$$PWD/pending-file.cpp \
|
||||||
$$PWD/search-manager.cpp \
|
$$PWD/search-manager.cpp \
|
||||||
$$PWD/searchmethodmanager.cpp \
|
$$PWD/search-method-manager.cpp \
|
||||||
$$PWD/traverse_bfs.cpp \
|
$$PWD/traverse_bfs.cpp \
|
||||||
$$PWD/ukui-search-qdbus.cpp
|
$$PWD/ukui-search-qdbus.cpp
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,20 @@
|
||||||
#include "searchmethodmanager.h"
|
#include "search-method-manager.h"
|
||||||
using namespace Zeeker;
|
using namespace Zeeker;
|
||||||
|
static SearchMethodManager* global_instance = nullptr;
|
||||||
|
|
||||||
SearchMethodManager::SearchMethodManager()
|
SearchMethodManager::SearchMethodManager()
|
||||||
{
|
{
|
||||||
m_iw = InotifyWatch::getInstance(HOME_PATH);
|
m_iw = InotifyWatch::getInstance(HOME_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SearchMethodManager *SearchMethodManager::getInstance()
|
||||||
|
{
|
||||||
|
if(!global_instance) {
|
||||||
|
global_instance = new SearchMethodManager();
|
||||||
|
}
|
||||||
|
return global_instance;
|
||||||
|
}
|
||||||
|
|
||||||
void SearchMethodManager::searchMethod(FileUtils::SearchMethod sm) {
|
void SearchMethodManager::searchMethod(FileUtils::SearchMethod sm) {
|
||||||
qWarning() << "searchMethod start: " << static_cast<int>(sm);
|
qWarning() << "searchMethod start: " << static_cast<int>(sm);
|
||||||
if(FileUtils::SearchMethod::INDEXSEARCH == sm || FileUtils::SearchMethod::DIRECTSEARCH == sm) {
|
if(FileUtils::SearchMethod::INDEXSEARCH == sm || FileUtils::SearchMethod::DIRECTSEARCH == sm) {
|
|
@ -7,9 +7,10 @@
|
||||||
namespace Zeeker {
|
namespace Zeeker {
|
||||||
class SearchMethodManager {
|
class SearchMethodManager {
|
||||||
public:
|
public:
|
||||||
SearchMethodManager();
|
static SearchMethodManager *getInstance();
|
||||||
void searchMethod(FileUtils::SearchMethod sm);
|
void searchMethod(FileUtils::SearchMethod sm);
|
||||||
private:
|
private:
|
||||||
|
SearchMethodManager();
|
||||||
FirstIndex m_fi;
|
FirstIndex m_fi;
|
||||||
// InotifyIndex* m_ii;
|
// InotifyIndex* m_ii;
|
||||||
InotifyWatch *m_iw = nullptr;
|
InotifyWatch *m_iw = nullptr;
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
#include "plugininterface/search-plugin-iface.h"
|
#include "plugininterface/search-plugin-iface.h"
|
||||||
#include "plugininterface/data-queue.h"
|
#include "plugininterface/data-queue.h"
|
||||||
#include "index/searchmethodmanager.h"
|
#include "index/search-method-manager.h"
|
||||||
#include "index/first-index.h"
|
#include "index/first-index.h"
|
||||||
#include "index/ukui-search-qdbus.h"
|
#include "index/ukui-search-qdbus.h"
|
||||||
#include "index/search-manager.h"
|
#include "index/search-manager.h"
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QTime>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QDir>
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <QDebug>
|
||||||
|
#include "ukui-search-service.h"
|
||||||
|
using namespace Zeeker;
|
||||||
|
void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||||
|
{
|
||||||
|
QByteArray localMsg = msg.toLocal8Bit();
|
||||||
|
QByteArray currentTime = QTime::currentTime().toString().toLocal8Bit();
|
||||||
|
|
||||||
|
bool showDebug = true;
|
||||||
|
// QString logFilePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/ukui-search.log";
|
||||||
|
// QString logFilePath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.config/org.ukui/ukui-search/ukui-search.log";
|
||||||
|
QString logFilePath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.config/org.ukui/ukui-search-service.log";
|
||||||
|
if (!QFile::exists(logFilePath)) {
|
||||||
|
showDebug = false;
|
||||||
|
}
|
||||||
|
FILE *log_file = nullptr;
|
||||||
|
|
||||||
|
if (showDebug) {
|
||||||
|
log_file = fopen(logFilePath.toLocal8Bit().constData(), "a+");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *file = context.file ? context.file : "";
|
||||||
|
const char *function = context.function ? context.function : "";
|
||||||
|
switch (type) {
|
||||||
|
case QtDebugMsg:
|
||||||
|
if (!log_file) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fprintf(log_file, "Debug: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function);
|
||||||
|
break;
|
||||||
|
case QtInfoMsg:
|
||||||
|
fprintf(log_file? log_file: stdout, "Info: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function);
|
||||||
|
break;
|
||||||
|
case QtWarningMsg:
|
||||||
|
fprintf(log_file? log_file: stderr, "Warning: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function);
|
||||||
|
break;
|
||||||
|
case QtCriticalMsg:
|
||||||
|
fprintf(log_file? log_file: stderr, "Critical: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function);
|
||||||
|
break;
|
||||||
|
case QtFatalMsg:
|
||||||
|
fprintf(log_file? log_file: stderr, "Fatal: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log_file)
|
||||||
|
fclose(log_file);
|
||||||
|
}
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
char *p_home = NULL;
|
||||||
|
unsigned int i = 0;
|
||||||
|
while(p_home == NULL) {
|
||||||
|
::sleep(1);
|
||||||
|
++i;
|
||||||
|
p_home = getenv("HOME");
|
||||||
|
if(i % 5 == 0) {
|
||||||
|
qWarning() << "I can't find home! I'm done here!!";
|
||||||
|
printf("I can't find home! I'm done here!!");
|
||||||
|
syslog(LOG_ERR, "I can't find home! I'm done here!!\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p_home = NULL;
|
||||||
|
while(!QDir(QDir::homePath()).exists()) {
|
||||||
|
qWarning() << "Home is not exits!!";
|
||||||
|
printf("Home is not exits!!");
|
||||||
|
syslog(LOG_ERR, "Home is not exits!!\n");
|
||||||
|
::sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output log to file
|
||||||
|
qInstallMessageHandler(messageOutput);
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||||
|
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||||
|
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||||
|
#endif
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||||
|
QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||||
|
#endif
|
||||||
|
UkuiSearchService ukss(argc, argv, "ukui-search-service");
|
||||||
|
if (ukss.isRunning())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return ukss.exec();
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
#include <QDebug>
|
||||||
|
#include "ukui-search-service.h"
|
||||||
|
#define UKUI_SEARCH_SCHEMAS "org.ukui.search.settings"
|
||||||
|
#define SEARCH_METHOD_KEY "indexSearch"
|
||||||
|
using namespace Zeeker;
|
||||||
|
UkuiSearchService::UkuiSearchService(int &argc, char *argv[], const QString &applicationName): QtSingleApplication (applicationName, argc, argv)
|
||||||
|
{
|
||||||
|
qDebug()<<"ukui search service constructor start";
|
||||||
|
setApplicationVersion(QString("v%1").arg(VERSION));
|
||||||
|
setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
|
if (!this->isRunning()) {
|
||||||
|
connect(this, &QtSingleApplication::messageReceived, [=](QString msg) {
|
||||||
|
this->parseCmd(msg, true);
|
||||||
|
});
|
||||||
|
initGsettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
//parse cmd
|
||||||
|
qDebug()<<"parse cmd";
|
||||||
|
auto message = this->arguments().join(' ').toUtf8();
|
||||||
|
parseCmd(message, !isRunning());
|
||||||
|
|
||||||
|
qDebug()<<"ukui search service constructor end";
|
||||||
|
}
|
||||||
|
|
||||||
|
void UkuiSearchService::parseCmd(QString msg, bool isPrimary)
|
||||||
|
{
|
||||||
|
QCommandLineParser parser;
|
||||||
|
|
||||||
|
parser.addHelpOption();
|
||||||
|
parser.addVersionOption();
|
||||||
|
|
||||||
|
QCommandLineOption quitOption(QStringList()<<"q"<<"quit", tr("Stop service"));
|
||||||
|
parser.addOption(quitOption);
|
||||||
|
|
||||||
|
QCommandLineOption startOption(QStringList()<<"i"<<"index", tr("start or stop file index"), "option");
|
||||||
|
parser.addOption(startOption);
|
||||||
|
|
||||||
|
// QCommandLineOption statusOption(QStringList()<<"s"<<"status", tr("show status of file index service"));
|
||||||
|
// parser.addOption(statusOption);
|
||||||
|
|
||||||
|
if (isPrimary) {
|
||||||
|
const QStringList args = QString(msg).split(' ');
|
||||||
|
parser.process(args);
|
||||||
|
if(parser.isSet(startOption)) {
|
||||||
|
qDebug() << "options!!!!" << parser.value(startOption);
|
||||||
|
if(parser.value(startOption) == "start") {
|
||||||
|
indexServiceSwitch(true);
|
||||||
|
} else if (parser.value(startOption) == "stop") {
|
||||||
|
indexServiceSwitch(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if(parser.isSet(statusOption)) {
|
||||||
|
// }
|
||||||
|
if (parser.isSet(quitOption)) {
|
||||||
|
qApp->quit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (arguments().count() < 2) {
|
||||||
|
parser.showHelp();
|
||||||
|
}
|
||||||
|
parser.process(arguments());
|
||||||
|
sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UkuiSearchService::initGsettings()
|
||||||
|
{
|
||||||
|
const QByteArray id(UKUI_SEARCH_SCHEMAS);
|
||||||
|
if(QGSettings::isSchemaInstalled(id)) {
|
||||||
|
m_SearchGsettings = new QGSettings(id);
|
||||||
|
connect(m_SearchGsettings, &QGSettings::changed, this, [ = ](const QString &key) {
|
||||||
|
if(key == SEARCH_METHOD_KEY) {
|
||||||
|
setSearchMethodByGsettings();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(m_SearchGsettings->keys().contains(SEARCH_METHOD_KEY)) {
|
||||||
|
setSearchMethodByGsettings();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qWarning() << UKUI_SEARCH_SCHEMAS << " is not found!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UkuiSearchService::setSearchMethodByGsettings()
|
||||||
|
{
|
||||||
|
bool isIndexSearch = m_SearchGsettings->get(SEARCH_METHOD_KEY).toBool();
|
||||||
|
if(isIndexSearch) {
|
||||||
|
FileUtils::searchMethod = FileUtils::SearchMethod::INDEXSEARCH;
|
||||||
|
} else {
|
||||||
|
FileUtils::searchMethod = FileUtils::SearchMethod::DIRECTSEARCH;
|
||||||
|
}
|
||||||
|
SearchMethodManager::getInstance()->searchMethod(FileUtils::searchMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UkuiSearchService::indexServiceSwitch(bool startIndex)
|
||||||
|
{
|
||||||
|
if(startIndex) {
|
||||||
|
FileUtils::searchMethod = FileUtils::SearchMethod::INDEXSEARCH;
|
||||||
|
} else {
|
||||||
|
FileUtils::searchMethod = FileUtils::SearchMethod::DIRECTSEARCH;
|
||||||
|
}
|
||||||
|
SearchMethodManager::getInstance()->searchMethod(FileUtils::searchMethod);
|
||||||
|
|
||||||
|
const QByteArray id(UKUI_SEARCH_SCHEMAS);
|
||||||
|
if(QGSettings::isSchemaInstalled(id)) {
|
||||||
|
m_SearchGsettings = new QGSettings(id);
|
||||||
|
if(m_SearchGsettings->keys().contains(SEARCH_METHOD_KEY)) {
|
||||||
|
m_SearchGsettings->set(SEARCH_METHOD_KEY, startIndex);
|
||||||
|
} else {
|
||||||
|
qWarning() << SEARCH_METHOD_KEY << " is not found!";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qWarning() << UKUI_SEARCH_SCHEMAS << " is not found!";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
#ifndef UKUISEARCHSERVICE_H
|
||||||
|
#define UKUISEARCHSERVICE_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QCommandLineParser>
|
||||||
|
#include <QGSettings/QGSettings>
|
||||||
|
#include "qtsingleapplication.h"
|
||||||
|
#include "search-method-manager.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "file-utils.h"
|
||||||
|
namespace Zeeker {
|
||||||
|
|
||||||
|
class UkuiSearchService : public QtSingleApplication
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
UkuiSearchService(int &argc, char *argv[], const QString &applicationName = "ukui-search-service");
|
||||||
|
|
||||||
|
protected Q_SLOTS:
|
||||||
|
void parseCmd(QString msg, bool isPrimary);
|
||||||
|
private:
|
||||||
|
void initGsettings();
|
||||||
|
void setSearchMethodByGsettings();
|
||||||
|
void indexServiceSwitch(bool startIndex = true);
|
||||||
|
QGSettings *m_SearchGsettings;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif // UKUISEARCHSERVICE_H
|
|
@ -0,0 +1,45 @@
|
||||||
|
QT += core gui dbus
|
||||||
|
|
||||||
|
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||||
|
|
||||||
|
|
||||||
|
TARGET = ukui-search-service
|
||||||
|
VERSION = 1.0.0
|
||||||
|
DEFINES += VERSION='\\"$${VERSION}\\"'
|
||||||
|
CONFIG += c++11 link_pkgconfig no_keywords lrelease
|
||||||
|
PKGCONFIG += gsettings-qt
|
||||||
|
|
||||||
|
# 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). Please consult the documentation of the
|
||||||
|
# deprecated API in order 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
|
||||||
|
|
||||||
|
include(../libsearch/libukui-search-headers.pri)
|
||||||
|
include(../3rd-parties/qtsingleapplication/qtsingleapplication.pri)
|
||||||
|
|
||||||
|
LIBS += -L$$OUT_PWD/../libsearch -lukui-search
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
main.cpp \
|
||||||
|
ukui-search-service.cpp
|
||||||
|
|
||||||
|
schemes.path = /usr/share/glib-2.0/schemas/
|
||||||
|
schemes.files += ../data/org.ukui.search.data.gschema.xml
|
||||||
|
|
||||||
|
INSTALLS += schemes
|
||||||
|
|
||||||
|
target.path = /usr/bin
|
||||||
|
INSTALLS += target
|
||||||
|
|
||||||
|
desktop.path = /etc/xdg/autostart
|
||||||
|
desktop.files += ../data/ukui-search-service.desktop
|
||||||
|
INSTALLS += desktop
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
ukui-search-service.h
|
|
@ -3,7 +3,8 @@ SUBDIRS += $$PWD/libchinese-segmentation \
|
||||||
$$PWD/libsearch \
|
$$PWD/libsearch \
|
||||||
$$PWD/frontend \
|
$$PWD/frontend \
|
||||||
$$PWD/ukuisearch-systemdbus \
|
$$PWD/ukuisearch-systemdbus \
|
||||||
$$PWD/search-ukcc-plugin
|
$$PWD/search-ukcc-plugin \
|
||||||
|
$$PWD/ukui-search-service
|
||||||
# The following define makes your compiler emit warnings if you use
|
# The following define makes your compiler emit warnings if you use
|
||||||
# any Qt feature that has been marked deprecated (the exact warnings
|
# any Qt feature that has been marked deprecated (the exact warnings
|
||||||
# depend on your compiler). Please consult the documentation of the
|
# depend on your compiler). Please consult the documentation of the
|
||||||
|
@ -16,6 +17,7 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
||||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||||
|
|
||||||
libsearch.depends += libchinese-segmentation
|
libsearch.depends += libchinese-segmentation
|
||||||
|
ukui-search-service.depends += libsearch
|
||||||
#src.depends = libsearch
|
#src.depends = libsearch
|
||||||
frontend.depends = libsearch
|
frontend.depends = libsearch
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue