mirror of
https://github.com/textmate/textmate.git
synced 2026-02-14 16:34:57 -05:00
265 lines
7.7 KiB
C++
265 lines
7.7 KiB
C++
#include "application.h"
|
||
#include "process.h"
|
||
#include <io/io.h>
|
||
#include <cf/cf.h>
|
||
#include <text/format.h>
|
||
#include <oak/compat.h>
|
||
#include <oak/debug.h>
|
||
|
||
OAK_DEBUG_VAR(Application);
|
||
|
||
namespace oak
|
||
{
|
||
static std::string _app_name = NULL_STR;
|
||
static std::string _app_path = NULL_STR;
|
||
static std::string _full_app_path = NULL_STR;
|
||
static std::string _pid_path = NULL_STR;
|
||
static std::string _support_path = NULL_STR;
|
||
|
||
static std::string process_name (pid_t pid)
|
||
{
|
||
struct kinfo_proc procInfo;
|
||
size_t length = sizeof(procInfo);
|
||
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid };
|
||
if(sysctl(mib, 4, &procInfo, &length, NULL, 0) == 0 && length > 0)
|
||
return procInfo.kp_proc.p_comm;
|
||
return NULL_STR;
|
||
}
|
||
|
||
static void disable_interactive_input ()
|
||
{
|
||
if(int fd = strtol(getenv("TM_INTERACTIVE_INPUT_ECHO_FD") ?: "0", NULL, 10))
|
||
{
|
||
close(fd);
|
||
unsetenv("TM_INTERACTIVE_INPUT_ECHO_FD");
|
||
}
|
||
if(int fd = strtol(getenv("TM_ERROR_FD") ?: "0", NULL, 10))
|
||
{
|
||
close(fd);
|
||
unsetenv("TM_ERROR_FD");
|
||
}
|
||
}
|
||
|
||
application_t::application_t (int argc, char const* argv[])
|
||
{
|
||
disable_interactive_input(); // this is only relevant when we launch TM from a TM command that uses interactive input (like make) — each relaunch will allocate two new file descriptors, so eventually we’ll run out of them
|
||
|
||
// When upgrading from r9110 or earlier.
|
||
static char const* LegacyVariables[] = { "RELAUNCH", "ACTIVATE", "DISABLE_UNTITLED_FILE" };
|
||
iterate(variable, LegacyVariables)
|
||
{
|
||
if(getenv(*variable))
|
||
{
|
||
setenv((std::string("OAK_") + *variable).c_str(), getenv(*variable), 1);
|
||
unsetenv(*variable);
|
||
}
|
||
}
|
||
|
||
_app_name = getenv("OAK_APP_NAME") ?: path::name(argv[0]);
|
||
_full_app_path = path::join(path::cwd(), argv[0]);
|
||
_app_path = _full_app_path;
|
||
_pid_path = support(((CFBundleGetMainBundle() && CFBundleGetIdentifier(CFBundleGetMainBundle())) ? cf::to_s(CFBundleGetIdentifier(CFBundleGetMainBundle())) : _app_name) + ".pid");
|
||
|
||
std::string const appBinary = path::join("Contents/MacOS", _app_name);
|
||
if(_app_path.size() > appBinary.size() && _app_path.find(appBinary) == _app_path.size() - appBinary.size())
|
||
_app_path.erase(_app_path.end() - appBinary.size(), _app_path.end());
|
||
|
||
std::string content = path::content(_pid_path);
|
||
if(content == NULL_STR && strcmp(getenv("OAK_RELAUNCH") ?: "0", "1") == 0)
|
||
content = path::content(support(_app_name + ".pid"));
|
||
|
||
long pid = content != NULL_STR ? strtol(content.c_str(), NULL, 10) : 0;
|
||
if(pid != 0 && process_name(pid) == _app_name)
|
||
{
|
||
int event_queue = kqueue();
|
||
|
||
struct kevent changeList = { (uintptr_t)pid, EVFILT_PROC, EV_ADD | EV_ENABLE | EV_ONESHOT, NOTE_EXIT, 0, NULL };
|
||
int res = kevent(event_queue, &changeList, 1, NULL /* event list */, 0 /* number of events */, NULL);
|
||
if(res == -1 && errno == ESRCH)
|
||
{
|
||
D(DBF_Application, bug("old instance no longer running\n"););
|
||
}
|
||
else if(res == 0)
|
||
{
|
||
if(char const* relaunch = getenv("OAK_RELAUNCH"))
|
||
{
|
||
D(DBF_Application, bug("%s is already running (pid %ld), OAK_RELAUNCH = %s\n", _app_name.c_str(), pid, relaunch););
|
||
kill(pid, strcmp(relaunch, "QUICK") == 0 ? SIGTERM : SIGINT);
|
||
|
||
struct kevent changed;
|
||
struct timespec timeout = { 30, 0 };
|
||
if(kevent(event_queue, NULL /* change list */, 0 /* number of changes */, &changed /* event list */, 1 /* number of events */, &timeout) == 1)
|
||
{
|
||
D(DBF_Application, bug("other instance terminated\n"););
|
||
ASSERTF((pid_t)changed.ident == pid, "pid %d, changed %d", pid, (pid_t)changed.ident);
|
||
CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
|
||
}
|
||
else
|
||
{
|
||
D(DBF_Application, bug("timeout expired, terminate\n"););
|
||
exit(1);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
D(DBF_Application, bug("%s is already running (pid %ld), OAK_RELAUNCH = NO\n", name().c_str(), pid););
|
||
ProcessSerialNumber psn;
|
||
if(noErr == GetProcessForPID(pid, &psn))
|
||
{
|
||
SetFrontProcess(&psn);
|
||
exit(0);
|
||
}
|
||
// procNotFound
|
||
}
|
||
}
|
||
close(event_queue);
|
||
}
|
||
|
||
create_pid_file();
|
||
|
||
if(getenv("OAK_ACTIVATE") || getenv("OAK_RELAUNCH"))
|
||
{
|
||
D(DBF_Application, bug("bring new instance to front\n"););
|
||
SetFrontProcess(&(ProcessSerialNumber){ 0, kCurrentProcess });
|
||
unsetenv("OAK_ACTIVATE");
|
||
unsetenv("OAK_RELAUNCH");
|
||
}
|
||
}
|
||
|
||
void application_t::create_pid_file ()
|
||
{
|
||
if(path::set_content(_pid_path, text::format("%d", getpid())))
|
||
{
|
||
atexit(&remove_pid_file);
|
||
}
|
||
else
|
||
{
|
||
D(DBF_Application, bug("open(\"%s\", O_WRONLY): %s\n", _pid_path.c_str(), strerror(errno)););
|
||
}
|
||
}
|
||
|
||
void application_t::remove_pid_file ()
|
||
{
|
||
D(DBF_Application, bug("unlink %s\n", _pid_path.c_str()););
|
||
unlink(_pid_path.c_str());
|
||
}
|
||
|
||
std::string application_t::name ()
|
||
{
|
||
if(_app_name == NULL_STR)
|
||
{
|
||
if(CFBundleRef mainBundle = CFBundleGetMainBundle())
|
||
{
|
||
if(CFTypeRef appName = CFBundleGetValueForInfoDictionaryKey(mainBundle, CFSTR("CFBundleName")))
|
||
{
|
||
if(CFGetTypeID(appName) == CFStringGetTypeID())
|
||
_app_name = cf::to_s((CFStringRef)appName);
|
||
}
|
||
}
|
||
}
|
||
return _app_name;
|
||
}
|
||
|
||
std::string application_t::path (std::string const& relativePath)
|
||
{
|
||
if(_app_path == NULL_STR)
|
||
{
|
||
if(CFBundleRef mainBundle = CFBundleGetMainBundle())
|
||
{
|
||
if(CFURLRef bundleURL = CFBundleCopyBundleURL(mainBundle))
|
||
{
|
||
if(CFStringRef bundlePath = CFURLCopyFileSystemPath(bundleURL, kCFURLPOSIXPathStyle))
|
||
{
|
||
_app_path = cf::to_s(bundlePath);
|
||
CFRelease(bundlePath);
|
||
}
|
||
CFRelease(bundleURL);
|
||
}
|
||
}
|
||
}
|
||
return path::join(_app_path, relativePath);
|
||
}
|
||
|
||
void application_t::set_name (std::string const& newName)
|
||
{
|
||
_app_name = newName;
|
||
}
|
||
|
||
void application_t::set_path (std::string const& newPath)
|
||
{
|
||
_app_path = newPath;
|
||
}
|
||
|
||
void application_t::set_support (std::string const& newPath)
|
||
{
|
||
_support_path = newPath;
|
||
}
|
||
|
||
std::string application_t::support (std::string const& relativePath)
|
||
{
|
||
if(_support_path == NULL_STR)
|
||
_support_path = path::join(path::join(path::home(), "Library/Application Support"), name());
|
||
return path::join(_support_path, relativePath);
|
||
}
|
||
|
||
std::string application_t::revision ()
|
||
{
|
||
if(CFBundleRef mainBundle = CFBundleGetMainBundle())
|
||
{
|
||
if(CFTypeRef bundleVersion = CFBundleGetValueForInfoDictionaryKey(mainBundle, CFSTR("CFBundleVersion")))
|
||
{
|
||
if(CFGetTypeID(bundleVersion) == CFStringGetTypeID())
|
||
return cf::to_s((CFStringRef)bundleVersion);
|
||
}
|
||
}
|
||
return "???";
|
||
}
|
||
|
||
static void* relaunch_thread (void* userdata)
|
||
{
|
||
oak::set_thread_name("application_t::relaunch");
|
||
pid_t pid = (pid_t)userdata;
|
||
|
||
int status = 0;
|
||
if(waitpid(pid, &status, 0) != pid)
|
||
fprintf(stderr, "*** relaunch failed: no process for pid %d.\n", pid);
|
||
else if(WIFSIGNALED(status))
|
||
fprintf(stderr, "*** relaunch failed: process terminated, %s.\n", strsignal(WTERMSIG(status)));
|
||
else if(!WIFEXITED(status))
|
||
fprintf(stderr, "*** relaunch failed: process terminated abnormally %d.\n", status);
|
||
else
|
||
fprintf(stderr, "*** relaunch failed: process terminated with status %d.\n", status);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
void application_t::relaunch ()
|
||
{
|
||
ASSERT(_full_app_path != NULL_STR);
|
||
D(DBF_Application, bug("%s\n", _full_app_path.c_str()););
|
||
|
||
create_pid_file(); // we create this during startup, but incase there was no support folder it would have failed
|
||
|
||
std::map<std::string, std::string> envMap = oak::basic_environment();
|
||
envMap["OAK_RELAUNCH"] = "1";
|
||
oak::c_array env(envMap);
|
||
|
||
pid_t pid = vfork();
|
||
if(pid == 0)
|
||
{
|
||
char const* argv[] = { _full_app_path.c_str(), NULL };
|
||
execve(argv[0], (char* const*)argv, env);
|
||
perror("relaunch");
|
||
_exit(-1);
|
||
}
|
||
else
|
||
{
|
||
pthread_t thread;
|
||
if(pthread_create(&thread, NULL, &relaunch_thread, (void*)pid) == 0)
|
||
pthread_detach(thread);
|
||
else perror("pthread_create()");
|
||
}
|
||
}
|
||
|
||
} /* oak */
|