Merge pull request #4775 from jmarshallnz/empty_episode_playcount
[vuplus_xbmc] / xbmc / osx / XBMCHelper.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 #if defined(__APPLE__) && !defined(__arm__)
22 #include <fstream>
23 #include <signal.h>
24 #include <sstream>
25 #include <mach-o/dyld.h>
26
27 #include "XBMCHelper.h"
28 #include "PlatformDefs.h"
29 #include "Util.h"
30
31 #include "dialogs/GUIDialogOK.h"
32 #include "dialogs/GUIDialogYesNo.h"
33 #include "utils/log.h"
34 #include "system.h"
35 #include "settings/lib/Setting.h"
36 #include "settings/Settings.h"
37 #include "utils/SystemInfo.h"
38
39 #include "threads/Atomics.h"
40
41 static long sg_singleton_lock_variable = 0;
42 XBMCHelper* XBMCHelper::smp_instance = 0;
43
44 #define XBMC_HELPER_PROGRAM "XBMCHelper"
45 #define SOFA_CONTROL_PROGRAM "Sofa Control"
46 #define XBMC_LAUNCH_PLIST "org.xbmc.helper.plist"
47
48 static int GetBSDProcessList(kinfo_proc **procList, size_t *procCount);
49
50 XBMCHelper&
51 XBMCHelper::GetInstance()
52 {
53   CAtomicSpinLock lock(sg_singleton_lock_variable);
54   if( ! smp_instance )
55   {
56     smp_instance = new XBMCHelper();
57   }
58   return *smp_instance;
59 }
60
61 /////////////////////////////////////////////////////////////////////////////
62 XBMCHelper::XBMCHelper()
63   : m_alwaysOn(false)
64   , m_mode(APPLE_REMOTE_DISABLED)
65   , m_sequenceDelay(0)
66   , m_port(0)
67   , m_errorStarting(false)
68 {
69   // Compute the XBMC_HOME path.
70   CStdString homePath;
71   CUtil::GetHomePath(homePath);
72   m_homepath = homePath;
73
74   // Compute the helper filename.
75   m_helperFile = m_homepath + "/tools/darwin/runtime/";
76   m_helperFile += XBMC_HELPER_PROGRAM;
77   
78   // Compute the local (pristine) launch agent filename.
79   m_launchAgentLocalFile = m_homepath + "/tools/darwin/runtime/";
80   m_launchAgentLocalFile += XBMC_LAUNCH_PLIST;
81
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;
87
88   // Compute the configuration file name.
89   m_configFile = getenv("HOME");
90   m_configFile += "/Library/Application Support/XBMC/XBMCHelper.conf";
91 }
92
93 /////////////////////////////////////////////////////////////////////////////
94 bool XBMCHelper::OnSettingChanging(const CSetting *setting)
95 {
96   if (setting == NULL)
97     return false;
98
99   const std::string &settingId = setting->GetId();
100   if (settingId == "input.appleremotemode")
101   {
102     int remoteMode = ((CSettingInt*)setting)->GetValue();
103
104     // if it's not disabled, start the event server or else apple remote won't work
105     if (remoteMode != APPLE_REMOTE_DISABLED)
106     {
107       // if starting the event server fails, we have to revert the change
108       if (!CSettings::Get().SetBool("services.esenabled", true))
109         return false;
110     }
111
112     // if XBMC helper is running, prompt user before effecting change
113     if (IsRunning() && GetMode() != remoteMode)
114     {
115       bool cancelled;
116       if (!CGUIDialogYesNo::ShowAndGetInput(13144, 13145, 13146, 13147, -1, -1, cancelled, 10000))
117         return false;
118       // reload configuration
119       else
120         Configure();
121     }
122     // set new configuration.
123     else
124       Configure();
125
126     if (ErrorStarting() == true)
127     {
128       // inform user about error
129       CGUIDialogOK::ShowAndGetInput(13620, 13621, 20022, 20022);
130       return false;
131     }
132   }
133
134   return true;
135 }
136
137 /////////////////////////////////////////////////////////////////////////////
138 void XBMCHelper::Start()
139 {
140   int pid = GetProcessPid(XBMC_HELPER_PROGRAM);
141   if (pid == -1)
142   {
143     //printf("Asking helper to start.\n");
144     // use -x to have XBMCHelper read its configure file
145     std::string cmd = "\"" + m_helperFile + "\" -x &";
146     system(cmd.c_str());
147   }
148 }
149
150 /////////////////////////////////////////////////////////////////////////////
151 void XBMCHelper::Stop()
152 {
153
154   // Kill the process.
155   int pid = GetProcessPid(XBMC_HELPER_PROGRAM);
156   if (pid != -1)
157   {
158     printf("Asked to stop\n");
159     kill(pid, SIGKILL);
160   }
161 }
162
163 /////////////////////////////////////////////////////////////////////////////
164 void XBMCHelper::Configure()
165 {
166   int oldMode = m_mode;
167   int oldDelay = m_sequenceDelay;
168   int oldAlwaysOn = m_alwaysOn;
169   int oldPort = m_port;
170
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");
177
178
179   // Don't let it enable if sofa control or remote buddy is around.
180   if (IsRemoteBuddyInstalled() || IsSofaControlRunning())
181   {
182     // If we were starting then remember error.
183     if (oldMode == APPLE_REMOTE_DISABLED && m_mode != APPLE_REMOTE_DISABLED)
184       m_errorStarting = true;
185
186     m_mode = APPLE_REMOTE_DISABLED;
187     CSettings::Get().SetInt("input.appleremotemode", APPLE_REMOTE_DISABLED);
188   }
189
190   // New configuration.
191   if (oldMode != m_mode || oldDelay != m_sequenceDelay || oldPort != m_port)
192   {
193     // Build a new config string.
194     std::string strConfig;
195     switch (m_mode) {
196       case APPLE_REMOTE_UNIVERSAL:
197         strConfig = "--universal ";
198         break;
199       case APPLE_REMOTE_MULTIREMOTE:
200         strConfig = "--multiremote ";
201         break;
202       default:
203         break;
204     }
205     std::stringstream strPort;
206     strPort << "--port " << m_port;
207     strConfig += strPort.str();
208
209 #ifdef _DEBUG
210     strConfig += "--verbose ";
211 #endif
212     char strDelay[64];
213     sprintf(strDelay, "--timeout %d ", m_sequenceDelay);
214     strConfig += strDelay;
215
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;
220
221     if (_NSGetExecutablePath(given_path, &path_size) == 0)
222     {
223       if (realpath(given_path, real_path) != NULL)
224       {
225         strConfig += "--appPath \"";
226         strConfig += real_path;
227         strConfig += "\" ";
228
229         strConfig += "--appHome \"";
230         strConfig += m_homepath;
231         strConfig += "\" ";
232       }
233     }
234
235     // Write the new configuration.
236     strConfig + "\n";
237     WriteFile(m_configFile.c_str(), strConfig);
238
239     // If process is running, kill -HUP to have it reload settings.
240     int pid = GetProcessPid(XBMC_HELPER_PROGRAM);
241     if (pid != -1)
242       kill(pid, SIGHUP);
243   }
244
245   // Turning off?
246   if (oldMode != APPLE_REMOTE_DISABLED && m_mode == APPLE_REMOTE_DISABLED)
247   {
248     Stop();
249     Uninstall();
250   }
251
252   // Turning on.
253   if (oldMode == APPLE_REMOTE_DISABLED && m_mode != APPLE_REMOTE_DISABLED)
254     Start();
255
256   // Installation/uninstallation.
257   if (oldAlwaysOn == false && m_alwaysOn == true)
258     Install();
259   if (oldAlwaysOn == true && m_alwaysOn == false)
260     Uninstall();
261 }
262
263 /////////////////////////////////////////////////////////////////////////////
264 void XBMCHelper::Install()
265 {
266   // Make sure directory exists.
267   std::string strDir = getenv("HOME");
268   strDir += "/Library/LaunchAgents";
269   CreateDirectory(strDir.c_str(), NULL);
270
271   // Load template.
272   std::string plistData = ReadFile(m_launchAgentLocalFile.c_str());
273
274   if (plistData != "") 
275   {
276       std::string launchd_args;
277
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());
281
282       // Replace ARG1 with a single argument, additional args 
283       // will need ARG2, ARG3 added to plist.
284       launchd_args = "-x";
285       start = plistData.find("${ARG1}");
286       plistData.replace(start, 7, launchd_args.c_str(), launchd_args.length());
287
288       // Install it.
289       WriteFile(m_launchAgentInstallFile.c_str(), plistData);
290
291       // Load it if not running already.
292       int pid = GetProcessPid(XBMC_HELPER_PROGRAM);
293       if (pid == -1)
294       {
295           std::string cmd = "/bin/launchctl load ";
296           cmd += m_launchAgentInstallFile;
297           system(cmd.c_str());
298       }
299   }
300 }
301
302 /////////////////////////////////////////////////////////////////////////////
303 void XBMCHelper::Uninstall()
304 {
305   // Call the unloader.
306   std::string cmd = "/bin/launchctl unload ";
307   cmd += m_launchAgentInstallFile;
308   system(cmd.c_str());
309   
310   //this also stops the helper, so restart it here again, if not disabled
311   if(m_mode != APPLE_REMOTE_DISABLED)
312     Start();
313
314   // Remove the plist file.
315   DeleteFile(m_launchAgentInstallFile.c_str());
316 }
317
318 /////////////////////////////////////////////////////////////////////////////
319 bool XBMCHelper::IsRunning()
320 {
321   return (GetProcessPid(XBMC_HELPER_PROGRAM)!=-1);
322 }
323
324 /////////////////////////////////////////////////////////////////////////////
325 bool XBMCHelper::IsRemoteBuddyInstalled()
326 {
327   return false;
328   // Check for existence of kext file.
329   return access("/System/Library/Extensions/RBIOKitHelper.kext", R_OK) != -1;
330 }
331
332 /////////////////////////////////////////////////////////////////////////////
333 bool XBMCHelper::IsSofaControlRunning()
334 {
335   return false;
336   // Check for a "Sofa Control" process running.
337   return GetProcessPid(SOFA_CONTROL_PROGRAM) != -1;
338 }
339
340 /////////////////////////////////////////////////////////////////////////////
341 std::string XBMCHelper::ReadFile(const char* fileName)
342 {
343   std::string ret = "";
344   std::ifstream is;
345   
346   is.open(fileName);
347   if( is.good() )
348   {
349     // Get length of file:
350     is.seekg(0, std::ios::end);
351     int length = is.tellg();
352     is.seekg(0, std::ios::beg);
353
354     // Allocate memory:
355     char* buffer = new char [length+1];
356
357     // Read data as a block:
358     is.read(buffer,length);
359     is.close();
360     buffer[length] = '\0';
361
362     ret = buffer;
363     delete[] buffer;
364   }
365   return ret;
366 }
367
368 /////////////////////////////////////////////////////////////////////////////
369 void XBMCHelper::WriteFile(const char* fileName, const std::string& data)
370 {
371   std::ofstream out(fileName);
372   if (!out)
373   {
374     CLog::Log(LOGERROR, "XBMCHelper: Unable to open file '%s'", fileName);
375   }
376   else
377   {
378     // Write new configuration.
379     out << data << std::endl;
380     out.flush();
381     out.close();
382   }
383 }
384
385 /////////////////////////////////////////////////////////////////////////////
386 int XBMCHelper::GetProcessPid(const char* strProgram)
387 {
388   kinfo_proc* mylist = 0;
389   size_t mycount = 0;
390   int ret = -1;
391
392   GetBSDProcessList(&mylist, &mycount);
393   for (size_t k = 0; k < mycount && ret == -1; k++)
394   {
395     kinfo_proc *proc = NULL;
396     proc = &mylist[k];
397
398     // Process names are at most sixteen characters long.
399     if (strncmp(proc->kp_proc.p_comm, strProgram, 16) == 0)
400     {
401       ret = proc->kp_proc.p_pid;
402     }
403   }
404
405   free (mylist);
406
407   return ret;
408 }
409
410 typedef struct kinfo_proc kinfo_proc;
411
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.
418 //
419 static int GetBSDProcessList(kinfo_proc **procList, size_t *procCount)
420 {
421   // example from http://developer.apple.com/qa/qa2001/qa1123.html
422   int err;
423   kinfo_proc * result;
424   bool done;
425   static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
426
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.
429   size_t length;
430
431   assert(procList != NULL);
432   assert(procCount != NULL);
433
434   *procCount = 0;
435
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.
445   //
446   result = NULL;
447   done = false;
448   do
449   {
450     assert(result == NULL);
451
452     // Call sysctl with a NULL buffer.
453     length = 0;
454     err = sysctl((int *) name, (sizeof(name) / sizeof(*name)) - 1, NULL,
455         &length, NULL, 0);
456     if (err == -1)
457       err = errno;
458
459     // Allocate an appropriately sized buffer based on the results from the previous call.
460     if (err == 0)
461     {
462       result = (kinfo_proc*) malloc(length);
463       if (result == NULL)
464         err = ENOMEM;
465     }
466
467     // Call sysctl again with the new buffer.  If we get an ENOMEM
468     // error, toss away our buffer and start again.
469     //
470     if (err == 0)
471     {
472       err = sysctl((int *) name, (sizeof(name) / sizeof(*name)) - 1, result,
473           &length, NULL, 0);
474
475       if (err == -1)
476         err = errno;
477         
478       if (err == 0)
479       {
480         done = true;
481       }
482       else if (err == ENOMEM)
483       {
484         assert(result != NULL);
485         free(result);
486         result = NULL;
487         err = 0;
488       }
489     }
490   } while (err == 0 && !done);
491
492   // Clean up and establish post conditions.
493   if (err != 0 && result != NULL)
494   {
495     free(result);
496     result = NULL;
497   }
498
499   *procList = result;
500   if (err == 0)
501     *procCount = length / sizeof(kinfo_proc);
502
503   assert( (err == 0) == (*procList != NULL) );
504   return err;
505 }
506 #endif