Moved all IPC functions into the MUtilities libraries.
This commit is contained in:
parent
8b3e28a131
commit
563cc1c185
@ -24,6 +24,7 @@
|
|||||||
<ClCompile Include="src\Global.cpp" />
|
<ClCompile Include="src\Global.cpp" />
|
||||||
<ClCompile Include="src\GUI.cpp" />
|
<ClCompile Include="src\GUI.cpp" />
|
||||||
<ClCompile Include="src\GUI_Win32.cpp" />
|
<ClCompile Include="src\GUI_Win32.cpp" />
|
||||||
|
<ClCompile Include="src\IPCChannel.cpp" />
|
||||||
<ClCompile Include="src\JobObject_Win32.cpp" />
|
<ClCompile Include="src\JobObject_Win32.cpp" />
|
||||||
<ClCompile Include="src\KeccakHash.cpp" />
|
<ClCompile Include="src\KeccakHash.cpp" />
|
||||||
<ClCompile Include="src\OSSupport_Win32.cpp" />
|
<ClCompile Include="src\OSSupport_Win32.cpp" />
|
||||||
@ -39,6 +40,7 @@
|
|||||||
<ClInclude Include="include\MUtils\Exception.h" />
|
<ClInclude Include="include\MUtils\Exception.h" />
|
||||||
<ClInclude Include="include\MUtils\Global.h" />
|
<ClInclude Include="include\MUtils\Global.h" />
|
||||||
<ClInclude Include="include\MUtils\GUI.h" />
|
<ClInclude Include="include\MUtils\GUI.h" />
|
||||||
|
<ClInclude Include="include\MUtils\IPCChannel.h" />
|
||||||
<ClInclude Include="include\MUtils\JobObject.h" />
|
<ClInclude Include="include\MUtils\JobObject.h" />
|
||||||
<ClInclude Include="include\MUtils\KeccakHash.h" />
|
<ClInclude Include="include\MUtils\KeccakHash.h" />
|
||||||
<ClInclude Include="include\MUtils\OSSupport.h" />
|
<ClInclude Include="include\MUtils\OSSupport.h" />
|
||||||
|
@ -78,6 +78,9 @@
|
|||||||
<ClCompile Include="src\GUI_Win32.cpp">
|
<ClCompile Include="src\GUI_Win32.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="src\IPCChannel.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="src\CriticalSection_Win32.h">
|
<ClInclude Include="src\CriticalSection_Win32.h">
|
||||||
@ -131,6 +134,9 @@
|
|||||||
<ClInclude Include="include\MUtils\JobObject.h">
|
<ClInclude Include="include\MUtils\JobObject.h">
|
||||||
<Filter>Public Headers</Filter>
|
<Filter>Public Headers</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="include\MUtils\IPCChannel.h">
|
||||||
|
<Filter>Public Headers</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<CustomBuild Include="include\Mutils\UpdateChecker.h">
|
<CustomBuild Include="include\Mutils\UpdateChecker.h">
|
||||||
|
58
include/MUtils/IPCChannel.h
Normal file
58
include/MUtils/IPCChannel.h
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// MuldeR's Utilities for Qt
|
||||||
|
// Copyright (C) 2004-2014 LoRd_MuldeR <MuldeR2@GMX.de>
|
||||||
|
//
|
||||||
|
// This library is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU Lesser General Public
|
||||||
|
// License as published by the Free Software Foundation; either
|
||||||
|
// version 2.1 of the License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public
|
||||||
|
// License along with this library; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
//
|
||||||
|
// http://www.gnu.org/licenses/lgpl-2.1.txt
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <MUtils/Global.h>
|
||||||
|
|
||||||
|
namespace MUtils
|
||||||
|
{
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
IPC_RET_SUCCESS_MASTER = 0,
|
||||||
|
IPC_RET_SUCCESS_SLAVE = 1,
|
||||||
|
IPC_RET_ALREADY_INITIALIZED = 2,
|
||||||
|
IPC_RET_FAILURE = 3
|
||||||
|
}
|
||||||
|
ipc_result_t;
|
||||||
|
|
||||||
|
class MUTILS_API IPCChannel_Private;
|
||||||
|
|
||||||
|
class MUTILS_API IPCChannel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IPCChannel(const QString &applicationId, const QString &channelId);
|
||||||
|
~IPCChannel(void);
|
||||||
|
|
||||||
|
int initialize(void);
|
||||||
|
|
||||||
|
bool send(const unsigned int &command, const char *const message);
|
||||||
|
bool read(unsigned int &command, char *const message, const size_t &buffSize);
|
||||||
|
|
||||||
|
private:
|
||||||
|
IPCChannel(const IPCChannel&) : p(NULL) {}
|
||||||
|
IPCChannel &operator=(const IPCChannel&) { return *this; }
|
||||||
|
|
||||||
|
IPCChannel_Private *const p;
|
||||||
|
const QString m_applicationId;
|
||||||
|
const QString m_channelId;
|
||||||
|
};
|
||||||
|
}
|
@ -22,7 +22,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <MUtils/Global.h>
|
#include <MUtils/Global.h>
|
||||||
#include <QScopedPointer>
|
|
||||||
|
|
||||||
class QProcess;
|
class QProcess;
|
||||||
|
|
||||||
@ -40,6 +39,6 @@ namespace MUtils
|
|||||||
bool terminateJob(unsigned int exitCode);
|
bool terminateJob(unsigned int exitCode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QScopedPointer<JobObject_Private> p;
|
JobObject_Private *const p;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
337
src/IPCChannel.cpp
Normal file
337
src/IPCChannel.cpp
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// MuldeR's Utilities for Qt
|
||||||
|
// Copyright (C) 2004-2014 LoRd_MuldeR <MuldeR2@GMX.de>
|
||||||
|
//
|
||||||
|
// This library is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU Lesser General Public
|
||||||
|
// License as published by the Free Software Foundation; either
|
||||||
|
// version 2.1 of the License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public
|
||||||
|
// License along with this library; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
//
|
||||||
|
// http://www.gnu.org/licenses/lgpl-2.1.txt
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//MUtils
|
||||||
|
#include <MUtils/IPCChannel.h>
|
||||||
|
#include <MUtils/Exception.h>
|
||||||
|
|
||||||
|
//Qt includes
|
||||||
|
#include <QRegExp>
|
||||||
|
#include <QSharedMemory>
|
||||||
|
#include <QSystemSemaphore>
|
||||||
|
#include <QWriteLocker>
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TYPES
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
namespace MUtils
|
||||||
|
{
|
||||||
|
static const size_t IPC_SLOTS = 128;
|
||||||
|
static const size_t MAX_MESSAGE_LEN = 4096;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned int command;
|
||||||
|
unsigned int reserved_1;
|
||||||
|
unsigned int reserved_2;
|
||||||
|
char parameter[MAX_MESSAGE_LEN];
|
||||||
|
}
|
||||||
|
ipc_data_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned int pos_wr;
|
||||||
|
unsigned int pos_rd;
|
||||||
|
ipc_data_t data[IPC_SLOTS];
|
||||||
|
}
|
||||||
|
ipc_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// UTILITIES
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static inline QString ESCAPE(QString str)
|
||||||
|
{
|
||||||
|
return str.replace(QRegExp("[^A-Za-z0-9_]"), "_").toLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString MAKE_ID(const QString &applicationId, const QString &channelId, const QString &itemId)
|
||||||
|
{
|
||||||
|
return QString("ipc://mutilities.muldersoft.com:37402/%1/%2/%3").arg(ESCAPE(applicationId), ESCAPE(channelId), ESCAPE(itemId));
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// PRIVATE DATA
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
namespace MUtils
|
||||||
|
{
|
||||||
|
class IPCChannel_Private
|
||||||
|
{
|
||||||
|
friend class IPCChannel;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
volatile bool initialized;
|
||||||
|
QScopedPointer<QSharedMemory> sharedmem;
|
||||||
|
QScopedPointer<QSystemSemaphore> semaphore_rd;
|
||||||
|
QScopedPointer<QSystemSemaphore> semaphore_wr;
|
||||||
|
QReadWriteLock lock;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// CONSTRUCTOR & DESTRUCTOR
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
MUtils::IPCChannel::IPCChannel(const QString &applicationId, const QString &channelId)
|
||||||
|
:
|
||||||
|
p(new IPCChannel_Private()),
|
||||||
|
m_applicationId(applicationId),
|
||||||
|
m_channelId(channelId)
|
||||||
|
{
|
||||||
|
p->initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MUtils::IPCChannel::~IPCChannel(void)
|
||||||
|
{
|
||||||
|
if(p->initialized)
|
||||||
|
{
|
||||||
|
if(p->sharedmem->isAttached())
|
||||||
|
{
|
||||||
|
p->sharedmem->detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete p;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// INITIALIZATION
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
int MUtils::IPCChannel::initialize(void)
|
||||||
|
{
|
||||||
|
QWriteLocker writeLock(&p->lock);
|
||||||
|
|
||||||
|
if(p->initialized)
|
||||||
|
{
|
||||||
|
return IPC_RET_ALREADY_INITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->sharedmem.reset(new QSharedMemory(MAKE_ID(m_applicationId, m_channelId, "sharedmem"), NULL));
|
||||||
|
p->semaphore_rd.reset(new QSystemSemaphore(MAKE_ID(m_applicationId, m_channelId, "semaphore_rd"), 0));
|
||||||
|
p->semaphore_wr.reset(new QSystemSemaphore(MAKE_ID(m_applicationId, m_channelId, "semaphore_wr"), 0));
|
||||||
|
|
||||||
|
if(p->semaphore_rd->error() != QSystemSemaphore::NoError)
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->semaphore_rd->errorString();
|
||||||
|
qWarning("Failed to create system smaphore: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return IPC_RET_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(p->semaphore_wr->error() != QSystemSemaphore::NoError)
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->semaphore_wr->errorString();
|
||||||
|
qWarning("Failed to create system smaphore: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return IPC_RET_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!p->sharedmem->create(sizeof(ipc_t)))
|
||||||
|
{
|
||||||
|
if(p->sharedmem->error() == QSharedMemory::AlreadyExists)
|
||||||
|
{
|
||||||
|
if(!p->sharedmem->attach())
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->sharedmem->errorString();
|
||||||
|
qWarning("Failed to attach to shared memory: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return IPC_RET_FAILURE;
|
||||||
|
}
|
||||||
|
if(p->sharedmem->error() != QSharedMemory::NoError)
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->sharedmem->errorString();
|
||||||
|
qWarning("Failed to attach to shared memory: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return IPC_RET_FAILURE;
|
||||||
|
}
|
||||||
|
p->initialized = true;
|
||||||
|
return IPC_RET_SUCCESS_SLAVE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->sharedmem->errorString();
|
||||||
|
qWarning("Failed to create shared memory: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return IPC_RET_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(p->sharedmem->error() != QSharedMemory::NoError)
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->sharedmem->errorString();
|
||||||
|
qWarning("Failed to create shared memory: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return IPC_RET_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(void *const data = p->sharedmem->data())
|
||||||
|
{
|
||||||
|
memset(data, 0, sizeof(ipc_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!p->semaphore_wr->release(IPC_SLOTS))
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->semaphore_wr->errorString();
|
||||||
|
qWarning("Failed to release system semaphore: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return IPC_RET_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->initialized = true;
|
||||||
|
return IPC_RET_SUCCESS_MASTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// SEND MESSAGE
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
bool MUtils::IPCChannel::send(const unsigned int &command, const char *const message)
|
||||||
|
{
|
||||||
|
bool success = false;
|
||||||
|
QReadLocker readLock(&p->lock);
|
||||||
|
|
||||||
|
if(!p->initialized)
|
||||||
|
{
|
||||||
|
MUTILS_THROW("Shared memory for IPC not initialized yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ipc_data_t ipc_data;
|
||||||
|
memset(&ipc_data, 0, sizeof(ipc_data_t));
|
||||||
|
ipc_data.command = command;
|
||||||
|
|
||||||
|
if(message)
|
||||||
|
{
|
||||||
|
strncpy_s(ipc_data.parameter, MAX_MESSAGE_LEN, message, _TRUNCATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!p->semaphore_wr->acquire())
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->semaphore_wr->errorString();
|
||||||
|
qWarning("Failed to acquire system semaphore: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!p->sharedmem->lock())
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->sharedmem->errorString();
|
||||||
|
qWarning("Failed to lock shared memory: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ipc_t *const ptr = reinterpret_cast<ipc_t*>(p->sharedmem->data()))
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
memcpy(&ptr->data[ptr->pos_wr], &ipc_data, sizeof(ipc_data_t));
|
||||||
|
ptr->pos_wr = (ptr->pos_wr + 1) % IPC_SLOTS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning("Shared memory pointer is NULL -> unable to write data!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!p->sharedmem->unlock())
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->sharedmem->errorString();
|
||||||
|
qWarning("Failed to unlock shared memory: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!p->semaphore_rd->release())
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->semaphore_rd->errorString();
|
||||||
|
qWarning("Failed to acquire release semaphore: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// READ MESSAGE
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
bool MUtils::IPCChannel::read(unsigned int &command, char *const message, const size_t &buffSize)
|
||||||
|
{
|
||||||
|
bool success = false;
|
||||||
|
QReadLocker readLock(&p->lock);
|
||||||
|
|
||||||
|
command = 0;
|
||||||
|
if(message && (buffSize > 0))
|
||||||
|
{
|
||||||
|
message[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!p->initialized)
|
||||||
|
{
|
||||||
|
MUTILS_THROW("Shared memory for IPC not initialized yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ipc_data_t ipc_data;
|
||||||
|
memset(&ipc_data, 0, sizeof(ipc_data_t));
|
||||||
|
|
||||||
|
if(!p->semaphore_rd->acquire())
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->semaphore_rd->errorString();
|
||||||
|
qWarning("Failed to acquire system semaphore: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!p->sharedmem->lock())
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->sharedmem->errorString();
|
||||||
|
qWarning("Failed to lock shared memory: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ipc_t *const ptr = reinterpret_cast<ipc_t*>(p->sharedmem->data()))
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
memcpy(&ipc_data, &ptr->data[ptr->pos_rd], sizeof(ipc_data_t));
|
||||||
|
ptr->pos_rd = (ptr->pos_rd + 1) % IPC_SLOTS;
|
||||||
|
|
||||||
|
if(!(ipc_data.reserved_1 || ipc_data.reserved_2))
|
||||||
|
{
|
||||||
|
command = ipc_data.command;
|
||||||
|
strncpy_s(message, buffSize, ipc_data.parameter, _TRUNCATE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning("Malformed IPC message, will be ignored");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning("Shared memory pointer is NULL -> unable to write data!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!p->sharedmem->unlock())
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->sharedmem->errorString();
|
||||||
|
qWarning("Failed to unlock shared memory: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!p->semaphore_wr->release())
|
||||||
|
{
|
||||||
|
const QString errorMessage = p->semaphore_wr->errorString();
|
||||||
|
qWarning("Failed to acquire release semaphore: %s", MUTILS_UTF8(errorMessage));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
@ -84,6 +84,8 @@ MUtils::JobObject::~JobObject(void)
|
|||||||
CloseHandle(p->m_hJobObject);
|
CloseHandle(p->m_hJobObject);
|
||||||
p->m_hJobObject = NULL;
|
p->m_hJobObject = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete p;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MUtils::JobObject::addProcessToJob(const QProcess *proc)
|
bool MUtils::JobObject::addProcessToJob(const QProcess *proc)
|
||||||
|
Loading…
Reference in New Issue
Block a user