FIX: [droid] set "remote as keyboard" default to true
[vuplus_xbmc] / xbmc / cores / ExternalPlayer / ExternalPlayer.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 #include "threads/SystemClock.h"
22 #include "system.h"
23 #include "signal.h"
24 #include "limits.h"
25 #include "threads/SingleLock.h"
26 #include "ExternalPlayer.h"
27 #include "windowing/WindowingFactory.h"
28 #include "dialogs/GUIDialogOK.h"
29 #include "guilib/GUIWindowManager.h"
30 #include "Application.h"
31 #include "filesystem/MusicDatabaseFile.h"
32 #include "FileItem.h"
33 #include "utils/RegExp.h"
34 #include "utils/StringUtils.h"
35 #include "utils/URIUtils.h"
36 #include "URL.h"
37 #include "utils/XMLUtils.h"
38 #include "utils/TimeUtils.h"
39 #include "utils/log.h"
40 #include "cores/AudioEngine/AEFactory.h"
41 #if defined(TARGET_WINDOWS)
42   #include "utils/CharsetConverter.h"
43   #include "Windows.h"
44   #ifdef HAS_IRSERVERSUITE
45     #include "input/windows/IRServerSuite.h"
46   #endif
47 #endif
48 #if defined(HAS_LIRC)
49   #include "input/linux/LIRC.h"
50 #endif
51 #if defined(TARGET_ANDROID)
52   #include "android/activity/XBMCApp.h"
53 #endif
54
55 // If the process ends in less than this time (ms), we assume it's a launcher
56 // and wait for manual intervention before continuing
57 #define LAUNCHER_PROCESS_TIME 2000
58 // Time (ms) we give a process we sent a WM_QUIT to close before terminating
59 #define PROCESS_GRACE_TIME 3000
60 // Default time after which the item's playcount is incremented
61 #define DEFAULT_PLAYCOUNT_MIN_TIME 10
62
63 using namespace XFILE;
64
65 #if defined(TARGET_WINDOWS)
66 extern HWND g_hWnd;
67 #endif
68
69 CExternalPlayer::CExternalPlayer(IPlayerCallback& callback)
70     : IPlayer(callback),
71       CThread("ExternalPlayer")
72 {
73   m_bAbortRequest = false;
74   m_bIsPlaying = false;
75   m_paused = false;
76   m_playbackStartTime = 0;
77   m_speed = 1;
78   m_totalTime = 1;
79   m_time = 0;
80
81   m_hideconsole = false;
82   m_warpcursor = WARP_NONE;
83   m_hidexbmc = false;
84   m_islauncher = false;
85   m_playCountMinTime = DEFAULT_PLAYCOUNT_MIN_TIME;
86   m_playOneStackItem = false;
87
88   m_dialog = NULL;
89   m_hwndXbmc = NULL;
90   m_xPos = 0;
91   m_yPos = 0;
92
93
94 #if defined(TARGET_WINDOWS)
95   memset(&m_processInfo, 0, sizeof(m_processInfo));
96 #endif
97 }
98
99 CExternalPlayer::~CExternalPlayer()
100 {
101   CloseFile();
102 }
103
104 bool CExternalPlayer::OpenFile(const CFileItem& file, const CPlayerOptions &options)
105 {
106   try
107   {
108     m_bIsPlaying = true;
109     m_launchFilename = file.GetPath();
110     CLog::Log(LOGNOTICE, "%s: %s", __FUNCTION__, m_launchFilename.c_str());
111     Create();
112
113     return true;
114   }
115   catch(...)
116   {
117     m_bIsPlaying = false;
118     CLog::Log(LOGERROR,"%s - Exception thrown", __FUNCTION__);
119     return false;
120   }
121 }
122
123 bool CExternalPlayer::CloseFile(bool reopen)
124 {
125   m_bAbortRequest = true;
126
127   if (m_dialog && m_dialog->IsActive()) m_dialog->Close();
128
129 #if defined(TARGET_WINDOWS)
130   if (m_bIsPlaying && m_processInfo.hProcess)
131   {
132     TerminateProcess(m_processInfo.hProcess, 1);
133   }
134 #endif
135
136   return true;
137 }
138
139 bool CExternalPlayer::IsPlaying() const
140 {
141   return m_bIsPlaying;
142 }
143
144 void CExternalPlayer::Process()
145 {
146   CStdString mainFile = m_launchFilename;
147   CStdString archiveContent = "";
148
149   if (m_args.find("{0}") == std::string::npos)
150   {
151     // Unwind archive names
152     CURL url(m_launchFilename);
153     CStdString protocol = url.GetProtocol();
154     if (protocol == "zip" || protocol == "rar"/* || protocol == "iso9660" ??*/ || protocol == "udf")
155     {
156       mainFile = url.GetHostName();
157       archiveContent = url.GetFileName();
158     }
159     if (protocol == "musicdb")
160       mainFile = CMusicDatabaseFile::TranslateUrl(url);
161     if (protocol == "bluray")
162     {
163       CURL base(url.GetHostName());
164       if(base.GetProtocol() == "udf")
165       {
166         mainFile = base.GetHostName(); /* image file */
167         archiveContent = base.GetFileName();
168       }
169       else
170         mainFile = URIUtils::AddFileToFolder(base.Get(), url.GetFileName());
171     }
172   }
173
174   if (m_filenameReplacers.size() > 0)
175   {
176     for (unsigned int i = 0; i < m_filenameReplacers.size(); i++)
177     {
178       std::vector<CStdString> vecSplit;
179       StringUtils::SplitString(m_filenameReplacers[i], " , ", vecSplit);
180
181       // something is wrong, go to next substitution
182       if (vecSplit.size() != 4)
183         continue;
184
185       CStdString strMatch = vecSplit[0];
186       StringUtils::Replace(strMatch, ",,",",");
187       bool bCaseless = vecSplit[3].find('i') != std::string::npos;
188       CRegExp regExp(bCaseless, CRegExp::autoUtf8);
189
190       if (!regExp.RegComp(strMatch.c_str()))
191       { // invalid regexp - complain in logs
192         CLog::Log(LOGERROR, "%s: Invalid RegExp:'%s'", __FUNCTION__, strMatch.c_str());
193         continue;
194       }
195
196       if (regExp.RegFind(mainFile) > -1)
197       {
198         CStdString strPat = vecSplit[1];
199         StringUtils::Replace(strPat, ",,",",");
200
201         if (!regExp.RegComp(strPat.c_str()))
202         { // invalid regexp - complain in logs
203           CLog::Log(LOGERROR, "%s: Invalid RegExp:'%s'", __FUNCTION__, strPat.c_str());
204           continue;
205         }
206
207         CStdString strRep = vecSplit[2];
208         StringUtils::Replace(strRep, ",,",",");
209         bool bGlobal = vecSplit[3].find('g') != std::string::npos;
210         bool bStop = vecSplit[3].find('s') != std::string::npos;
211         int iStart = 0;
212         while ((iStart = regExp.RegFind(mainFile, iStart)) > -1)
213         {
214           int iLength = regExp.GetFindLen();
215           mainFile = mainFile.substr(0, iStart) + regExp.GetReplaceString(strRep) + mainFile.substr(iStart + iLength);
216           if (!bGlobal)
217             break;
218         }
219         CLog::Log(LOGINFO, "%s: File matched:'%s' (RE='%s',Rep='%s') new filename:'%s'.", __FUNCTION__, strMatch.c_str(), strPat.c_str(), strRep.c_str(), mainFile.c_str());
220         if (bStop) break;
221       }
222     }
223   }
224
225   CLog::Log(LOGNOTICE, "%s: Player : %s", __FUNCTION__, m_filename.c_str());
226   CLog::Log(LOGNOTICE, "%s: File   : %s", __FUNCTION__, mainFile.c_str());
227   CLog::Log(LOGNOTICE, "%s: Content: %s", __FUNCTION__, archiveContent.c_str());
228   CLog::Log(LOGNOTICE, "%s: Args   : %s", __FUNCTION__, m_args.c_str());
229   CLog::Log(LOGNOTICE, "%s: Start", __FUNCTION__);
230
231   // make sure we surround the arguments with quotes where necessary
232   CStdString strFName;
233   CStdString strFArgs;
234 #if defined(TARGET_WINDOWS)
235   // W32 batch-file handline
236   if (StringUtils::EndsWith(m_filename, ".bat") || StringUtils::EndsWith(m_filename, ".cmd"))
237   {
238     // MSDN says you just need to do this, but cmd's handing of spaces and
239     // quotes is soo broken it seems to work much better if you just omit
240     // lpApplicationName and enclose the module in lpCommandLine in quotes
241     //strFName = "cmd.exe";
242     //strFArgs = "/c ";
243   }
244   else
245 #endif
246     strFName = m_filename;
247
248   strFArgs.append("\"");
249   strFArgs.append(m_filename);
250   strFArgs.append("\" ");
251   strFArgs.append(m_args);
252
253   int nReplaced = StringUtils::Replace(strFArgs, "{0}", mainFile);
254
255   if (!nReplaced)
256     nReplaced = StringUtils::Replace(strFArgs, "{1}", mainFile) + StringUtils::Replace(strFArgs, "{2}", archiveContent);
257
258   if (!nReplaced)
259   {
260     strFArgs.append(" \"");
261     strFArgs.append(mainFile);
262     strFArgs.append("\"");
263   }
264
265 #if defined(TARGET_WINDOWS)
266   if (m_warpcursor)
267   {
268     GetCursorPos(&m_ptCursorpos);
269     int x = 0;
270     int y = 0;
271     switch (m_warpcursor)
272     {
273       case WARP_BOTTOM_RIGHT:
274         x = GetSystemMetrics(SM_CXSCREEN);
275       case WARP_BOTTOM_LEFT:
276         y = GetSystemMetrics(SM_CYSCREEN);
277         break;
278       case WARP_TOP_RIGHT:
279         x = GetSystemMetrics(SM_CXSCREEN);
280         break;
281       case WARP_CENTER:
282         x = GetSystemMetrics(SM_CXSCREEN) / 2;
283         y = GetSystemMetrics(SM_CYSCREEN) / 2;
284         break;
285     }
286     CLog::Log(LOGNOTICE, "%s: Warping cursor to (%d,%d)", __FUNCTION__, x, y);
287     SetCursorPos(x,y);
288   }
289
290   LONG currentStyle = GetWindowLong(g_hWnd, GWL_EXSTYLE);
291 #endif
292
293   if (m_hidexbmc && !m_islauncher)
294   {
295     CLog::Log(LOGNOTICE, "%s: Hiding XBMC window", __FUNCTION__);
296     g_Windowing.Hide();
297   }
298 #if defined(TARGET_WINDOWS)
299   else if (currentStyle & WS_EX_TOPMOST)
300   {
301     CLog::Log(LOGNOTICE, "%s: Lowering XBMC window", __FUNCTION__);
302     SetWindowPos(g_hWnd,HWND_BOTTOM,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOREDRAW);
303   }
304
305   CLog::Log(LOGDEBUG, "%s: Unlocking foreground window", __FUNCTION__);
306   LockSetForegroundWindow(LSFW_UNLOCK);
307 #endif
308
309   m_playbackStartTime = XbmcThreads::SystemClockMillis();
310
311   /* Suspend AE temporarily so exclusive or hog-mode sinks */
312   /* don't block external player's access to audio device  */
313   CAEFactory::Suspend();
314   // wait for AE has completed suspended
315   XbmcThreads::EndTime timer(2000);
316   while (!timer.IsTimePast() && !CAEFactory::IsSuspended())
317   {
318     Sleep(50);
319   }
320   if (timer.IsTimePast())
321   {
322     CLog::Log(LOGERROR,"%s: AudioEngine did not suspend before launching external player", __FUNCTION__);
323   }
324
325   BOOL ret = TRUE;
326 #if defined(TARGET_WINDOWS)
327   ret = ExecuteAppW32(strFName.c_str(),strFArgs.c_str());
328 #elif defined(TARGET_ANDROID)
329   ret = ExecuteAppAndroid(m_filename.c_str(), mainFile.c_str());
330 #elif defined(TARGET_POSIX) || defined(TARGET_DARWIN_OSX)
331   ret = ExecuteAppLinux(strFArgs.c_str());
332 #endif
333   int64_t elapsedMillis = XbmcThreads::SystemClockMillis() - m_playbackStartTime;
334
335   if (ret && (m_islauncher || elapsedMillis < LAUNCHER_PROCESS_TIME))
336   {
337     if (m_hidexbmc)
338     {
339       CLog::Log(LOGNOTICE, "%s: XBMC cannot stay hidden for a launcher process", __FUNCTION__);
340       g_Windowing.Show(false);
341     }
342
343     {
344       CSingleLock lock(g_graphicsContext);
345       m_dialog = (CGUIDialogOK *)g_windowManager.GetWindow(WINDOW_DIALOG_OK);
346       m_dialog->SetHeading(23100);
347       m_dialog->SetLine(1, 23104);
348       m_dialog->SetLine(2, 23105);
349       m_dialog->SetLine(3, 23106);
350     }
351
352     if (!m_bAbortRequest) m_dialog->DoModal();
353   }
354
355   m_bIsPlaying = false;
356   CLog::Log(LOGNOTICE, "%s: Stop", __FUNCTION__);
357
358 #if defined(TARGET_WINDOWS)
359   g_Windowing.Restore();
360
361   if (currentStyle & WS_EX_TOPMOST)
362   {
363     CLog::Log(LOGNOTICE, "%s: Showing XBMC window TOPMOST", __FUNCTION__);
364     SetWindowPos(g_hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW);
365     SetForegroundWindow(g_hWnd);
366   }
367   else
368 #endif
369   {
370     CLog::Log(LOGNOTICE, "%s: Showing XBMC window", __FUNCTION__);
371     g_Windowing.Show();
372   }
373
374 #if defined(TARGET_WINDOWS)
375   if (m_warpcursor)
376   {
377     m_xPos = 0;
378     m_yPos = 0;
379     if (&m_ptCursorpos != 0)
380     {
381       m_xPos = (m_ptCursorpos.x);
382       m_yPos = (m_ptCursorpos.y);
383     }
384     CLog::Log(LOGNOTICE, "%s: Restoring cursor to (%d,%d)", __FUNCTION__, m_xPos, m_yPos);
385     SetCursorPos(m_xPos,m_yPos);
386   }
387 #endif
388
389   /* Resume AE processing of XBMC native audio */
390   if (!CAEFactory::Resume())
391   {
392     CLog::Log(LOGFATAL, "%s: Failed to restart AudioEngine after return from external player",__FUNCTION__);
393   }
394
395   // We don't want to come back to an active screensaver
396   g_application.ResetScreenSaver();
397   g_application.WakeUpScreenSaverAndDPMS();
398
399   if (!ret || (m_playOneStackItem && g_application.CurrentFileItem().IsStack()))
400     m_callback.OnPlayBackStopped();
401   else
402     m_callback.OnPlayBackEnded();
403 }
404
405 #if defined(TARGET_WINDOWS)
406 BOOL CExternalPlayer::ExecuteAppW32(const char* strPath, const char* strSwitches)
407 {
408   CLog::Log(LOGNOTICE, "%s: %s %s", __FUNCTION__, strPath, strSwitches);
409
410   STARTUPINFOW si;
411   memset(&si, 0, sizeof(si));
412   si.cb = sizeof(si);
413   si.dwFlags = STARTF_USESHOWWINDOW;
414   si.wShowWindow = m_hideconsole ? SW_HIDE : SW_SHOW;
415
416   CStdStringW WstrPath, WstrSwitches;
417   g_charsetConverter.utf8ToW(strPath, WstrPath);
418   g_charsetConverter.utf8ToW(strSwitches, WstrSwitches);
419
420   if (m_bAbortRequest) return false;
421
422   BOOL ret = CreateProcessW(WstrPath.empty() ? NULL : WstrPath.c_str(),
423                             (LPWSTR) WstrSwitches.c_str(), NULL, NULL, FALSE, NULL,
424                             NULL, NULL, &si, &m_processInfo);
425
426   if (ret == FALSE)
427   {
428     DWORD lastError = GetLastError();
429     CLog::Log(LOGNOTICE, "%s - Failure: %d", __FUNCTION__, lastError);
430   }
431   else
432   {
433     int res = WaitForSingleObject(m_processInfo.hProcess, INFINITE);
434
435     switch (res)
436     {
437       case WAIT_OBJECT_0:
438         CLog::Log(LOGNOTICE, "%s: WAIT_OBJECT_0", __FUNCTION__);
439         break;
440       case WAIT_ABANDONED:
441         CLog::Log(LOGNOTICE, "%s: WAIT_ABANDONED", __FUNCTION__);
442         break;
443       case WAIT_TIMEOUT:
444         CLog::Log(LOGNOTICE, "%s: WAIT_TIMEOUT", __FUNCTION__);
445         break;
446       case WAIT_FAILED:
447         CLog::Log(LOGNOTICE, "%s: WAIT_FAILED (%d)", __FUNCTION__, GetLastError());
448         ret = FALSE;
449         break;
450     }
451
452     CloseHandle(m_processInfo.hThread);
453     m_processInfo.hThread = 0;
454     CloseHandle(m_processInfo.hProcess);
455     m_processInfo.hProcess = 0;
456   }
457
458   return ret;
459 }
460 #endif
461
462 #if !defined(TARGET_ANDROID) && (defined(TARGET_POSIX) || defined(TARGET_DARWIN_OSX))
463 BOOL CExternalPlayer::ExecuteAppLinux(const char* strSwitches)
464 {
465   CLog::Log(LOGNOTICE, "%s: %s", __FUNCTION__, strSwitches);
466 #ifdef HAS_LIRC
467   bool remoteused = g_RemoteControl.IsInUse();
468   g_RemoteControl.Disconnect();
469   g_RemoteControl.setUsed(false);
470 #endif
471
472   int ret = system(strSwitches);
473
474 #ifdef HAS_LIRC
475   g_RemoteControl.setUsed(remoteused);
476   g_RemoteControl.Initialize();
477 #endif
478
479   if (ret != 0)
480   {
481     CLog::Log(LOGNOTICE, "%s: Failure: %d", __FUNCTION__, ret);
482   }
483
484   return ret == 0;
485 }
486 #endif
487
488 #if defined(TARGET_ANDROID)
489 BOOL CExternalPlayer::ExecuteAppAndroid(const char* strSwitches,const char* strPath)
490 {
491   CLog::Log(LOGNOTICE, "%s: %s", __FUNCTION__, strSwitches);
492
493   int ret = CXBMCApp::StartActivity(strSwitches, "android.intent.action.VIEW", "video/*", strPath);
494
495   if (ret != 0)
496   {
497     CLog::Log(LOGNOTICE, "%s: Failure: %d", __FUNCTION__, ret);
498   }
499
500   return ret == 0;
501 }
502 #endif
503
504 void CExternalPlayer::Pause()
505 {
506 }
507
508 bool CExternalPlayer::IsPaused() const
509 {
510   return false;
511 }
512
513 bool CExternalPlayer::HasVideo() const
514 {
515   return true;
516 }
517
518 bool CExternalPlayer::HasAudio() const
519 {
520   return false;
521 }
522
523 void CExternalPlayer::SwitchToNextLanguage()
524 {
525 }
526
527 void CExternalPlayer::ToggleSubtitles()
528 {
529 }
530
531 bool CExternalPlayer::CanSeek()
532 {
533   return false;
534 }
535
536 void CExternalPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride)
537 {
538 }
539
540 void CExternalPlayer::GetAudioInfo(CStdString& strAudioInfo)
541 {
542   strAudioInfo = "CExternalPlayer:GetAudioInfo";
543 }
544
545 void CExternalPlayer::GetVideoInfo(CStdString& strVideoInfo)
546 {
547   strVideoInfo = "CExternalPlayer:GetVideoInfo";
548 }
549
550 void CExternalPlayer::GetGeneralInfo(CStdString& strGeneralInfo)
551 {
552   strGeneralInfo = "CExternalPlayer:GetGeneralInfo";
553 }
554
555 void CExternalPlayer::SwitchToNextAudioLanguage()
556 {
557 }
558
559 void CExternalPlayer::SeekPercentage(float iPercent)
560 {
561 }
562
563 float CExternalPlayer::GetPercentage()
564 {
565   int64_t iTime = GetTime();
566   int64_t iTotalTime = GetTotalTime();
567
568   if (iTotalTime != 0)
569   {
570     CLog::Log(LOGDEBUG, "Percentage is %f", (iTime * 100 / (float)iTotalTime));
571     return iTime * 100 / (float)iTotalTime;
572   }
573
574   return 0.0f;
575 }
576
577 void CExternalPlayer::SetAVDelay(float fValue)
578 {
579 }
580
581 float CExternalPlayer::GetAVDelay()
582 {
583   return 0.0f;
584 }
585
586 void CExternalPlayer::SetSubTitleDelay(float fValue)
587 {
588 }
589
590 float CExternalPlayer::GetSubTitleDelay()
591 {
592   return 0.0;
593 }
594
595 void CExternalPlayer::SeekTime(int64_t iTime)
596 {
597 }
598
599 int64_t CExternalPlayer::GetTime() // in millis
600 {
601   if ((XbmcThreads::SystemClockMillis() - m_playbackStartTime) / 1000 > m_playCountMinTime)
602   {
603     m_time = m_totalTime * 1000;
604   }
605
606   return m_time;
607 }
608
609 int64_t CExternalPlayer::GetTotalTime() // in milliseconds
610 {
611   return (int64_t)m_totalTime * 1000;
612 }
613
614 void CExternalPlayer::ToFFRW(int iSpeed)
615 {
616   m_speed = iSpeed;
617 }
618
619 void CExternalPlayer::ShowOSD(bool bOnoff)
620 {
621 }
622
623 CStdString CExternalPlayer::GetPlayerState()
624 {
625   return "";
626 }
627
628 bool CExternalPlayer::SetPlayerState(CStdString state)
629 {
630   return true;
631 }
632
633 bool CExternalPlayer::Initialize(TiXmlElement* pConfig)
634 {
635   XMLUtils::GetString(pConfig, "filename", m_filename);
636   if (m_filename.length() > 0)
637   {
638     CLog::Log(LOGNOTICE, "ExternalPlayer Filename: %s", m_filename.c_str());
639   }
640   else
641   {
642     CStdString xml;
643     xml<<*pConfig;
644     CLog::Log(LOGERROR, "ExternalPlayer Error: filename element missing from: %s", xml.c_str());
645     return false;
646   }
647
648   XMLUtils::GetString(pConfig, "args", m_args);
649   XMLUtils::GetBoolean(pConfig, "playonestackitem", m_playOneStackItem);
650   XMLUtils::GetBoolean(pConfig, "islauncher", m_islauncher);
651   XMLUtils::GetBoolean(pConfig, "hidexbmc", m_hidexbmc);
652   if (!XMLUtils::GetBoolean(pConfig, "hideconsole", m_hideconsole))
653   {
654 #ifdef TARGET_WINDOWS
655     // Default depends on whether player is a batch file
656     m_hideconsole = StringUtils::EndsWith(m_filename, ".bat");
657 #endif
658   }
659
660   bool bHideCursor;
661   if (XMLUtils::GetBoolean(pConfig, "hidecursor", bHideCursor) && bHideCursor)
662     m_warpcursor = WARP_BOTTOM_RIGHT;
663
664   CStdString warpCursor;
665   if (XMLUtils::GetString(pConfig, "warpcursor", warpCursor) && !warpCursor.empty())
666   {
667     if (warpCursor == "bottomright") m_warpcursor = WARP_BOTTOM_RIGHT;
668     else if (warpCursor == "bottomleft") m_warpcursor = WARP_BOTTOM_LEFT;
669     else if (warpCursor == "topleft") m_warpcursor = WARP_TOP_LEFT;
670     else if (warpCursor == "topright") m_warpcursor = WARP_TOP_RIGHT;
671     else if (warpCursor == "center") m_warpcursor = WARP_CENTER;
672     else
673     {
674       warpCursor = "none";
675       CLog::Log(LOGWARNING, "ExternalPlayer: invalid value for warpcursor: %s", warpCursor.c_str());
676     }
677   }
678
679   XMLUtils::GetInt(pConfig, "playcountminimumtime", m_playCountMinTime, 1, INT_MAX);
680
681   CLog::Log(LOGNOTICE, "ExternalPlayer Tweaks: hideconsole (%s), hidexbmc (%s), islauncher (%s), warpcursor (%s)",
682           m_hideconsole ? "true" : "false",
683           m_hidexbmc ? "true" : "false",
684           m_islauncher ? "true" : "false",
685           warpCursor.c_str());
686
687 #ifdef TARGET_WINDOWS
688   m_filenameReplacers.push_back("^smb:// , / , \\\\ , g");
689   m_filenameReplacers.push_back("^smb:\\\\\\\\ , smb:(\\\\\\\\[^\\\\]*\\\\) , \\1 , ");
690 #endif
691
692   TiXmlElement* pReplacers = pConfig->FirstChildElement("replacers");
693   while (pReplacers)
694   {
695     GetCustomRegexpReplacers(pReplacers, m_filenameReplacers);
696     pReplacers = pReplacers->NextSiblingElement("replacers");
697   }
698
699   return true;
700 }
701
702 void CExternalPlayer::GetCustomRegexpReplacers(TiXmlElement *pRootElement,
703                                                CStdStringArray& settings)
704 {
705   int iAction = 0; // overwrite
706   // for backward compatibility
707   const char* szAppend = pRootElement->Attribute("append");
708   if ((szAppend && stricmp(szAppend, "yes") == 0))
709     iAction = 1;
710   // action takes precedence if both attributes exist
711   const char* szAction = pRootElement->Attribute("action");
712   if (szAction)
713   {
714     iAction = 0; // overwrite
715     if (stricmp(szAction, "append") == 0)
716       iAction = 1; // append
717     else if (stricmp(szAction, "prepend") == 0)
718       iAction = 2; // prepend
719   }
720   if (iAction == 0)
721     settings.clear();
722
723   TiXmlElement* pReplacer = pRootElement->FirstChildElement("replacer");
724   int i = 0;
725   while (pReplacer)
726   {
727     if (pReplacer->FirstChild())
728     {
729       const char* szGlobal = pReplacer->Attribute("global");
730       const char* szStop = pReplacer->Attribute("stop");
731       bool bGlobal = szGlobal && stricmp(szGlobal, "true") == 0;
732       bool bStop = szStop && stricmp(szStop, "true") == 0;
733
734       CStdString strMatch;
735       CStdString strPat;
736       CStdString strRep;
737       XMLUtils::GetString(pReplacer,"match",strMatch);
738       XMLUtils::GetString(pReplacer,"pat",strPat);
739       XMLUtils::GetString(pReplacer,"rep",strRep);
740
741       if (!strPat.empty() && !strRep.empty())
742       {
743         CLog::Log(LOGDEBUG,"  Registering replacer:");
744         CLog::Log(LOGDEBUG,"    Match:[%s] Pattern:[%s] Replacement:[%s]", strMatch.c_str(), strPat.c_str(), strRep.c_str());
745         CLog::Log(LOGDEBUG,"    Global:[%s] Stop:[%s]", bGlobal?"true":"false", bStop?"true":"false");
746         // keep literal commas since we use comma as a seperator
747         StringUtils::Replace(strMatch, ",",",,");
748         StringUtils::Replace(strPat, ",",",,");
749         StringUtils::Replace(strRep, ",",",,");
750
751         CStdString strReplacer = strMatch + " , " + strPat + " , " + strRep + " , " + (bGlobal ? "g" : "") + (bStop ? "s" : "");
752         if (iAction == 2)
753           settings.insert(settings.begin() + i++, 1, strReplacer);
754         else
755           settings.push_back(strReplacer);
756       }
757       else
758       {
759         // error message about missing tag
760         if (strPat.empty())
761           CLog::Log(LOGERROR,"  Missing <Pat> tag");
762         else
763           CLog::Log(LOGERROR,"  Missing <Rep> tag");
764       }
765     }
766
767     pReplacer = pReplacer->NextSiblingElement("replacer");
768   }
769 }