Skip to content

Commit c75d47d

Browse files
committed
Add single application implementation from poniesbox. Should fix #14.
1 parent b950430 commit c75d47d

6 files changed

Lines changed: 426 additions & 4 deletions

File tree

qt-ponies.pro

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
QT += core gui
1+
QT += core gui network
22

33
lessThan(QT_MAJOR_VERSION, 5) {
44
DEFINES += IS_QT4
@@ -34,7 +34,8 @@ SOURCES += src/main.cpp \
3434
src/configwindow.cpp \
3535
src/csv_parser.cpp \
3636
src/interaction.cpp \
37-
src/debugwindow.cpp
37+
src/debugwindow.cpp \
38+
src/singleapplication.cpp
3839

3940
HEADERS += \
4041
src/pony.h \
@@ -44,7 +45,8 @@ HEADERS += \
4445
src/configwindow.h \
4546
src/csv_parser.h \
4647
src/interaction.h \
47-
src/debugwindow.h
48+
src/debugwindow.h \
49+
src/singleapplication.h
4850

4951
FORMS += \
5052
src/configwindow.ui \

src/configwindow.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,3 +666,12 @@ void ConfigWindow::show_debuglog()
666666
{
667667
ui_debug->show();
668668
}
669+
670+
void ConfigWindow::receiveFromInstance(const QString &message)
671+
{
672+
if ( message == "showConfigWindow" ) {
673+
show();
674+
raise();
675+
QApplication::setActiveWindow(this);
676+
}
677+
}

src/configwindow.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ private slots:
108108
void change_ponydata_directory();
109109
void update_interactions();
110110
void show_debuglog();
111+
void receiveFromInstance(const QString &message);
111112

112113
private:
113114
void reload_available_ponies();

src/main.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,19 @@
2929
#include "effect.h"
3030
#include "speak.h"
3131

32+
#include "singleapplication.h"
3233
#include "configwindow.h"
3334
#include "pony.h"
3435

36+
#define APPID "qt-ponies"
37+
3538
int main(int argc, char *argv[])
3639
{
3740
CSVParser::AddParseTypes("Behavior", Behavior::OptionTypes);
3841
CSVParser::AddParseTypes("Effect", Effect::OptionTypes);
3942
CSVParser::AddParseTypes("Speak", Speak::OptionTypes);
4043

41-
QApplication app(argc, argv);
44+
DSingleApplication app(APPID, argc, argv);
4245
QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath());
4346
QCoreApplication::setOrganizationName("qt-ponies");
4447
QCoreApplication::setApplicationName("qt-ponies");
@@ -51,14 +54,22 @@ int main(int argc, char *argv[])
5154
app.installTranslator(&translator);
5255

5356
app.setQuitOnLastWindowClosed(false);
57+
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
58+
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
5459
QSettings::setDefaultFormat(QSettings::IniFormat);
5560

61+
if ( app.isRunning() ) {
62+
app.sendMessage("showConfigWindow");
63+
return 0;
64+
}
65+
5666
QFile qss(":/styles/res/style.qss");
5767
qss.open(QFile::ReadOnly);
5868
app.setStyleSheet( QString::fromUtf8(qss.readAll()) );
5969
qss.close();
6070

6171
ConfigWindow config;
72+
QObject::connect(qApp, SIGNAL(messageReceived(QString)), &config, SLOT(receiveFromInstance(QString)));
6273

6374
qDebug() << "Locale:" << locale;
6475

src/singleapplication.cpp

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
#include "singleapplication.h"
2+
3+
DSingleApplication::DSingleApplication(const QString &id, int &argc, char *argv[]):
4+
QApplication(argc, argv) {
5+
other_instance_running = false;
6+
app_id = id;
7+
port = d_unique_port_start;
8+
9+
tcp_server = new DTalker(app_id, this);
10+
tcp_socket = NULL;
11+
12+
// start at d_unique_port_start and go until find a free port or a port
13+
// that answers correctly
14+
other_instance_running = false;
15+
16+
DPortChecker checker(app_id, d_unique_port_start, this);
17+
18+
// first go over the range of ports and check for other instance
19+
// if not, then listen on the forst port available
20+
DPortList ports;
21+
22+
while ( port <= d_unique_port_finish ) {
23+
24+
// here check if the stuff running on port is our instance if not procede
25+
checker.check( port );
26+
checker.wait();
27+
DPortChecker::PortStatus port_status = checker.status();
28+
29+
if ( port_status == DPortChecker::us ) {
30+
other_instance_running = true;
31+
// here we have to connect to other instance to send messages
32+
tcp_socket = checker.transferSocketOwnership();
33+
return;
34+
}
35+
36+
DPortInfo pi(port, checker.status() == DPortChecker::free);
37+
ports << pi;
38+
++port;
39+
}
40+
41+
port = ports.firstFreePort();
42+
43+
// other instance is not running in the range and there's available port
44+
if ( port == -1 )
45+
return;
46+
47+
// this port is free and current instance is in listening mode
48+
bool listening = tcp_server->listen( QHostAddress::LocalHost, port );
49+
if ( listening )
50+
connect(tcp_server, SIGNAL(messageReceived(const QString&)), this, SLOT(onClientMessage(const QString&)));
51+
}
52+
53+
DSingleApplication::~DSingleApplication() {
54+
if ( tcp_server )
55+
delete(tcp_server);
56+
if ( tcp_socket )
57+
delete(tcp_socket);
58+
}
59+
60+
QString DSingleApplication::id() {
61+
return app_id;
62+
}
63+
64+
bool DSingleApplication::isRunning () {
65+
// may require some checks here
66+
return other_instance_running;
67+
}
68+
69+
bool DSingleApplication::sendMessage(const QString &message) {
70+
if ( ! other_instance_running )
71+
return false;
72+
if ( ! tcp_socket )
73+
return false;
74+
if ( tcp_socket->state() != QAbstractSocket::ConnectedState )
75+
return false;
76+
77+
QByteArray block;
78+
QDataStream out(&block, QIODevice::WriteOnly);
79+
out.setVersion(out.version()); // set to the current Qt version
80+
81+
QString msg = app_id + ":" + message;
82+
out << (quint32) msg.size();
83+
out << msg;
84+
85+
tcp_socket->write(block);
86+
tcp_socket->flush();
87+
tcp_socket->waitForBytesWritten(d_timeout_try_write);
88+
89+
return true;
90+
}
91+
92+
void DSingleApplication::onClientMessage(const QString &message) {
93+
emit messageReceived(message);
94+
}
95+
96+
// This class is used to check specific port if it has an instance of this app.
97+
98+
DPortChecker::DPortChecker(const QString &id, int port, QObject *parent): QThread(parent) {
99+
app_id = QString(id);
100+
tcp_socket = NULL;
101+
this->port = port;
102+
result = DPortChecker::free;
103+
}
104+
105+
DPortChecker::~DPortChecker() {
106+
if ( tcp_socket )
107+
delete(tcp_socket);
108+
}
109+
110+
DPortChecker::PortStatus DPortChecker::status() {
111+
return result;
112+
}
113+
114+
void DPortChecker::check(int port) {
115+
this->port = port;
116+
start();
117+
}
118+
119+
void DPortChecker::run() {
120+
result = DPortChecker::free;
121+
122+
if ( tcp_socket == NULL )
123+
tcp_socket = new QTcpSocket();
124+
125+
tcp_socket->connectToHost(QHostAddress(QHostAddress::LocalHost), port);
126+
if ( ! tcp_socket->waitForConnected(d_timeout_try_connect) ) {
127+
tcp_socket->abort();
128+
return;
129+
}
130+
131+
result = DPortChecker::others;
132+
if ( ! tcp_socket->waitForReadyRead(d_timeout_try_read) ) {
133+
tcp_socket->abort();
134+
return;
135+
}
136+
137+
// now compare received bytes with app_id
138+
QDataStream in(tcp_socket);
139+
in.setVersion(in.version()); // set to the current Qt version
140+
141+
if ( tcp_socket->bytesAvailable() > 0 ) {
142+
QString msgString;
143+
in >> msgString;
144+
if ( msgString.size() <= 1 ) {
145+
tcp_socket->abort();
146+
return;
147+
}
148+
int s = qMin(msgString.size(), app_id.size());
149+
if ( QString::compare(msgString.left(s), app_id.left(s)) == 0 )
150+
result = DPortChecker::us;
151+
}
152+
}
153+
154+
QTcpSocket *DPortChecker::transferSocketOwnership() {
155+
QTcpSocket *tmp = tcp_socket;
156+
tcp_socket = NULL;
157+
return tmp;
158+
}
159+
160+
// This is a server responsible to talking to incoming connections.
161+
162+
DTalker::DTalker(const QString &id, QObject *parent): QTcpServer(parent) {
163+
app_id = QString(id);
164+
}
165+
166+
void DTalker::incomingConnection(int socket_descriptor ) {
167+
DListner *listner = new DListner(app_id, socket_descriptor, this);
168+
connect(listner, SIGNAL(messageReceived(const QString&)), this, SLOT(onClientMessage(const QString&)));
169+
connect(listner, SIGNAL(finished()), listner, SLOT(deleteLater()));
170+
listner->start();
171+
}
172+
173+
void DTalker::onClientMessage(const QString &message) {
174+
emit messageReceived( message );
175+
}
176+
177+
// This thread is used to communicate with connected client.
178+
179+
DListner::DListner(const QString &id, int _socket_descriptor, QObject *parent): QThread(parent) {
180+
app_id = QString(id);
181+
socket_descriptor = _socket_descriptor;
182+
block_size = 0;
183+
}
184+
185+
void DListner::run() {
186+
QTcpSocket tcp_socket;
187+
if ( ! tcp_socket.setSocketDescriptor(socket_descriptor) )
188+
return;
189+
190+
// send app_id to client
191+
QByteArray block;
192+
QDataStream out(&block, QIODevice::WriteOnly);
193+
out.setVersion(out.version()); // set to the current Qt version instead
194+
out << app_id;
195+
tcp_socket.write(block);
196+
//waitForBytesWritten ( int msecs )
197+
198+
while ( true ) {
199+
if ( tcp_socket.state() != QAbstractSocket::ConnectedState )
200+
return;
201+
tcp_socket.waitForReadyRead(-1);
202+
read(&tcp_socket);
203+
}
204+
}
205+
206+
void DListner::read( QTcpSocket *tcp_socket ) {
207+
if (tcp_socket == NULL)
208+
return;
209+
if ( tcp_socket->state() != QAbstractSocket::ConnectedState )
210+
return;
211+
212+
QDataStream in(tcp_socket);
213+
in.setVersion(in.version()); // set to the current Qt version instead
214+
215+
if ( block_size == 0 ) {
216+
if ( tcp_socket->bytesAvailable() < (int) sizeof(quint32) )
217+
return;
218+
in >> block_size;
219+
}
220+
if ( tcp_socket->bytesAvailable() < block_size )
221+
return;
222+
QString msgString;
223+
in >> msgString;
224+
225+
// if header matches, trim and emit
226+
QString magic = app_id + ":";
227+
if ( QString::compare( msgString.left(magic.size()), magic ) == 0)
228+
emit messageReceived( msgString.remove(0, magic.size()) );
229+
230+
block_size = 0;
231+
if ( tcp_socket->bytesAvailable() > 0 )
232+
read(tcp_socket);
233+
}
234+
235+
int DPortList::firstFreePort() {
236+
DPortList::iterator it = this->begin();
237+
while ( it < this->end() ) {
238+
if ( it->free )
239+
return it->port;
240+
++it;
241+
}
242+
return -1;
243+
}
244+
245+
bool DPortList::freePortAvailable() {
246+
int p = firstFreePort();
247+
return p != -1;
248+
}

0 commit comments

Comments
 (0)