2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #if defined(__APPLE__) && !defined(__arm__)
25 #include <mach-o/dyld.h>
27 #include "XBMCHelper.h"
28 #include "PlatformDefs.h"
31 #include "dialogs/GUIDialogOK.h"
32 #include "dialogs/GUIDialogYesNo.h"
33 #include "utils/log.h"
35 #include "settings/lib/Setting.h"
36 #include "settings/Settings.h"
37 #include "utils/SystemInfo.h"
39 #include "threads/Atomics.h"
41 static long sg_singleton_lock_variable = 0;
42 XBMCHelper* XBMCHelper::smp_instance = 0;
44 #define XBMC_HELPER_PROGRAM "XBMCHelper"
45 #define SOFA_CONTROL_PROGRAM "Sofa Control"
46 #define XBMC_LAUNCH_PLIST "org.xbmc.helper.plist"
48 static int GetBSDProcessList(kinfo_proc **procList, size_t *procCount);
51 XBMCHelper::GetInstance()
53 CAtomicSpinLock lock(sg_singleton_lock_variable);
56 smp_instance = new XBMCHelper();
61 /////////////////////////////////////////////////////////////////////////////
62 XBMCHelper::XBMCHelper()
64 , m_mode(APPLE_REMOTE_DISABLED)
67 , m_errorStarting(false)
69 // Compute the XBMC_HOME path.
71 CUtil::GetHomePath(homePath);
72 m_homepath = homePath;
74 // Compute the helper filename.
75 m_helperFile = m_homepath + "/tools/darwin/runtime/";
76 m_helperFile += XBMC_HELPER_PROGRAM;
78 // Compute the local (pristine) launch agent filename.
79 m_launchAgentLocalFile = m_homepath + "/tools/darwin/runtime/";
80 m_launchAgentLocalFile += XBMC_LAUNCH_PLIST;
82 // Compute the install path for the launch agent.
83 // not to be confused with app home, this is user home
84 m_launchAgentInstallFile = getenv("HOME");
85 m_launchAgentInstallFile += "/Library/LaunchAgents/";
86 m_launchAgentInstallFile += XBMC_LAUNCH_PLIST;
88 // Compute the configuration file name.
89 m_configFile = getenv("HOME");
90 m_configFile += "/Library/Application Support/XBMC/XBMCHelper.conf";
93 /////////////////////////////////////////////////////////////////////////////
94 bool XBMCHelper::OnSettingChanging(const CSetting *setting)
99 const std::string &settingId = setting->GetId();
100 if (settingId == "input.appleremotemode")
102 int remoteMode = ((CSettingInt*)setting)->GetValue();
104 // if it's not disabled, start the event server or else apple remote won't work
105 if (remoteMode != APPLE_REMOTE_DISABLED)
107 // if starting the event server fails, we have to revert the change
108 if (!CSettings::Get().SetBool("services.esenabled", true))
112 // if XBMC helper is running, prompt user before effecting change
113 if (IsRunning() && GetMode() != remoteMode)
116 if (!CGUIDialogYesNo::ShowAndGetInput(13144, 13145, 13146, 13147, -1, -1, cancelled, 10000))
118 // reload configuration
122 // set new configuration.
126 if (ErrorStarting() == true)
128 // inform user about error
129 CGUIDialogOK::ShowAndGetInput(13620, 13621, 20022, 20022);
137 /////////////////////////////////////////////////////////////////////////////
138 void XBMCHelper::Start()
140 int pid = GetProcessPid(XBMC_HELPER_PROGRAM);
143 //printf("Asking helper to start.\n");
144 // use -x to have XBMCHelper read its configure file
145 std::string cmd = "\"" + m_helperFile + "\" -x &";
150 /////////////////////////////////////////////////////////////////////////////
151 void XBMCHelper::Stop()
155 int pid = GetProcessPid(XBMC_HELPER_PROGRAM);
158 printf("Asked to stop\n");
163 /////////////////////////////////////////////////////////////////////////////
164 void XBMCHelper::Configure()
166 int oldMode = m_mode;
167 int oldDelay = m_sequenceDelay;
168 int oldAlwaysOn = m_alwaysOn;
169 int oldPort = m_port;
171 // Read the new configuration.
172 m_errorStarting = false;
173 m_mode = CSettings::Get().GetInt("input.appleremotemode");
174 m_sequenceDelay = CSettings::Get().GetInt("input.appleremotesequencetime");
175 m_alwaysOn = CSettings::Get().GetBool("input.appleremotealwayson");
176 m_port = CSettings::Get().GetInt("services.esport");
179 // Don't let it enable if sofa control or remote buddy is around.
180 if (IsRemoteBuddyInstalled() || IsSofaControlRunning())
182 // If we were starting then remember error.
183 if (oldMode == APPLE_REMOTE_DISABLED && m_mode != APPLE_REMOTE_DISABLED)
184 m_errorStarting = true;
186 m_mode = APPLE_REMOTE_DISABLED;
187 CSettings::Get().SetInt("input.appleremotemode", APPLE_REMOTE_DISABLED);
190 // New configuration.
191 if (oldMode != m_mode || oldDelay != m_sequenceDelay || oldPort != m_port)
193 // Build a new config string.
194 std::string strConfig;
196 case APPLE_REMOTE_UNIVERSAL:
197 strConfig = "--universal ";
199 case APPLE_REMOTE_MULTIREMOTE:
200 strConfig = "--multiremote ";
205 std::stringstream strPort;
206 strPort << "--port " << m_port;
207 strConfig += strPort.str();
210 strConfig += "--verbose ";
213 sprintf(strDelay, "--timeout %d ", m_sequenceDelay);
214 strConfig += strDelay;
216 // Find out where we're running from.
217 char real_path[2*MAXPATHLEN];
218 char given_path[2*MAXPATHLEN];
219 uint32_t path_size = 2*MAXPATHLEN;
221 if (_NSGetExecutablePath(given_path, &path_size) == 0)
223 if (realpath(given_path, real_path) != NULL)
225 strConfig += "--appPath \"";
226 strConfig += real_path;
229 strConfig += "--appHome \"";
230 strConfig += m_homepath;
235 // Write the new configuration.
237 WriteFile(m_configFile.c_str(), strConfig);
239 // If process is running, kill -HUP to have it reload settings.
240 int pid = GetProcessPid(XBMC_HELPER_PROGRAM);
246 if (oldMode != APPLE_REMOTE_DISABLED && m_mode == APPLE_REMOTE_DISABLED)
253 if (oldMode == APPLE_REMOTE_DISABLED && m_mode != APPLE_REMOTE_DISABLED)
256 // Installation/uninstallation.
257 if (oldAlwaysOn == false && m_alwaysOn == true)
259 if (oldAlwaysOn == true && m_alwaysOn == false)
263 /////////////////////////////////////////////////////////////////////////////
264 void XBMCHelper::Install()
266 // Make sure directory exists.
267 std::string strDir = getenv("HOME");
268 strDir += "/Library/LaunchAgents";
269 CreateDirectory(strDir.c_str(), NULL);
272 std::string plistData = ReadFile(m_launchAgentLocalFile.c_str());
276 std::string launchd_args;
278 // Replace PATH with path to app.
279 int start = plistData.find("${PATH}");
280 plistData.replace(start, 7, m_helperFile.c_str(), m_helperFile.length());
282 // Replace ARG1 with a single argument, additional args
283 // will need ARG2, ARG3 added to plist.
285 start = plistData.find("${ARG1}");
286 plistData.replace(start, 7, launchd_args.c_str(), launchd_args.length());
289 WriteFile(m_launchAgentInstallFile.c_str(), plistData);
291 // Load it if not running already.
292 int pid = GetProcessPid(XBMC_HELPER_PROGRAM);
295 std::string cmd = "/bin/launchctl load ";
296 cmd += m_launchAgentInstallFile;
302 /////////////////////////////////////////////////////////////////////////////
303 void XBMCHelper::Uninstall()
305 // Call the unloader.
306 std::string cmd = "/bin/launchctl unload ";
307 cmd += m_launchAgentInstallFile;
310 //this also stops the helper, so restart it here again, if not disabled
311 if(m_mode != APPLE_REMOTE_DISABLED)
314 // Remove the plist file.
315 DeleteFile(m_launchAgentInstallFile.c_str());
318 /////////////////////////////////////////////////////////////////////////////
319 bool XBMCHelper::IsRunning()
321 return (GetProcessPid(XBMC_HELPER_PROGRAM)!=-1);
324 /////////////////////////////////////////////////////////////////////////////
325 bool XBMCHelper::IsRemoteBuddyInstalled()
328 // Check for existence of kext file.
329 return access("/System/Library/Extensions/RBIOKitHelper.kext", R_OK) != -1;
332 /////////////////////////////////////////////////////////////////////////////
333 bool XBMCHelper::IsSofaControlRunning()
336 // Check for a "Sofa Control" process running.
337 return GetProcessPid(SOFA_CONTROL_PROGRAM) != -1;
340 /////////////////////////////////////////////////////////////////////////////
341 std::string XBMCHelper::ReadFile(const char* fileName)
343 std::string ret = "";
349 // Get length of file:
350 is.seekg(0, std::ios::end);
351 int length = is.tellg();
352 is.seekg(0, std::ios::beg);
355 char* buffer = new char [length+1];
357 // Read data as a block:
358 is.read(buffer,length);
360 buffer[length] = '\0';
368 /////////////////////////////////////////////////////////////////////////////
369 void XBMCHelper::WriteFile(const char* fileName, const std::string& data)
371 std::ofstream out(fileName);
374 CLog::Log(LOGERROR, "XBMCHelper: Unable to open file '%s'", fileName);
378 // Write new configuration.
379 out << data << std::endl;
385 /////////////////////////////////////////////////////////////////////////////
386 int XBMCHelper::GetProcessPid(const char* strProgram)
388 kinfo_proc* mylist = 0;
392 GetBSDProcessList(&mylist, &mycount);
393 for (size_t k = 0; k < mycount && ret == -1; k++)
395 kinfo_proc *proc = NULL;
398 // Process names are at most sixteen characters long.
399 if (strncmp(proc->kp_proc.p_comm, strProgram, 16) == 0)
401 ret = proc->kp_proc.p_pid;
410 typedef struct kinfo_proc kinfo_proc;
412 // Returns a list of all BSD processes on the system. This routine
413 // allocates the list and puts it in *procList and a count of the
414 // number of entries in *procCount. You are responsible for freeing
415 // this list (use "free" from System framework).
416 // On success, the function returns 0.
417 // On error, the function returns a BSD errno value.
419 static int GetBSDProcessList(kinfo_proc **procList, size_t *procCount)
421 // example from http://developer.apple.com/qa/qa2001/qa1123.html
425 static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
427 // Declaring name as const requires us to cast it when passing it to
428 // sysctl because the prototype doesn't include the const modifier.
431 assert(procList != NULL);
432 assert(procCount != NULL);
436 // We start by calling sysctl with result == NULL and length == 0.
437 // That will succeed, and set length to the appropriate length.
438 // We then allocate a buffer of that size and call sysctl again
439 // with that buffer. If that succeeds, we're done. If that fails
440 // with ENOMEM, we have to throw away our buffer and loop. Note
441 // that the loop causes use to call sysctl with NULL again; this
442 // is necessary because the ENOMEM failure case sets length to
443 // the amount of data returned, not the amount of data that
444 // could have been returned.
450 assert(result == NULL);
452 // Call sysctl with a NULL buffer.
454 err = sysctl((int *) name, (sizeof(name) / sizeof(*name)) - 1, NULL,
459 // Allocate an appropriately sized buffer based on the results from the previous call.
462 result = (kinfo_proc*) malloc(length);
467 // Call sysctl again with the new buffer. If we get an ENOMEM
468 // error, toss away our buffer and start again.
472 err = sysctl((int *) name, (sizeof(name) / sizeof(*name)) - 1, result,
482 else if (err == ENOMEM)
484 assert(result != NULL);
490 } while (err == 0 && !done);
492 // Clean up and establish post conditions.
493 if (err != 0 && result != NULL)
501 *procCount = length / sizeof(kinfo_proc);
503 assert( (err == 0) == (*procList != NULL) );