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