75205dc1aa11709d84b651421f452005e0399b6d
[dcpomatic.git] / src / lib / cross.cc
1 /*
2     Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "cross.h"
22 #include "compose.hpp"
23 #include "log.h"
24 #include "config.h"
25 #include "exceptions.h"
26 extern "C" {
27 #include <libavformat/avio.h>
28 }
29 #include <boost/algorithm/string.hpp>
30 #ifdef DCPOMATIC_LINUX
31 #include <unistd.h>
32 #include <mntent.h>
33 #endif
34 #ifdef DCPOMATIC_WINDOWS
35 #include <windows.h>
36 #undef DATADIR
37 #include <shlwapi.h>
38 #include <shellapi.h>
39 #include <fcntl.h>
40 #endif
41 #ifdef DCPOMATIC_OSX
42 #include <sys/sysctl.h>
43 #include <mach-o/dyld.h>
44 #include <IOKit/pwr_mgt/IOPMLib.h>
45 #endif
46 #ifdef DCPOMATIC_POSIX
47 #include <sys/types.h>
48 #include <ifaddrs.h>
49 #include <netinet/in.h>
50 #include <arpa/inet.h>
51 #endif
52 #include <fstream>
53
54 #include "i18n.h"
55
56 #define LOG_GENERAL(...) log->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
57 #define LOG_ERROR(...) log->log (String::compose (__VA_ARGS__), LogEntry::TYPE_ERROR);
58 #define LOG_ERROR_NC(...) log->log (__VA_ARGS__, LogEntry::TYPE_ERROR);
59
60 using std::pair;
61 using std::list;
62 using std::ifstream;
63 using std::string;
64 using std::wstring;
65 using std::make_pair;
66 using std::runtime_error;
67 using boost::shared_ptr;
68
69 /** @param s Number of seconds to sleep for */
70 void
71 dcpomatic_sleep (int s)
72 {
73 #ifdef DCPOMATIC_POSIX
74         sleep (s);
75 #endif
76 #ifdef DCPOMATIC_WINDOWS
77         Sleep (s * 1000);
78 #endif
79 }
80
81 /** @return A string of CPU information (model name etc.) */
82 string
83 cpu_info ()
84 {
85         string info;
86
87 #ifdef DCPOMATIC_LINUX
88         /* This use of ifstream is ok; the filename can never
89            be non-Latin
90         */
91         ifstream f ("/proc/cpuinfo");
92         while (f.good ()) {
93                 string l;
94                 getline (f, l);
95                 if (boost::algorithm::starts_with (l, "model name")) {
96                         string::size_type const c = l.find (':');
97                         if (c != string::npos) {
98                                 info = l.substr (c + 2);
99                         }
100                 }
101         }
102 #endif
103
104 #ifdef DCPOMATIC_OSX
105         char buffer[64];
106         size_t N = sizeof (buffer);
107         if (sysctlbyname ("machdep.cpu.brand_string", buffer, &N, 0, 0) == 0) {
108                 info = buffer;
109         }
110 #endif
111
112 #ifdef DCPOMATIC_WINDOWS
113         HKEY key;
114         if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key) != ERROR_SUCCESS) {
115                 return info;
116         }
117
118         DWORD type;
119         DWORD data;
120         if (RegQueryValueEx (key, L"ProcessorNameString", 0, &type, 0, &data) != ERROR_SUCCESS) {
121                 return info;
122         }
123
124         if (type != REG_SZ) {
125                 return info;
126         }
127
128         wstring value (data / sizeof (wchar_t), L'\0');
129         if (RegQueryValueEx (key, L"ProcessorNameString", 0, 0, reinterpret_cast<LPBYTE> (&value[0]), &data) != ERROR_SUCCESS) {
130                 RegCloseKey (key);
131                 return info;
132         }
133
134         info = string (value.begin(), value.end());
135
136         RegCloseKey (key);
137
138 #endif
139
140         return info;
141 }
142
143 #ifdef DCPOMATIC_OSX
144 /** @return Path of the Contents directory in the .app */
145 boost::filesystem::path
146 app_contents ()
147 {
148         uint32_t size = 1024;
149         char buffer[size];
150         if (_NSGetExecutablePath (buffer, &size)) {
151                 throw runtime_error ("_NSGetExecutablePath failed");
152         }
153
154         boost::filesystem::path path (buffer);
155         path = boost::filesystem::canonical (path);
156         path = path.parent_path ();
157         path = path.parent_path ();
158         return path;
159 }
160 #endif
161
162 boost::filesystem::path
163 shared_path ()
164 {
165 #ifdef DCPOMATIC_LINUX
166         char const * p = getenv ("DCPOMATIC_LINUX_SHARE_PREFIX");
167         if (p) {
168                 return p;
169         }
170         return boost::filesystem::canonical (LINUX_SHARE_PREFIX);
171 #endif
172 #ifdef DCPOMATIC_WINDOWS
173         wchar_t dir[512];
174         GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
175         PathRemoveFileSpec (dir);
176         boost::filesystem::path path = dir;
177         return path.parent_path();
178 #endif
179 #ifdef DCPOMATIC_OSX
180         return app_contents() / "Resources";
181 #endif
182 }
183
184 void
185 run_ffprobe (boost::filesystem::path content, boost::filesystem::path out, shared_ptr<Log> log)
186 {
187 #ifdef DCPOMATIC_WINDOWS
188         SECURITY_ATTRIBUTES security;
189         security.nLength = sizeof (security);
190         security.bInheritHandle = TRUE;
191         security.lpSecurityDescriptor = 0;
192
193         HANDLE child_stderr_read;
194         HANDLE child_stderr_write;
195         if (!CreatePipe (&child_stderr_read, &child_stderr_write, &security, 0)) {
196                 LOG_ERROR_NC ("ffprobe call failed (could not CreatePipe)");
197                 return;
198         }
199
200         wchar_t dir[512];
201         GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
202         PathRemoveFileSpec (dir);
203         SetCurrentDirectory (dir);
204
205         STARTUPINFO startup_info;
206         ZeroMemory (&startup_info, sizeof (startup_info));
207         startup_info.cb = sizeof (startup_info);
208         startup_info.hStdError = child_stderr_write;
209         startup_info.dwFlags |= STARTF_USESTDHANDLES;
210
211         wchar_t command[512];
212         wcscpy (command, L"ffprobe.exe \"");
213
214         wchar_t file[512];
215         MultiByteToWideChar (CP_UTF8, 0, content.string().c_str(), -1, file, sizeof(file));
216         wcscat (command, file);
217
218         wcscat (command, L"\"");
219
220         PROCESS_INFORMATION process_info;
221         ZeroMemory (&process_info, sizeof (process_info));
222         if (!CreateProcess (0, command, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
223                 LOG_ERROR_NC (N_("ffprobe call failed (could not CreateProcess)"));
224                 return;
225         }
226
227         FILE* o = fopen_boost (out, "w");
228         if (!o) {
229                 LOG_ERROR_NC (N_("ffprobe call failed (could not create output file)"));
230                 return;
231         }
232
233         CloseHandle (child_stderr_write);
234
235         while (true) {
236                 char buffer[512];
237                 DWORD read;
238                 if (!ReadFile(child_stderr_read, buffer, sizeof(buffer), &read, 0) || read == 0) {
239                         break;
240                 }
241                 fwrite (buffer, read, 1, o);
242         }
243
244         fclose (o);
245
246         WaitForSingleObject (process_info.hProcess, INFINITE);
247         CloseHandle (process_info.hProcess);
248         CloseHandle (process_info.hThread);
249         CloseHandle (child_stderr_read);
250 #endif
251
252 #ifdef DCPOMATIC_LINUX
253         string ffprobe = "ffprobe \"" + content.string() + "\" 2> \"" + out.string() + "\"";
254         LOG_GENERAL (N_("Probing with %1"), ffprobe);
255         system (ffprobe.c_str ());
256 #endif
257
258 #ifdef DCPOMATIC_OSX
259         boost::filesystem::path path = app_contents();
260         path /= "MacOS";
261         path /= "ffprobe";
262
263         string ffprobe = "\"" + path.string() + "\" \"" + content.string() + "\" 2> \"" + out.string() + "\"";
264         LOG_GENERAL (N_("Probing with %1"), ffprobe);
265         system (ffprobe.c_str ());
266 #endif
267 }
268
269 list<pair<string, string> >
270 mount_info ()
271 {
272         list<pair<string, string> > m;
273
274 #ifdef DCPOMATIC_LINUX
275         FILE* f = setmntent ("/etc/mtab", "r");
276         if (!f) {
277                 return m;
278         }
279
280         while (true) {
281                 struct mntent* mnt = getmntent (f);
282                 if (!mnt) {
283                         break;
284                 }
285
286                 m.push_back (make_pair (mnt->mnt_dir, mnt->mnt_type));
287         }
288
289         endmntent (f);
290 #endif
291
292         return m;
293 }
294
295 boost::filesystem::path
296 openssl_path ()
297 {
298 #ifdef DCPOMATIC_WINDOWS
299         wchar_t dir[512];
300         GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
301         PathRemoveFileSpec (dir);
302
303         boost::filesystem::path path = dir;
304         path /= "openssl.exe";
305         return path;
306 #else
307         /* We assume that it's on the path for Linux and OS X */
308         return "openssl";
309 #endif
310
311 }
312
313 /* Apparently there is no way to create an ofstream using a UTF-8
314    filename under Windows.  We are hence reduced to using fopen
315    with this wrapper.
316 */
317 FILE *
318 fopen_boost (boost::filesystem::path p, string t)
319 {
320 #ifdef DCPOMATIC_WINDOWS
321         wstring w (t.begin(), t.end());
322         /* c_str() here should give a UTF-16 string */
323         return _wfopen (p.c_str(), w.c_str ());
324 #else
325         return fopen (p.c_str(), t.c_str ());
326 #endif
327 }
328
329 int
330 dcpomatic_fseek (FILE* stream, int64_t offset, int whence)
331 {
332 #ifdef DCPOMATIC_WINDOWS
333         return _fseeki64 (stream, offset, whence);
334 #else
335         return fseek (stream, offset, whence);
336 #endif
337 }
338
339 void
340 Waker::nudge ()
341 {
342 #ifdef DCPOMATIC_WINDOWS
343         SetThreadExecutionState (ES_SYSTEM_REQUIRED);
344 #endif
345 }
346
347 Waker::Waker ()
348 {
349 #ifdef DCPOMATIC_OSX
350         /* We should use this */
351         // IOPMAssertionCreateWithName (kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, CFSTR ("Encoding DCP"), &_assertion_id);
352         /* but it's not available on 10.5, so we use this */
353         IOPMAssertionCreate (kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, &_assertion_id);
354 #endif
355 }
356
357 Waker::~Waker ()
358 {
359 #ifdef DCPOMATIC_OSX
360         IOPMAssertionRelease (_assertion_id);
361 #endif
362 }
363
364 void
365 start_tool (boost::filesystem::path dcpomatic, string executable,
366 #ifdef DCPOMATIC_OSX
367             string app
368 #else
369             string
370 #endif
371         )
372 {
373 #if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_WINDOWS)
374         boost::filesystem::path batch = dcpomatic.parent_path() / executable;
375 #endif
376
377 #ifdef DCPOMATIC_OSX
378         boost::filesystem::path batch = dcpomatic.parent_path ();
379         batch = batch.parent_path (); // MacOS
380         batch = batch.parent_path (); // Contents
381         batch = batch.parent_path (); // DCP-o-matic.app
382         batch = batch.parent_path (); // Applications
383         batch /= app;
384         batch /= "Contents";
385         batch /= "MacOS";
386         batch /= executable;
387 #endif
388
389 #if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_OSX)
390         pid_t pid = fork ();
391         if (pid == 0) {
392                 int const r = system (batch.string().c_str());
393                 exit (WEXITSTATUS (r));
394         }
395 #endif
396
397 #ifdef DCPOMATIC_WINDOWS
398         STARTUPINFO startup_info;
399         ZeroMemory (&startup_info, sizeof (startup_info));
400         startup_info.cb = sizeof (startup_info);
401
402         PROCESS_INFORMATION process_info;
403         ZeroMemory (&process_info, sizeof (process_info));
404
405         wchar_t cmd[512];
406         MultiByteToWideChar (CP_UTF8, 0, batch.string().c_str(), -1, cmd, sizeof(cmd));
407         CreateProcess (0, cmd, 0, 0, FALSE, 0, 0, 0, &startup_info, &process_info);
408 #endif
409 }
410
411 void
412 start_batch_converter (boost::filesystem::path dcpomatic)
413 {
414         start_tool (dcpomatic, "dcpomatic2_batch", "DCP-o-matic\\ 2\\ Batch\\ Converter.app");
415 }
416
417 void
418 start_player (boost::filesystem::path dcpomatic)
419 {
420         start_tool (dcpomatic, "dcpomatic2_player", "DCP-o-matic\\ 2\\ Player.app");
421 }
422
423 uint64_t
424 thread_id ()
425 {
426 #ifdef DCPOMATIC_WINDOWS
427         return (uint64_t) GetCurrentThreadId ();
428 #else
429         return (uint64_t) pthread_self ();
430 #endif
431 }
432
433 int
434 avio_open_boost (AVIOContext** s, boost::filesystem::path file, int flags)
435 {
436 #ifdef DCPOMATIC_WINDOWS
437         int const length = (file.string().length() + 1) * 2;
438         char* utf8 = new char[length];
439         WideCharToMultiByte (CP_UTF8, 0, file.c_str(), -1, utf8, length, 0, 0);
440         int const r = avio_open (s, utf8, flags);
441         delete[] utf8;
442         return r;
443 #else
444         return avio_open (s, file.c_str(), flags);
445 #endif
446 }
447
448 #ifdef DCPOMATIC_WINDOWS
449 void
450 maybe_open_console ()
451 {
452         if (Config::instance()->win32_console ()) {
453                 AllocConsole();
454
455                 HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
456                 int hCrt = _open_osfhandle((intptr_t) handle_out, _O_TEXT);
457                 FILE* hf_out = _fdopen(hCrt, "w");
458                 setvbuf(hf_out, NULL, _IONBF, 1);
459                 *stdout = *hf_out;
460
461                 HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE);
462                 hCrt = _open_osfhandle((intptr_t) handle_in, _O_TEXT);
463                 FILE* hf_in = _fdopen(hCrt, "r");
464                 setvbuf(hf_in, NULL, _IONBF, 128);
465                 *stdin = *hf_in;
466         }
467 }
468 #endif