1 /*
2 SDL_mixer: An audio mixer library based on the SDL library
3 Copyright (C) 1997-2018 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 MUSIC_CMD
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 #include <limits.h>
36 #if defined(__linux__) && defined(__arm__)
37 # include <linux/limits.h>
38 #endif
39
40 #include "music_cmd.h"
41
42
43 typedef struct {
44 char *file;
45 char *cmd;
46 pid_t pid;
47 int play_count;
48 } MusicCMD;
49
50
51 /* Load a music stream from the given file */
MusicCMD_CreateFromFile(const char * file)52 static void *MusicCMD_CreateFromFile(const char *file)
53 {
54 MusicCMD *music;
55
56 if (!music_cmd) {
57 Mix_SetError("You must call Mix_SetMusicCMD() first");
58 return NULL;
59 }
60
61 /* Allocate and fill the music structure */
62 music = (MusicCMD *)SDL_calloc(1, sizeof *music);
63 if (music == NULL) {
64 Mix_SetError("Out of memory");
65 return NULL;
66 }
67 music->file = SDL_strdup(file);
68 music->cmd = SDL_strdup(music_cmd);
69 music->pid = 0;
70
71 /* We're done */
72 return music;
73 }
74
75 /* Parse a command line buffer into arguments */
ParseCommandLine(char * cmdline,char ** argv)76 static int ParseCommandLine(char *cmdline, char **argv)
77 {
78 char *bufp;
79 int argc;
80
81 argc = 0;
82 for (bufp = cmdline; *bufp;) {
83 /* Skip leading whitespace */
84 while (isspace(*bufp)) {
85 ++bufp;
86 }
87 /* Skip over argument */
88 if (*bufp == '"') {
89 ++bufp;
90 if (*bufp) {
91 if (argv) {
92 argv[argc] = bufp;
93 }
94 ++argc;
95 }
96 /* Skip over word */
97 while (*bufp && (*bufp != '"')) {
98 ++bufp;
99 }
100 } else {
101 if (*bufp) {
102 if (argv) {
103 argv[argc] = bufp;
104 }
105 ++argc;
106 }
107 /* Skip over word */
108 while (*bufp && ! isspace(*bufp)) {
109 ++bufp;
110 }
111 }
112 if (*bufp) {
113 if (argv) {
114 *bufp = '\0';
115 }
116 ++bufp;
117 }
118 }
119 if (argv) {
120 argv[argc] = NULL;
121 }
122 return(argc);
123 }
124
parse_args(char * command,char * last_arg)125 static char **parse_args(char *command, char *last_arg)
126 {
127 int argc;
128 char **argv;
129
130 /* Parse the command line */
131 argc = ParseCommandLine(command, NULL);
132 if (last_arg) {
133 ++argc;
134 }
135 argv = (char **)SDL_malloc((argc+1)*(sizeof *argv));
136 if (argv == NULL) {
137 return(NULL);
138 }
139 argc = ParseCommandLine(command, argv);
140
141 /* Add last command line argument */
142 if (last_arg) {
143 argv[argc++] = last_arg;
144 }
145 argv[argc] = NULL;
146
147 /* We're ready! */
148 return(argv);
149 }
150
151 /* Start playback of a given music stream */
MusicCMD_Play(void * context,int play_count)152 static int MusicCMD_Play(void *context, int play_count)
153 {
154 MusicCMD *music = (MusicCMD *)context;
155
156 music->play_count = play_count;
157 #ifdef HAVE_FORK
158 music->pid = fork();
159 #else
160 music->pid = vfork();
161 #endif
162 switch(music->pid) {
163 /* Failed fork() system call */
164 case -1:
165 Mix_SetError("fork() failed");
166 return -1;
167
168 /* Child process - executes here */
169 case 0: {
170 char **argv;
171
172 /* Unblock signals in case we're called from a thread */
173 {
174 sigset_t mask;
175 sigemptyset(&mask);
176 sigprocmask(SIG_SETMASK, &mask, NULL);
177 }
178
179 /* Execute the command */
180 argv = parse_args(music->cmd, music->file);
181 if (argv != NULL) {
182 execvp(argv[0], argv);
183
184 /* exec() failed */
185 perror(argv[0]);
186 }
187 _exit(-1);
188 }
189 break;
190
191 /* Parent process - executes here */
192 default:
193 break;
194 }
195 return 0;
196 }
197
198 /* Return non-zero if a stream is currently playing */
MusicCMD_IsPlaying(void * context)199 static SDL_bool MusicCMD_IsPlaying(void *context)
200 {
201 MusicCMD *music = (MusicCMD *)context;
202 int status;
203
204 if (music->pid > 0) {
205 waitpid(music->pid, &status, WNOHANG);
206 if (kill(music->pid, 0) == 0) {
207 return SDL_TRUE;
208 }
209
210 /* We might want to loop */
211 if (music->play_count != 1) {
212 int play_count = -1;
213 if (music->play_count > 0) {
214 play_count = (music->play_count - 1);
215 }
216 MusicCMD_Play(music, play_count);
217 return SDL_TRUE;
218 }
219 }
220 return SDL_FALSE;
221 }
222
223 /* Pause playback of a given music stream */
MusicCMD_Pause(void * context)224 static void MusicCMD_Pause(void *context)
225 {
226 MusicCMD *music = (MusicCMD *)context;
227 if (music->pid > 0) {
228 kill(music->pid, SIGSTOP);
229 }
230 }
231
232 /* Resume playback of a given music stream */
MusicCMD_Resume(void * context)233 static void MusicCMD_Resume(void *context)
234 {
235 MusicCMD *music = (MusicCMD *)context;
236 if (music->pid > 0) {
237 kill(music->pid, SIGCONT);
238 }
239 }
240
241 /* Stop playback of a stream previously started with MusicCMD_Start() */
MusicCMD_Stop(void * context)242 static void MusicCMD_Stop(void *context)
243 {
244 MusicCMD *music = (MusicCMD *)context;
245 int status;
246
247 if (music->pid > 0) {
248 while (kill(music->pid, 0) == 0) {
249 kill(music->pid, SIGTERM);
250 sleep(1);
251 waitpid(music->pid, &status, WNOHANG);
252 }
253 music->pid = 0;
254 }
255 }
256
257 /* Close the given music stream */
MusicCMD_Delete(void * context)258 void MusicCMD_Delete(void *context)
259 {
260 MusicCMD *music = (MusicCMD *)context;
261 SDL_free(music->file);
262 SDL_free(music);
263 }
264
265 Mix_MusicInterface Mix_MusicInterface_CMD =
266 {
267 "CMD",
268 MIX_MUSIC_CMD,
269 MUS_CMD,
270 SDL_FALSE,
271 SDL_FALSE,
272
273 NULL, /* Load */
274 NULL, /* Open */
275 NULL, /* CreateFromRW */
276 MusicCMD_CreateFromFile,
277 NULL, /* SetVolume */
278 MusicCMD_Play,
279 MusicCMD_IsPlaying,
280 NULL, /* GetAudio */
281 NULL, /* Seek */
282 MusicCMD_Pause,
283 MusicCMD_Resume,
284 MusicCMD_Stop,
285 MusicCMD_Delete,
286 NULL, /* Close */
287 NULL, /* Unload */
288 };
289
290 #endif /* MUSIC_CMD */
291
292 /* vi: set ts=4 sw=4 expandtab: */
293