1 /*
2   SDL_mixer:  An audio mixer library based on the SDL library
3   Copyright (C) 1997-2012 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "SDL_config.h"
22 
23 /* This file supports an external command for playing music */
24 
25 #ifdef CMD_MUSIC
26 
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <string.h>
33 #include <signal.h>
34 #include <ctype.h>
35 
36 #include "SDL_mixer.h"
37 #include "music_cmd.h"
38 
39 /* Unimplemented */
MusicCMD_SetVolume(int volume)40 void MusicCMD_SetVolume(int volume)
41 {
42 	Mix_SetError("No way to modify external player volume");
43 }
44 
45 /* Load a music stream from the given file */
MusicCMD_LoadSong(const char * cmd,const char * file)46 MusicCMD *MusicCMD_LoadSong(const char *cmd, const char *file)
47 {
48 	MusicCMD *music;
49 
50 	/* Allocate and fill the music structure */
51 	music = (MusicCMD *)SDL_malloc(sizeof *music);
52 	if ( music == NULL ) {
53 		Mix_SetError("Out of memory");
54 		return(NULL);
55 	}
56 	strncpy(music->file, file, (sizeof music->file)-1);
57 	music->file[(sizeof music->file)-1] = '\0';
58 	strncpy(music->cmd, cmd, (sizeof music->cmd)-1);
59 	music->cmd[(sizeof music->cmd)-1] = '\0';
60 	music->pid = 0;
61 
62 	/* We're done */
63 	return(music);
64 }
65 
66 /* Parse a command line buffer into arguments */
ParseCommandLine(char * cmdline,char ** argv)67 static int ParseCommandLine(char *cmdline, char **argv)
68 {
69 	char *bufp;
70 	int argc;
71 
72 	argc = 0;
73 	for ( bufp = cmdline; *bufp; ) {
74 		/* Skip leading whitespace */
75 		while ( isspace(*bufp) ) {
76 			++bufp;
77 		}
78 		/* Skip over argument */
79 		if ( *bufp == '"' ) {
80 			++bufp;
81 			if ( *bufp ) {
82 				if ( argv ) {
83 					argv[argc] = bufp;
84 				}
85 				++argc;
86 			}
87 			/* Skip over word */
88 			while ( *bufp && (*bufp != '"') ) {
89 				++bufp;
90 			}
91 		} else {
92 			if ( *bufp ) {
93 				if ( argv ) {
94 					argv[argc] = bufp;
95 				}
96 				++argc;
97 			}
98 			/* Skip over word */
99 			while ( *bufp && ! isspace(*bufp) ) {
100 				++bufp;
101 			}
102 		}
103 		if ( *bufp ) {
104 			if ( argv ) {
105 				*bufp = '\0';
106 			}
107 			++bufp;
108 		}
109 	}
110 	if ( argv ) {
111 		argv[argc] = NULL;
112 	}
113 	return(argc);
114 }
115 
parse_args(char * command,char * last_arg)116 static char **parse_args(char *command, char *last_arg)
117 {
118 	int argc;
119 	char **argv;
120 
121 	/* Parse the command line */
122 	argc = ParseCommandLine(command, NULL);
123 	if ( last_arg ) {
124 		++argc;
125 	}
126 	argv = (char **)SDL_malloc((argc+1)*(sizeof *argv));
127 	if ( argv == NULL ) {
128 		return(NULL);
129 	}
130 	argc = ParseCommandLine(command, argv);
131 
132 	/* Add last command line argument */
133 	if ( last_arg ) {
134 		argv[argc++] = last_arg;
135 	}
136 	argv[argc] = NULL;
137 
138 	/* We're ready! */
139 	return(argv);
140 }
141 
142 /* Start playback of a given music stream */
MusicCMD_Start(MusicCMD * music)143 void MusicCMD_Start(MusicCMD *music)
144 {
145 #ifdef HAVE_FORK
146 	music->pid = fork();
147 #else
148 	music->pid = vfork();
149 #endif
150 	switch(music->pid) {
151 	    /* Failed fork() system call */
152 	    case -1:
153 		Mix_SetError("fork() failed");
154 		return;
155 
156 	    /* Child process - executes here */
157 	    case 0: {
158 		    char command[PATH_MAX];
159 		    char **argv;
160 
161 		    /* Unblock signals in case we're called from a thread */
162 		    {
163 			sigset_t mask;
164 			sigemptyset(&mask);
165 			sigprocmask(SIG_SETMASK, &mask, NULL);
166 		    }
167 
168 		    /* Execute the command */
169 		    strcpy(command, music->cmd);
170 		    argv = parse_args(command, music->file);
171 		    if ( argv != NULL ) {
172 			execvp(argv[0], argv);
173 		    }
174 
175 		    /* exec() failed */
176 		    perror(argv[0]);
177 		    _exit(-1);
178 		}
179 		break;
180 
181 	    /* Parent process - executes here */
182 	    default:
183 		break;
184 	}
185 	return;
186 }
187 
188 /* Stop playback of a stream previously started with MusicCMD_Start() */
MusicCMD_Stop(MusicCMD * music)189 void MusicCMD_Stop(MusicCMD *music)
190 {
191 	int status;
192 
193 	if ( music->pid > 0 ) {
194 		while ( kill(music->pid, 0) == 0 ) {
195 			kill(music->pid, SIGTERM);
196 			sleep(1);
197 			waitpid(music->pid, &status, WNOHANG);
198 		}
199 		music->pid = 0;
200 	}
201 }
202 
203 /* Pause playback of a given music stream */
MusicCMD_Pause(MusicCMD * music)204 void MusicCMD_Pause(MusicCMD *music)
205 {
206 	if ( music->pid > 0 ) {
207 		kill(music->pid, SIGSTOP);
208 	}
209 }
210 
211 /* Resume playback of a given music stream */
MusicCMD_Resume(MusicCMD * music)212 void MusicCMD_Resume(MusicCMD *music)
213 {
214 	if ( music->pid > 0 ) {
215 		kill(music->pid, SIGCONT);
216 	}
217 }
218 
219 /* Close the given music stream */
MusicCMD_FreeSong(MusicCMD * music)220 void MusicCMD_FreeSong(MusicCMD *music)
221 {
222 	SDL_free(music);
223 }
224 
225 /* Return non-zero if a stream is currently playing */
MusicCMD_Active(MusicCMD * music)226 int MusicCMD_Active(MusicCMD *music)
227 {
228 	int status;
229 	int active;
230 
231 	active = 0;
232 	if ( music->pid > 0 ) {
233 		waitpid(music->pid, &status, WNOHANG);
234 		if ( kill(music->pid, 0) == 0 ) {
235 			active = 1;
236 		}
237 	}
238 	return(active);
239 }
240 
241 #endif /* CMD_MUSIC */
242