1 //===========================================
2 // Lumina-DE source code
3 // Copyright (c) 2014, Ken Moore
4 // Available under the 3-clause BSD license
5 // See the LICENSE file for full details
6 //===========================================
7 #include "LuminaSingleApplication.h"
8 #include <QDir>
9 #include <QFile>
10 #include <QLocalSocket>
11 #include <QDebug>
12 #include <QX11Info>
13
14 #include <unistd.h> //for getuid()
15
LSingleApplication(int & argc,char ** argv,QString appname)16 LSingleApplication::LSingleApplication(int &argc, char **argv, QString appname) : QApplication(argc, argv){
17 //Load the proper translation systems
18 this->setAttribute(Qt::AA_UseHighDpiPixmaps);
19 if(appname!="lumina-desktop"){ cTrans = LUtils::LoadTranslation(this, appname); }//save the translator for later
20 //Initialize a couple convenience internal variables
21 cfile = getLockfileName(this->applicationName()); //do not allow masking the utility name
22 lockfile = new QLockFile(cfile+"-lock");
23 lockfile->setStaleLockTime(0); //long-lived processes
24 for(int i=1; i<argc; i++){
25 QString path = QString::fromLocal8Bit(argv[i]);
26 //do few quick conversions for relative paths and such as necessary
27 // (Remember: this is only used for secondary processes, not the primary)
28 if(path=="."){
29 //Insert the current working directory instead
30 path = QDir::currentPath();
31 }else{
32 if(!path.startsWith("/") && !path.startsWith("-") ){ path.prepend(QDir::currentPath()+"/"); }
33 }
34 inputlist << path;
35 }
36 isActive = isBypass = false;
37 lserver = 0;
38 //Now check for the manual CLI flag to bypass single-instance forwarding (if necessary)
39 if(inputlist.contains("-new-instance")){
40 isBypass = true;
41 inputlist.removeAll("-new-instance");
42 }
43 PerformLockChecks();
44 }
45
~LSingleApplication()46 LSingleApplication::~LSingleApplication(){
47 if(lserver != 0 && lockfile->isLocked() ){
48 //currently locked instance: remove the lock now
49 lserver->close();
50 QLocalServer::removeServer(cfile);
51 lockfile->unlock();
52 }
53 }
54
getLockfileName(QString appname)55 QString LSingleApplication::getLockfileName(QString appname){
56 QString path = QDir::tempPath()+"/.LSingleApp-%1-%2-%3";
57 QString username = QString::number(getuid());
58 QString display = QString(getenv("DISPLAY"));
59 if(display.startsWith(":")){ display.remove(0,1); }
60 display = display.section(".",0,0);
61 path = path.arg( username, appname, display );
62 return path;
63 }
64
removeLocks(QString appname)65 void LSingleApplication::removeLocks(QString appname){
66 QString path = getLockfileName(appname);
67 if(QFile::exists(path+"-lock")){
68 QFile::remove(path+"-lock");
69 }
70 if(QFile::exists(path)){
71 QFile::remove(path);
72 }
73 }
74
isPrimaryProcess()75 bool LSingleApplication::isPrimaryProcess(){
76 return (isActive || isBypass);
77 }
78
PerformLockChecks()79 void LSingleApplication::PerformLockChecks(){
80 bool primary = lockfile->tryLock();
81 //qDebug() << "Try Lock: " << primary;
82 if(!primary){
83 //Pre-existing lock - check it for validity
84 QString appname, hostname;
85 qint64 pid;
86 lockfile->getLockInfo(&pid, &hostname, &appname); //PID already exists if it gets this far, ignore hostname
87 //qDebug() << " - Lock Info:" << pid << hostname << appname;
88 if( appname!=this->applicationName() || !QFile::exists(cfile) ){
89 //Some other process has the same PID or the server does not exist - stale lock
90 qDebug() << " - Cleaning stale single-instance lock:";
91 if(lockfile->removeStaleLockFile() ){
92 if(QFile::exists(cfile)){ QLocalServer::removeServer(cfile); } //also remove stale socket/server file
93 }else{
94 qDebug() << " -- Could not remove lock file";
95 }
96 //Now re-try to create the lock
97 primary = lockfile->tryLock();
98 //qDebug() << " - Try Lock Again:" << primary;
99 }
100 }
101 if(primary || !QFile::exists(cfile) ){
102 //Create the server socket
103 //qDebug() << "Create Local Server";
104 if(QFile::exists(cfile)){ QLocalServer::removeServer(cfile); } //stale socket/server file
105 lserver = new QLocalServer(this);
106 connect(lserver, SIGNAL(newConnection()), this, SLOT(newInputsAvailable()) );
107 if( lserver->listen(cfile) ){
108 qDebug() << " - Created new single-instance lock";
109 lserver->setSocketOptions(QLocalServer::UserAccessOption);
110 //qDebug() << " - Success";
111 isActive = true;
112 }else{
113 qDebug() << " - WARNING: Could not create single-instance framework";
114 qDebug() << " - Falling back on standard application startup";
115 lockfile->unlock();
116 isActive = true;
117 }
118
119 }else if(!isBypass){
120 //forward the current inputs to the locked process for processing and exit
121 //Check the connection to the local server first
122 qDebug() << "Single-instance lock found";
123 QLocalSocket socket(this);
124 socket.connectToServer(cfile);
125 socket.waitForConnected();
126 if(!socket.isValid() || socket.state()!=QLocalSocket::ConnectedState){
127 //error - could not forward info for some reason
128 qDebug() << " - Could not connect to locking process: exiting...";
129 exit(1);
130 }
131
132 qDebug() << " - Forwarding inputs to locking process and closing down this instance...";
133 socket.write( inputlist.join("::::").toLocal8Bit() );
134 socket.waitForDisconnected(500); //max out at 1/2 second (only hits this if no inputs)
135 }
136
137 }
138
139 //New messages detected
newInputsAvailable()140 void LSingleApplication::newInputsAvailable(){
141 while(lserver->hasPendingConnections()){
142 QLocalSocket *sock = lserver->nextPendingConnection();
143 QByteArray bytes;
144 sock->waitForReadyRead();
145 while(sock->bytesAvailable() > 0){ //if(sock->waitForReadyRead()){
146 //qDebug() << "Info Available";
147 bytes.append( sock->readAll() );
148 }
149 sock->disconnectFromServer();
150 QStringList inputs = QString::fromLocal8Bit(bytes).split("::::");
151 //qDebug() << " - New Inputs Detected:" << inputs;
152 emit InputsAvailable(inputs);
153 }
154 }
155