mirror of https://github.com/cutefishos/appmotor
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
361 lines
10 KiB
C++
361 lines
10 KiB
C++
/***************************************************************************
|
|
**
|
|
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
|
** All rights reserved.
|
|
** Contact: Nokia Corporation (directui@nokia.com)
|
|
**
|
|
** This file is part of applauncherd
|
|
**
|
|
** If you have questions regarding the use of this file, please contact
|
|
** Nokia at directui@nokia.com.
|
|
**
|
|
** This library is free software; you can redistribute it and/or
|
|
** modify it under the terms of the GNU Lesser General Public
|
|
** License version 2.1 as published by the Free Software Foundation
|
|
** and appearing in the file LICENSE.LGPL included in the packaging
|
|
** of this file.
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xatom.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <sys/stat.h>
|
|
|
|
#define DECL_EXPORT extern "C" __attribute__ ((__visibility__("default")))
|
|
|
|
namespace
|
|
{
|
|
int g_lockFd = -1;
|
|
const std::string LOCK_PATH_BASE("/var/run/single-instance-locks/");
|
|
const std::string LOCK_FILE_NAME("instance.lock");
|
|
}
|
|
|
|
//! Create a path and its components using mask 0777
|
|
static bool mkpath(const std::string & path)
|
|
{
|
|
for (unsigned int i = 0; i < path.size(); i++)
|
|
{
|
|
if ((i + 1 < path.size() && path[i + 1] == '/' && path[i] != '/') ||
|
|
(i + 1 == path.size()))
|
|
{
|
|
const std::string part = path.substr(0, i + 1);
|
|
if (mkdir(part.c_str(), 0777) != -1)
|
|
{
|
|
// chmod again, because permissions set by mkdir()
|
|
// are modified by umask
|
|
if (chmod(part.c_str(), 0777) == -1)
|
|
{
|
|
std::cerr << "ERROR!!: chmod() failed: " <<
|
|
strerror(errno) << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//! Print help.
|
|
static void printHelp()
|
|
{
|
|
printf("\nUsage: %s [options] [application]\n"
|
|
"Launch application as a single instance.\n"
|
|
"If given application is already running, its window is raised.\n"
|
|
"Give the full path to the application binary.\n\n"
|
|
"Options:\n"
|
|
" -h, --help Print this help message.\n\n"
|
|
"Example: %s /usr/bin/helloworld\n\n",
|
|
PROG_NAME_SINGLE_INSTANCE, PROG_NAME_SINGLE_INSTANCE);
|
|
}
|
|
|
|
//! Activate a window.
|
|
static int clientMsg(Display *disp, Window win, const char *msg,
|
|
unsigned long data0, unsigned long data1,
|
|
unsigned long data2, unsigned long data3,
|
|
unsigned long data4)
|
|
{
|
|
XEvent event;
|
|
long mask = SubstructureRedirectMask | SubstructureNotifyMask;
|
|
|
|
event.xclient.type = ClientMessage;
|
|
event.xclient.serial = 0;
|
|
event.xclient.send_event = True;
|
|
event.xclient.message_type = XInternAtom(disp, msg, False);
|
|
event.xclient.window = win;
|
|
event.xclient.format = 32;
|
|
event.xclient.data.l[0] = data0;
|
|
event.xclient.data.l[1] = data1;
|
|
event.xclient.data.l[2] = data2;
|
|
event.xclient.data.l[3] = data3;
|
|
event.xclient.data.l[4] = data4;
|
|
|
|
if (XSendEvent(disp, DefaultRootWindow(disp), False, mask, &event))
|
|
{
|
|
return EXIT_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "ERROR!!: Cannot send " << msg << " event." << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Return binary name assigned to a process id.
|
|
*
|
|
* Source for the binary name is /proc/[pid]/cmdline.
|
|
*
|
|
* \param pid The process id.
|
|
* \return Full path to the command on success, empty string on failure.
|
|
*/
|
|
static std::string binaryNameForPid(int pid)
|
|
{
|
|
std::string cmdLine;
|
|
|
|
std::stringstream ss;
|
|
ss << "/proc/" << pid << "/cmdline";
|
|
|
|
std::ifstream procFile;
|
|
procFile.open(ss.str().c_str());
|
|
if (procFile.is_open())
|
|
{
|
|
procFile >> cmdLine;
|
|
|
|
size_t nul = cmdLine.find_first_of('\0');
|
|
if (nul != std::string::npos)
|
|
{
|
|
cmdLine = cmdLine.substr(0, nul);
|
|
}
|
|
}
|
|
|
|
return cmdLine;
|
|
}
|
|
|
|
/*!
|
|
* \brief Return pid assigned to a window id.
|
|
*
|
|
* \param dpy The X11 display.
|
|
* \param window The window id.
|
|
* \return Pid on success, -1 on failure.
|
|
*/
|
|
static int windowPid(Display * dpy, Window window)
|
|
{
|
|
if (dpy)
|
|
{
|
|
static Atom pidAtom = XInternAtom(dpy, "_NET_WM_PID", False);
|
|
Atom type;
|
|
int format;
|
|
unsigned long nItems;
|
|
unsigned long bytesAfter;
|
|
unsigned char *propPID = 0;
|
|
|
|
// Get the PID of the window
|
|
if(XGetWindowProperty(dpy, window, pidAtom, 0, 1, False, XA_CARDINAL,
|
|
&type, &format, &nItems, &bytesAfter, &propPID) == Success)
|
|
{
|
|
if(propPID != 0)
|
|
{
|
|
// If the PID matches, add this window to the result set.
|
|
int pid = *(reinterpret_cast<int *>(propPID));
|
|
XFree(propPID);
|
|
return pid;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
//! Raise given window of the given display
|
|
void raiseWindow(Display *dpy, Window window)
|
|
{
|
|
clientMsg(dpy, window, "_NET_ACTIVE_WINDOW", 0, 0, 0, 0, 0);
|
|
XSync(dpy, False);
|
|
}
|
|
|
|
/*!
|
|
* \brief Return window id for the given binary.
|
|
*
|
|
* This method first fetches _NET_CLIENT_LIST for window candidates,
|
|
* and then finds the matching binary using /proc/[pid]/cmdline.
|
|
* Proc fs is primarily used, because we cannot trust WM_COMMAND window property
|
|
* in all cases. Anyhow we check also WM_COMMAND because proc fs does not work
|
|
* with scripts (python etc.).
|
|
*
|
|
* \param dpy The X11 display.
|
|
* \param binaryName Full path to the binary.
|
|
* \return Window id on success, 0 on failure.
|
|
*/
|
|
Window windowIdForBinary(Display *dpy, const char *binaryName)
|
|
{
|
|
Window retValue = 0;
|
|
if (dpy)
|
|
{
|
|
Atom netClientListAtom = XInternAtom(dpy, "_NET_CLIENT_LIST", False);
|
|
Atom type;
|
|
int format;
|
|
unsigned long nItems;
|
|
unsigned long bytesAfter;
|
|
unsigned char *prop = 0;
|
|
char **wmCommand = NULL;
|
|
int wmCommandCount = 0;
|
|
|
|
// Get the client list of the root window
|
|
if(XGetWindowProperty(dpy, XDefaultRootWindow(dpy), netClientListAtom,
|
|
0, 0x7fffffff, False, XA_WINDOW,
|
|
&type, &format, &nItems, &bytesAfter, &prop) == Success)
|
|
{
|
|
Window * clients = reinterpret_cast<Window *>(prop);
|
|
for (unsigned long i = 0; i < nItems; i++)
|
|
{
|
|
if (binaryNameForPid(windowPid(dpy, clients[i])) == binaryName ||
|
|
(XGetCommand (dpy, clients[i], &wmCommand, &wmCommandCount) != 0 &&
|
|
wmCommandCount > 0 && strcmp(wmCommand[0], binaryName) == 0))
|
|
{
|
|
retValue = clients[i];
|
|
break;
|
|
}
|
|
|
|
if (wmCommand) {
|
|
XFreeStringList(wmCommand);
|
|
}
|
|
}
|
|
|
|
XFree(prop);
|
|
}
|
|
}
|
|
|
|
return retValue;
|
|
}
|
|
|
|
|
|
// **** Export these functions when used as a library ****
|
|
extern "C"
|
|
{
|
|
/*!
|
|
* \brief Try to acquire a lock file.
|
|
*
|
|
* Tries to acquire a lock currently at
|
|
* /var/run/single-instance-locks/[binaryName]/instance.lock
|
|
*
|
|
* \param binaryName Full path to the binary.
|
|
* \return true if succeeded, false on failure.
|
|
*/
|
|
DECL_EXPORT bool lock(const char * binaryName)
|
|
{
|
|
std::string path(LOCK_PATH_BASE + binaryName);
|
|
if (!mkpath(path))
|
|
{
|
|
std::cerr << "ERROR!!: Couldn't create dir " <<
|
|
path << std::endl;
|
|
|
|
return false;
|
|
}
|
|
|
|
path += std::string("/") + LOCK_FILE_NAME;
|
|
|
|
struct flock fl;
|
|
|
|
fl.l_type = F_WRLCK;
|
|
fl.l_whence = SEEK_SET;
|
|
fl.l_start = 0;
|
|
fl.l_len = 1;
|
|
|
|
if((g_lockFd = open(path.c_str(), O_WRONLY | O_CREAT, 0666)) == -1)
|
|
{
|
|
std::cerr << "ERROR!!: Couldn't create/open lock file '" <<
|
|
path << "' : " << strerror(errno) << std::endl;
|
|
|
|
return false;
|
|
}
|
|
|
|
if(fcntl(g_lockFd, F_SETLK, &fl) == -1)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//! Close the lock file acquired by lock()
|
|
DECL_EXPORT void unlock()
|
|
{
|
|
if (g_lockFd != -1)
|
|
{
|
|
close(g_lockFd);
|
|
g_lockFd = -1;
|
|
}
|
|
}
|
|
|
|
//! Activate existing application
|
|
DECL_EXPORT bool activateExistingInstance(const char * binaryName)
|
|
{
|
|
if (Display * dpy = XOpenDisplay(NULL))
|
|
{
|
|
if (Window winId = windowIdForBinary(dpy, binaryName))
|
|
{
|
|
raiseWindow(dpy, winId);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "ERROR!!: Lock reserved but no window id for binary name found." << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "ERROR!!: Failed to open display!" << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//! The main function
|
|
int main(int argc, char **argv)
|
|
{
|
|
if (argc < 2)
|
|
{
|
|
printHelp();
|
|
return EXIT_FAILURE;
|
|
}
|
|
else if (std::string(argv[1]) == "--help" || std::string(argv[1]) == "-h")
|
|
{
|
|
printHelp();
|
|
return EXIT_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
if (!lock(argv[1]))
|
|
{
|
|
if (!activateExistingInstance(argv[1]))
|
|
{
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (execve(argv[1], argv + 1, environ) == -1)
|
|
{
|
|
std::cerr << "ERROR!!: Failed to exec binary '" <<
|
|
argv[1] << "' : " << strerror(errno) << std::endl;
|
|
unlock();
|
|
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|