Another Windows include.
[dcpomatic.git] / src / lib / cross.cc
1 /*
2     Copyright (C) 2012-2015 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_batch_converter (boost::filesystem::path dcpomatic)
366 {
367 #if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_WINDOWS)
368         boost::filesystem::path batch = dcpomatic.parent_path() / "dcpomatic2_batch";
369 #endif
370
371 #ifdef DCPOMATIC_OSX
372         boost::filesystem::path batch = dcpomatic.parent_path ();
373         batch = batch.parent_path (); // MacOS
374         batch = batch.parent_path (); // Contents
375         batch = batch.parent_path (); // DCP-o-matic.app
376         batch = batch.parent_path (); // Applications
377         batch /= "DCP-o-matic\\ 2\\ Batch\\ Converter.app";
378         batch /= "Contents";
379         batch /= "MacOS";
380         batch /= "dcpomatic2_batch";
381 #endif
382
383 #if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_OSX)
384         pid_t pid = fork ();
385         if (pid == 0) {
386                 std::cout << "start " << batch << " from " << dcpomatic << "\n";
387                 int const r = system (batch.string().c_str());
388                 exit (WEXITSTATUS (r));
389         }
390 #endif
391
392 #ifdef DCPOMATIC_WINDOWS
393         STARTUPINFO startup_info;
394         ZeroMemory (&startup_info, sizeof (startup_info));
395         startup_info.cb = sizeof (startup_info);
396
397         PROCESS_INFORMATION process_info;
398         ZeroMemory (&process_info, sizeof (process_info));
399
400         wchar_t cmd[512];
401         MultiByteToWideChar (CP_UTF8, 0, batch.string().c_str(), -1, cmd, sizeof(cmd));
402         CreateProcess (0, cmd, 0, 0, FALSE, 0, 0, 0, &startup_info, &process_info);
403 #endif
404 }
405
406 uint64_t
407 thread_id ()
408 {
409 #ifdef DCPOMATIC_WINDOWS
410         return (uint64_t) GetCurrentThreadId ();
411 #else
412         return (uint64_t) pthread_self ();
413 #endif
414 }
415
416 int
417 avio_open_boost (AVIOContext** s, boost::filesystem::path file, int flags)
418 {
419 #ifdef DCPOMATIC_WINDOWS
420         int const length = (file.string().length() + 1) * 2;
421         char* utf8 = new char[length];
422         WideCharToMultiByte (CP_UTF8, 0, file.c_str(), -1, utf8, length, 0, 0);
423         int const r = avio_open (s, utf8, flags);
424         delete[] utf8;
425         return r;
426 #else
427         return avio_open (s, file.c_str(), flags);
428 #endif
429 }
430
431 #ifdef DCPOMATIC_WINDOWS
432 void
433 maybe_open_console ()
434 {
435         if (Config::instance()->win32_console ()) {
436                 AllocConsole();
437
438                 HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
439                 int hCrt = _open_osfhandle((intptr_t) handle_out, _O_TEXT);
440                 FILE* hf_out = _fdopen(hCrt, "w");
441                 setvbuf(hf_out, NULL, _IONBF, 1);
442                 *stdout = *hf_out;
443
444                 HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE);
445                 hCrt = _open_osfhandle((intptr_t) handle_in, _O_TEXT);
446                 FILE* hf_in = _fdopen(hCrt, "r");
447                 setvbuf(hf_in, NULL, _IONBF, 128);
448                 *stdin = *hf_in;
449         }
450 }
451 #endif