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