1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #define FORBIDDEN_SYMBOL_EXCEPTION_getenv
24 #define FORBIDDEN_SYMBOL_EXCEPTION_mkdir
25 #define FORBIDDEN_SYMBOL_EXCEPTION_exit
26 #define FORBIDDEN_SYMBOL_EXCEPTION_unistd_h
27 #define FORBIDDEN_SYMBOL_EXCEPTION_time_h //On IRIX, sys/stat.h includes sys/time.h
28 #define FORBIDDEN_SYMBOL_EXCEPTION_system
29 #define FORBIDDEN_SYMBOL_EXCEPTION_random
30 #define FORBIDDEN_SYMBOL_EXCEPTION_srandom
31
32 #include "common/scummsys.h"
33
34 #ifdef POSIX
35
36 #include "backends/platform/sdl/posix/posix.h"
37 #include "backends/saves/posix/posix-saves.h"
38 #include "backends/fs/posix/posix-fs-factory.h"
39 #include "backends/fs/posix/posix-fs.h"
40 #include "backends/taskbar/unity/unity-taskbar.h"
41 #include "backends/dialogs/gtk/gtk-dialogs.h"
42
43 #ifdef USE_LINUXCD
44 #include "backends/audiocd/linux/linux-audiocd.h"
45 #endif
46
47 #include "common/textconsole.h"
48
49 #include <stdlib.h>
50 #include <errno.h>
51 #include <sys/stat.h>
52 #include <sys/wait.h>
53 #include <unistd.h>
54
55 #ifdef HAS_POSIX_SPAWN
56 #include <spawn.h>
57 #endif
58
59 #if defined(USE_SPEECH_DISPATCHER) && defined(USE_TTS)
60 #include "backends/text-to-speech/linux/linux-text-to-speech.h"
61 #endif
62 extern char **environ;
63
init()64 void OSystem_POSIX::init() {
65 // Initialze File System Factory
66 _fsFactory = new POSIXFilesystemFactory();
67
68 #if defined(USE_TASKBAR) && defined(USE_UNITY)
69 // Initialize taskbar manager
70 _taskbarManager = new UnityTaskbarManager();
71 #endif
72
73 #if defined(USE_SYSDIALOGS) && defined(USE_GTK)
74 // Initialize dialog manager
75 _dialogManager = new GtkDialogManager();
76 #endif
77
78 // Invoke parent implementation of this method
79 OSystem_SDL::init();
80 }
81
initBackend()82 void OSystem_POSIX::initBackend() {
83 // Create the savefile manager
84 if (_savefileManager == 0)
85 _savefileManager = new POSIXSaveFileManager();
86
87 #if defined(USE_SPEECH_DISPATCHER) && defined(USE_TTS)
88 // Initialize Text to Speech manager
89 _textToSpeechManager = new SpeechDispatcherManager();
90 #endif
91
92 // Invoke parent implementation of this method
93 OSystem_SDL::initBackend();
94
95 #if defined(USE_TASKBAR) && defined(USE_UNITY)
96 // Register the taskbar manager as an event source (this is necessary for the glib event loop to be run)
97 _eventManager->getEventDispatcher()->registerSource((UnityTaskbarManager *)_taskbarManager, false);
98 #endif
99 }
100
hasFeature(Feature f)101 bool OSystem_POSIX::hasFeature(Feature f) {
102 if (f == kFeatureDisplayLogFile)
103 return true;
104 #ifdef HAS_POSIX_SPAWN
105 if (f == kFeatureOpenUrl)
106 return true;
107 #endif
108 #if defined(USE_SYSDIALOGS) && defined(USE_GTK)
109 if (f == kFeatureSystemBrowserDialog)
110 return true;
111 #endif
112 return OSystem_SDL::hasFeature(f);
113 }
114
getDefaultConfigFileName()115 Common::String OSystem_POSIX::getDefaultConfigFileName() {
116 const Common::String baseConfigName = "scummvm.ini";
117
118 Common::String configFile;
119
120 Common::String prefix;
121
122 // Our old configuration file path for POSIX systems was ~/.scummvmrc.
123 // If that file exists, we still use it.
124 const char *envVar = getenv("HOME");
125 if (envVar && *envVar) {
126 configFile = envVar;
127 configFile += '/';
128 configFile += ".scummvmrc";
129
130 if (configFile.size() < MAXPATHLEN) {
131 struct stat sb;
132 if (stat(configFile.c_str(), &sb) == 0) {
133 return configFile;
134 }
135 }
136 }
137
138 // On POSIX systems we follow the XDG Base Directory Specification for
139 // where to store files. The version we based our code upon can be found
140 // over here: http://standards.freedesktop.org/basedir-spec/basedir-spec-0.8.html
141 envVar = getenv("XDG_CONFIG_HOME");
142 if (!envVar || !*envVar) {
143 envVar = getenv("HOME");
144 if (!envVar) {
145 return 0;
146 }
147
148 if (Posix::assureDirectoryExists(".config", envVar)) {
149 prefix = envVar;
150 prefix += "/.config";
151 }
152 } else {
153 prefix = envVar;
154 }
155
156 if (!prefix.empty() && Posix::assureDirectoryExists("scummvm", prefix.c_str())) {
157 prefix += "/scummvm";
158 }
159
160 if (!prefix.empty() && (prefix.size() + 1 + baseConfigName.size()) < MAXPATHLEN) {
161 configFile = prefix;
162 configFile += '/';
163 configFile += baseConfigName;
164 } else {
165 configFile = baseConfigName;
166 }
167
168 return configFile;
169 }
170
getXdgUserDir(const char * name)171 Common::String OSystem_POSIX::getXdgUserDir(const char *name) {
172 // The xdg-user-dirs configuration path is stored in the XDG config
173 // home directory. We start by retrieving this value.
174 Common::String configHome = getenv("XDG_CONFIG_HOME");
175 if (configHome.empty()) {
176 const char *home = getenv("HOME");
177 if (!home) {
178 return "";
179 }
180
181 configHome = Common::String::format("%s/.config", home);
182 }
183
184 // Find the requested directory line in the xdg-user-dirs configuration file
185 // Example line value: XDG_PICTURES_DIR="$HOME/Pictures"
186 Common::FSNode userDirsFile(configHome + "/user-dirs.dirs");
187 if (!userDirsFile.exists() || !userDirsFile.isReadable() || userDirsFile.isDirectory()) {
188 return "";
189 }
190
191 Common::SeekableReadStream *userDirsStream = userDirsFile.createReadStream();
192 if (!userDirsStream) {
193 return "";
194 }
195
196 Common::String dirLinePrefix = Common::String::format("XDG_%s_DIR=", name);
197
198 Common::String directoryValue;
199 while (!userDirsStream->eos() && !userDirsStream->err()) {
200 Common::String userDirsLine = userDirsStream->readLine();
201 userDirsLine.trim();
202
203 if (userDirsLine.hasPrefix(dirLinePrefix)) {
204 directoryValue = Common::String(userDirsLine.c_str() + dirLinePrefix.size());
205 break;
206 }
207 }
208
209 delete userDirsStream;
210
211 // Extract the path from the value
212 // Example value: "$HOME/Pictures"
213 if (directoryValue.empty() || directoryValue[0] != '"') {
214 return "";
215 }
216
217 if (directoryValue[directoryValue.size() - 1] != '"') {
218 return "";
219 }
220
221 // According to the spec the value is shell-escaped, and would need to be
222 // unescaped to be used, but neither the GTK+ nor the Qt implementation seem to
223 // properly perform that step, it's probably fine if we don't do it either.
224 Common::String directoryPath(directoryValue.c_str() + 1, directoryValue.size() - 2);
225
226 if (directoryPath.hasPrefix("$HOME/")) {
227 const char *home = getenv("HOME");
228 directoryPath = Common::String::format("%s%s", home, directoryPath.c_str() + 5);
229 }
230
231 // At this point, the path must be absolute
232 if (directoryPath.empty() || directoryPath[0] != '/') {
233 return "";
234 }
235
236 return directoryPath;
237 }
238
getScreenshotsPath()239 Common::String OSystem_POSIX::getScreenshotsPath() {
240 // If the user has configured a screenshots path, use it
241 const Common::String path = OSystem_SDL::getScreenshotsPath();
242 if (!path.empty()) {
243 return path;
244 }
245
246 // Otherwise, the default screenshots path is the "ScummVM Screenshots"
247 // directory in the XDG "Pictures" user directory, as defined in the
248 // xdg-user-dirs spec: https://www.freedesktop.org/wiki/Software/xdg-user-dirs/
249 Common::String picturesPath = getXdgUserDir("PICTURES");
250 if (picturesPath.empty()) {
251 return "";
252 }
253
254 if (!picturesPath.hasSuffix("/")) {
255 picturesPath += "/";
256 }
257
258 static const char *SCREENSHOTS_DIR_NAME = "ScummVM Screenshots";
259 if (!Posix::assureDirectoryExists(SCREENSHOTS_DIR_NAME, picturesPath.c_str())) {
260 return "";
261 }
262
263 return picturesPath + SCREENSHOTS_DIR_NAME + "/";
264 }
265
addSysArchivesToSearchSet(Common::SearchSet & s,int priority)266 void OSystem_POSIX::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) {
267 #ifdef DATA_PATH
268 const char *snap = getenv("SNAP");
269 if (snap) {
270 Common::String dataPath = Common::String(snap) + DATA_PATH;
271 Common::FSNode dataNode(dataPath);
272 if (dataNode.exists() && dataNode.isDirectory()) {
273 // This is the same priority which is used for the data path (below),
274 // but we insert this one first, so it will be searched first.
275 s.add(dataPath, new Common::FSDirectory(dataNode, 4), priority);
276 }
277 }
278 #endif
279
280 // For now, we always add the data path, just in case SNAP doesn't make sense.
281 OSystem_SDL::addSysArchivesToSearchSet(s, priority);
282 }
283
getDefaultLogFileName()284 Common::String OSystem_POSIX::getDefaultLogFileName() {
285 Common::String logFile;
286
287 // On POSIX systems we follow the XDG Base Directory Specification for
288 // where to store files. The version we based our code upon can be found
289 // over here: http://standards.freedesktop.org/basedir-spec/basedir-spec-0.8.html
290 const char *prefix = getenv("XDG_CACHE_HOME");
291 if (prefix == nullptr || !*prefix) {
292 prefix = getenv("HOME");
293 if (prefix == nullptr) {
294 return Common::String();
295 }
296
297 logFile = ".cache/";
298 }
299
300 logFile += "scummvm/logs";
301
302 if (!Posix::assureDirectoryExists(logFile, prefix)) {
303 return Common::String();
304 }
305
306 return Common::String::format("%s/%s/scummvm.log", prefix, logFile.c_str());
307 }
308
displayLogFile()309 bool OSystem_POSIX::displayLogFile() {
310 if (_logFilePath.empty())
311 return false;
312
313 // FIXME: This may not work perfectly when in fullscreen mode.
314 // On my system it drops from fullscreen without ScummVM noticing,
315 // so the next Alt-Enter does nothing, going from windowed to windowed.
316 // (wjp, 20110604)
317
318 pid_t pid = fork();
319 if (pid < 0) {
320 // failed to fork
321 return false;
322 } else if (pid == 0) {
323
324 // Try xdg-open first
325 execlp("xdg-open", "xdg-open", _logFilePath.c_str(), (char *)0);
326
327 // If we're here, that clearly failed.
328
329 // TODO: We may also want to try detecting the case where
330 // xdg-open is successfully executed but returns an error code.
331
332 // Try xterm+less next
333
334 execlp("xterm", "xterm", "-e", "less", _logFilePath.c_str(), (char *)0);
335
336 // TODO: If less does not exist we could fall back to 'more'.
337 // However, we'll have to use 'xterm -hold' for that to prevent the
338 // terminal from closing immediately (for short log files) or
339 // unexpectedly.
340
341 exit(127);
342 }
343
344 int status;
345 // Wait for viewer to close.
346 // (But note that xdg-open may have spawned a viewer in the background.)
347
348 // FIXME: We probably want the viewer to always open in the background.
349 // This may require installing a SIGCHLD handler.
350 pid = waitpid(pid, &status, 0);
351
352 if (pid < 0) {
353 // Probably nothing sensible to do in this error situation
354 return false;
355 }
356
357 return WIFEXITED(status) && WEXITSTATUS(status) == 0;
358 }
359
360 #ifdef HAS_POSIX_SPAWN
openUrl(const Common::String & url)361 bool OSystem_POSIX::openUrl(const Common::String &url) {
362 // inspired by Qt's "qdesktopservices_x11.cpp"
363
364 // try "standards"
365 if (launchBrowser("xdg-open", url))
366 return true;
367 if (launchBrowser(getenv("DEFAULT_BROWSER"), url))
368 return true;
369 if (launchBrowser(getenv("BROWSER"), url))
370 return true;
371
372 // try desktop environment specific tools
373 if (launchBrowser("gnome-open", url)) // gnome
374 return true;
375 if (launchBrowser("kfmclient", url)) // kde
376 return true;
377 if (launchBrowser("exo-open", url)) // xfce
378 return true;
379
380 // try browser names
381 if (launchBrowser("firefox", url))
382 return true;
383 if (launchBrowser("mozilla", url))
384 return true;
385 if (launchBrowser("netscape", url))
386 return true;
387 if (launchBrowser("opera", url))
388 return true;
389 if (launchBrowser("chromium-browser", url))
390 return true;
391 if (launchBrowser("google-chrome", url))
392 return true;
393
394 warning("openUrl() (POSIX) failed to open URL");
395 return false;
396 }
397
launchBrowser(const Common::String & client,const Common::String & url)398 bool OSystem_POSIX::launchBrowser(const Common::String &client, const Common::String &url) {
399 pid_t pid;
400 const char *argv[] = {
401 client.c_str(),
402 url.c_str(),
403 NULL,
404 NULL
405 };
406 if (client == "kfmclient") {
407 argv[2] = argv[1];
408 argv[1] = "openURL";
409 }
410 if (posix_spawnp(&pid, client.c_str(), NULL, NULL, const_cast<char **>(argv), environ) != 0) {
411 return false;
412 }
413 return (waitpid(pid, NULL, WNOHANG) != -1);
414 }
415 #endif
416
createAudioCDManager()417 AudioCDManager *OSystem_POSIX::createAudioCDManager() {
418 #ifdef USE_LINUXCD
419 return createLinuxAudioCDManager();
420 #else
421 return OSystem_SDL::createAudioCDManager();
422 #endif
423 }
424
425 #endif
426