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