From 4f43140468cefba39573d1efbded5258fcc56c93 Mon Sep 17 00:00:00 2001 From: MichaÅ‚ CichoÅ„ Date: Tue, 25 Aug 2015 06:47:41 +0200 Subject: Port pianobar to Windows: - use newly introduced console.h instead of terminal.h which emulate some behavior of VT terminals - replace ffmpeg/libov player with more abstract one with DirectShow implementation --- src/console.c | 419 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/console.h | 45 ++++++ src/main.c | 278 ++++++++++++--------------------- src/main.h | 6 +- src/player.c | 451 ------------------------------------------------------ src/player.h | 91 ----------- src/player2.c | 364 +++++++++++++++++++++++++++++++++++++++++++ src/player2.h | 52 +++++++ src/settings.c | 75 ++++----- src/settings.h | 6 +- src/terminal.c | 58 ------- src/terminal.h | 30 ---- src/ui.c | 218 +++++++++++++------------- src/ui.h | 6 +- src/ui_act.c | 77 ++++------ src/ui_readline.c | 396 ++++++++++++++++++++++++++++++----------------- src/ui_readline.h | 20 +-- 17 files changed, 1425 insertions(+), 1167 deletions(-) create mode 100644 src/console.c create mode 100644 src/console.h delete mode 100644 src/player.c delete mode 100644 src/player.h create mode 100644 src/player2.c create mode 100644 src/player2.h delete mode 100644 src/terminal.c delete mode 100644 src/terminal.h (limited to 'src') diff --git a/src/console.c b/src/console.c new file mode 100644 index 0000000..09d3116 --- /dev/null +++ b/src/console.c @@ -0,0 +1,419 @@ +/* +Copyright (c) 2015 + Micha³ Cichoñ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include "console.h" +#include +#include +#include +#include +#include +#include "vtparse/vtparse.h" + +enum { READ = 0, WRITE }; + +# define BAR_IN_CAPACITY 1024 +# define BAR_OUT_CAPACITY 1024 + +static const int BarTerminalToAttibColor[8] = { + 0 /* black */, 4 /* red */, 2 /* green */, 6 /* yellow */, + 1 /* blue */, 5 /* magenta */, 3 /* cyan */, 7 /* white */ +}; + +static struct BarConsoleState { + HANDLE StdIn; + HANDLE StdOut; + + int Pipes[2]; + int OriginalStdOut; + int OriginalStdErr; + + WORD DefaultAttributes; + WORD CurrentAttributes; + + pthread_t ConsoleThread; + bool Spawned; + bool Terminate; + + vtparse_t Parser; + + char InBuffer[BAR_IN_CAPACITY]; + size_t InBufferSize; + + char OutBuffer[BAR_OUT_CAPACITY]; + size_t OutBufferSize; +} g_BarConsole; + +static inline void BarOutSetAttributes(WORD attributes) { + g_BarConsole.CurrentAttributes = attributes; + SetConsoleTextAttribute(CONSOLE_REAL_OUTPUT_HANDLE, g_BarConsole.CurrentAttributes); +} + +static inline void BarOutFlush() { + if (g_BarConsole.OutBufferSize == 0) + return; + + _write(g_BarConsole.OriginalStdOut, g_BarConsole.OutBuffer, g_BarConsole.OutBufferSize); + + g_BarConsole.OutBufferSize = 0; +} + +static inline void BarOutPut(char c) { + if (g_BarConsole.OutBufferSize >= BAR_OUT_CAPACITY) + BarOutFlush(); + + g_BarConsole.OutBuffer[g_BarConsole.OutBufferSize++] = c; +} + +static inline void BarOutPuts(const char* c) { + size_t length = strlen(c); + size_t i; + + for (i = 0; i < length; ++i) + BarOutPut(c[i]); +} + +static void BarParseCallback(struct vtparse* parser, vtparse_action_t action, unsigned char ch) { + if (action == VTPARSE_ACTION_PRINT || action == VTPARSE_ACTION_EXECUTE) + { + BarOutPut(ch); + if (action == VTPARSE_ACTION_EXECUTE) + BarOutFlush(); + } + + if (action == VTPARSE_ACTION_CSI_DISPATCH) + { + WORD attribute = g_BarConsole.CurrentAttributes; + int i; + + switch (ch) + { + case 'K': + BarConsoleEraseLine(parser->num_params > 0 ? parser->params[0] : 0); + break; + + case 'm': + for (i = 0; i < parser->num_params; ++i) { + int p = parser->params[i]; + if (p == 0) + attribute = g_BarConsole.DefaultAttributes; + //else if (p == 1) + // attribute |= FOREGROUND_INTENSITY; + else if (p == 4) + attribute |= COMMON_LVB_UNDERSCORE; + else if (p == 7) + attribute |= COMMON_LVB_REVERSE_VIDEO; + //else if (p == 21) + // attribute &= ~FOREGROUND_INTENSITY; + else if (p == 24) + attribute &= ~COMMON_LVB_UNDERSCORE; + else if (p == 27) + attribute &= ~COMMON_LVB_REVERSE_VIDEO; + else if (p >= 30 && p <= 37) + attribute = (attribute & ~0x07) | BarTerminalToAttibColor[p - 30]; + else if (p >= 40 && p <= 47) + attribute = (attribute & ~0x70) | (BarTerminalToAttibColor[p - 40] << 4); + else if (p >= 90 && p <= 97) + attribute = (attribute & ~0x07) | BarTerminalToAttibColor[p - 90] | FOREGROUND_INTENSITY; + else if (p >= 100 && p <= 107) + attribute = ((attribute & ~0x70) | (BarTerminalToAttibColor[p - 100] << 4)) | BACKGROUND_INTENSITY; + } + BarOutFlush(); + BarOutSetAttributes(attribute); + break; + } + } +} + +static void* BarConsoleThread(void* args) { + + while (!g_BarConsole.Terminate) { + + int bytes = _read(g_BarConsole.Pipes[READ], g_BarConsole.InBuffer, BAR_IN_CAPACITY); + + if (bytes > 0) + vtparse(&g_BarConsole.Parser, g_BarConsole.InBuffer, bytes); + + BarOutFlush(); + } + + return NULL; +} + +void BarConsoleInit() { + CONSOLE_SCREEN_BUFFER_INFO csbi; + memset(&g_BarConsole, 0, sizeof(g_BarConsole)); + + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + + g_BarConsole.StdIn = GetStdHandle(STD_INPUT_HANDLE); + g_BarConsole.StdOut = GetStdHandle(STD_OUTPUT_HANDLE); + + GetConsoleScreenBufferInfo(g_BarConsole.StdOut, &csbi); + + g_BarConsole.DefaultAttributes = csbi.wAttributes; + g_BarConsole.CurrentAttributes = csbi.wAttributes; + + if (_pipe(g_BarConsole.Pipes, 65536, O_BINARY) != -1) { + g_BarConsole.OriginalStdOut = _dup(_fileno(stdout)); + g_BarConsole.OriginalStdErr = _dup(_fileno(stderr)); + + fflush(stdout); + fflush(stderr); + + dup2(g_BarConsole.Pipes[WRITE], fileno(stdout)); + dup2(g_BarConsole.Pipes[WRITE], fileno(stderr)); + + g_BarConsole.Spawned = pthread_create(&g_BarConsole.ConsoleThread, NULL, BarConsoleThread, NULL) == 0; + if (!g_BarConsole.Spawned) + BarConsoleDestroy(); + } + + vtparse_init(&g_BarConsole.Parser, BarParseCallback); +} + +void BarConsoleDestroy() { + if (g_BarConsole.Spawned) { + g_BarConsole.Terminate = true; + fputs(" ", stderr); + fputs(" ", stdout); + + fflush(stdout); + fflush(stderr); + + pthread_join(g_BarConsole.ConsoleThread, NULL); + } + + if (g_BarConsole.OriginalStdErr > 0) { + fflush(stderr); + dup2(g_BarConsole.OriginalStdErr, fileno(stderr)); + _close(g_BarConsole.OriginalStdErr); + g_BarConsole.OriginalStdErr = 0; + } + if (g_BarConsole.OriginalStdOut > 0) { + fflush(stdout); + dup2(g_BarConsole.OriginalStdOut, fileno(stdout)); + _close(g_BarConsole.OriginalStdOut); + g_BarConsole.OriginalStdOut = 0; + } + if (g_BarConsole.Pipes[0] > 0) { + _close(g_BarConsole.Pipes[0]); + g_BarConsole.Pipes[0] = 0; + } + if (g_BarConsole.Pipes[1] > 0) { + _close(g_BarConsole.Pipes[1]); + g_BarConsole.Pipes[1] = 0; + } +} + +HANDLE BarConsoleGetStdIn () { + return g_BarConsole.StdIn; +} + +HANDLE BarConsoleGetStdOut () { + return GetStdHandle(STD_OUTPUT_HANDLE);//g_BarConsole.StdOut; +} + +void BarConsoleSetTitle (const char* title) { + size_t len = MultiByteToWideChar (CP_UTF8, 0, title, -1, NULL, 0); + + TCHAR* wTitle = malloc((len + 1) * sizeof(TCHAR)); + if (NULL != wTitle) + { + MultiByteToWideChar (CP_UTF8, 0, title, -1, wTitle, len); + SetConsoleTitleW (wTitle); + + free(wTitle); + } + else + SetConsoleTitleA (title); +} + +void BarConsoleSetSize (int width, int height) { + HANDLE handle; + SMALL_RECT r; + COORD c, s; + CONSOLE_SCREEN_BUFFER_INFO csbi; + + handle = CONSOLE_REAL_OUTPUT_HANDLE; + + if (!GetConsoleScreenBufferInfo(handle, &csbi)) + return; + + s.X = csbi.srWindow.Right - csbi.srWindow.Left + 1; + s.Y = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + + if (s.X > width && s.Y > height) + return; + + c.X = width; + c.Y = height; + + if (s.X > c.X) + c.X = s.X; + if (s.Y > c.Y) + c.Y = s.Y; + + SetConsoleScreenBufferSize(handle, c); + + r.Left = 0; + r.Top = 0; + r.Right = c.X - 1; + r.Bottom = c.Y - 1; + SetConsoleWindowInfo(handle, TRUE, &r); +} + +void BarConsoleSetCursorPosition (COORD position) { + SetConsoleCursorPosition(CONSOLE_REAL_OUTPUT_HANDLE, position); +} + +COORD BarConsoleGetCursorPosition () { + CONSOLE_SCREEN_BUFFER_INFO consoleInfo; + COORD result = { -1, -1 }; + + if (GetConsoleScreenBufferInfo(CONSOLE_REAL_OUTPUT_HANDLE, &consoleInfo)) + result = consoleInfo.dwCursorPosition; + + return result; +} + +COORD BarConsoleMoveCursor (int xoffset) { + COORD position; + + position = BarConsoleGetCursorPosition(); + position.X += xoffset; + BarConsoleSetCursorPosition(position); + + return position; +} + +void BarConsoleEraseCharacter () { + TCHAR buffer[256]; + WORD buffer2[256]; + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + COORD coords; + HANDLE handle = CONSOLE_REAL_OUTPUT_HANDLE; + int length, read, write; + + if (!GetConsoleScreenBufferInfo(handle, &csbiInfo)) + return; + + length = csbiInfo.dwSize.X - csbiInfo.dwCursorPosition.X - 1; + read = csbiInfo.dwCursorPosition.X + 1; + write = csbiInfo.dwCursorPosition.X; + + while (length >= 0) { + int size = min(length, 256); + DWORD chRead = 0, chWritten = 0; + coords = csbiInfo.dwCursorPosition; + coords.X = read; + ReadConsoleOutputAttribute(handle, buffer2, size, coords, &chRead); + ReadConsoleOutputCharacter(handle, buffer, size, coords, &chRead); + + if (chRead == 0) + break; + + coords.X = write; + WriteConsoleOutputAttribute(handle, buffer2, chRead, coords, &chWritten); + WriteConsoleOutputCharacter(handle, buffer, chRead, coords, &chWritten); + + read += chRead; + write += chRead; + length -= chRead; + } +} + +void BarConsoleEraseLine (int mode) { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + DWORD writen, length; + COORD coords; + + HANDLE handle = CONSOLE_REAL_OUTPUT_HANDLE; + + if (!GetConsoleScreenBufferInfo(handle, &csbiInfo)) + return; + + writen = 0; + coords.X = 0; + coords.Y = csbiInfo.dwCursorPosition.Y; + + switch (mode) { + default: + case 0: /* from cursor */ + coords.X = BarConsoleGetCursorPosition().X; + length = csbiInfo.dwSize.X - coords.X; + break; + + case 1: /* to cursor */ + coords.X = 0; + length = BarConsoleGetCursorPosition().X; + break; + + case 2: /* whole line */ + coords.X = 0; + length = csbiInfo.dwSize.X; + break; + } + + FillConsoleOutputCharacter(handle, ' ', length, coords, &writen); + FillConsoleOutputAttribute(handle, csbiInfo.wAttributes, csbiInfo.dwSize.X, coords, &writen); + + SetConsoleCursorPosition(handle, coords); +} + +void BarConsoleSetClipboard(const char* text) { + WCHAR* wideString; + HANDLE stringHandle; + size_t wideSize; + + wideSize = MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0); + wideString = calloc(1, (wideSize + 1) * sizeof(WCHAR)); + if (!wideString) + return; + + MultiByteToWideChar(CP_UTF8, 0, text, -1, wideString, wideSize); + + stringHandle = GlobalAlloc(GMEM_MOVEABLE, wideSize); + if (!stringHandle) { + free(wideString); + return; + } + + memcpy(GlobalLock(stringHandle), wideString, wideSize); + + GlobalUnlock(stringHandle); + + if (!OpenClipboard(NULL)) { + GlobalFree(stringHandle); + free(wideString); + return; + } + + EmptyClipboard(); + + SetClipboardData(CF_UNICODETEXT, stringHandle); + + CloseClipboard(); + + free(wideString); +} \ No newline at end of file diff --git a/src/console.h b/src/console.h new file mode 100644 index 0000000..4b3a073 --- /dev/null +++ b/src/console.h @@ -0,0 +1,45 @@ +/* +Copyright (c) 2015 + Micha³ Cichoñ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#ifndef SRC_CONSOLE_H_WY8F3MNH +#define SRC_CONSOLE_H_WY8F3MNH + +#include "config.h" + +#include + +void BarConsoleInit (); +void BarConsoleDestroy (); +HANDLE BarConsoleGetStdIn (); +HANDLE BarConsoleGetStdOut (); +void BarConsoleSetTitle (const char* title); +void BarConsoleSetSize (int width, int height); +void BarConsoleSetCursorPosition (COORD position); +COORD BarConsoleGetCursorPosition (); +COORD BarConsoleMoveCursor (int xoffset); +void BarConsoleEraseCharacter (); +void BarConsoleEraseLine (int mode); // 0 - from cursor, 1 - to cursor, 2 - entire line + +void BarConsoleSetClipboard (const char*); + +#endif /* SRC_CONSOLE_H_WY8F3MNH */ diff --git a/src/main.c b/src/main.c index b113f4e..4800d2d 100644 --- a/src/main.c +++ b/src/main.c @@ -23,35 +23,11 @@ THE SOFTWARE. #include "config.h" -/* system includes */ -#include -#include -#include -/* fork () */ -#include -#include -#include -#include -/* open () */ -#include -#include -#include -/* tcset/getattr () */ -#include -#include #include -#include -#include -#include -/* waitpid () */ -#include -#include - -/* pandora.com library */ #include #include "main.h" -#include "terminal.h" +#include "console.h" #include "ui.h" #include "ui_dispatch.h" #include "ui_readline.h" @@ -79,14 +55,14 @@ static bool BarMainLoginUser (BarApp_t *app) { /* ask for username/password if none were provided in settings */ static bool BarMainGetLoginCredentials (BarSettings_t *settings, - BarReadlineFds_t *input) { + BarReadline_t rl) { bool usernameFromConfig = true; if (settings->username == NULL) { char nameBuf[100]; BarUiMsg (settings, MSG_QUESTION, "Email: "); - BarReadlineStr (nameBuf, sizeof (nameBuf), input, BAR_RL_DEFAULT); + BarReadlineStr (nameBuf, sizeof (nameBuf), rl, BAR_RL_DEFAULT); settings->username = strdup (nameBuf); usernameFromConfig = false; } @@ -100,58 +76,59 @@ static bool BarMainGetLoginCredentials (BarSettings_t *settings, if (settings->passwordCmd == NULL) { BarUiMsg (settings, MSG_QUESTION, "Password: "); - BarReadlineStr (passBuf, sizeof (passBuf), input, BAR_RL_NOECHO); + BarReadlineStr (passBuf, sizeof (passBuf), rl, BAR_RL_NOECHO); /* write missing newline */ puts (""); settings->password = strdup (passBuf); } else { - pid_t chld; - int pipeFd[2]; - - BarUiMsg (settings, MSG_INFO, "Requesting password from external helper... "); - - if (pipe (pipeFd) == -1) { - BarUiMsg (settings, MSG_NONE, "Error: %s\n", strerror (errno)); - return false; - } - - chld = fork (); - if (chld == 0) { - /* child */ - close (pipeFd[0]); - dup2 (pipeFd[1], fileno (stdout)); - execl ("/bin/sh", "/bin/sh", "-c", settings->passwordCmd, (char *) NULL); - BarUiMsg (settings, MSG_NONE, "Error: %s\n", strerror (errno)); - close (pipeFd[1]); - exit (1); - } else if (chld == -1) { - BarUiMsg (settings, MSG_NONE, "Error: %s\n", strerror (errno)); - return false; - } else { - /* parent */ - int status; - - close (pipeFd[1]); - memset (passBuf, 0, sizeof (passBuf)); - read (pipeFd[0], passBuf, sizeof (passBuf)-1); - close (pipeFd[0]); - - /* drop trailing newlines */ - ssize_t len = strlen (passBuf)-1; - while (len >= 0 && passBuf[len] == '\n') { - passBuf[len] = '\0'; - --len; - } - - waitpid (chld, &status, 0); - if (WEXITSTATUS (status) == 0) { - settings->password = strdup (passBuf); - BarUiMsg (settings, MSG_NONE, "Ok.\n"); - } else { - BarUiMsg (settings, MSG_NONE, "Error: Exit status %i.\n", WEXITSTATUS (status)); - return false; - } - } + //pid_t chld; + //int pipeFd[2]; + + //BarUiMsg (settings, MSG_INFO, "Requesting password from external helper... "); + + //if (pipe (pipeFd) == -1) { + // BarUiMsg (settings, MSG_NONE, "Error: %s\n", strerror (errno)); + // return false; + //} + + //chld = fork (); + //if (chld == 0) { + // /* child */ + // close (pipeFd[0]); + // dup2 (pipeFd[1], fileno (stdout)); + // execl ("/bin/sh", "/bin/sh", "-c", settings->passwordCmd, (char *) NULL); + // BarUiMsg (settings, MSG_NONE, "Error: %s\n", strerror (errno)); + // close (pipeFd[1]); + // exit (1); + //} else if (chld == -1) { + // BarUiMsg (settings, MSG_NONE, "Error: %s\n", strerror (errno)); + // return false; + //} else { + // /* parent */ + // int status; + + // close (pipeFd[1]); + // memset (passBuf, 0, sizeof (passBuf)); + // read (pipeFd[0], passBuf, sizeof (passBuf)-1); + // close (pipeFd[0]); + + // /* drop trailing newlines */ + // ssize_t len = strlen (passBuf)-1; + // while (len >= 0 && passBuf[len] == '\n') { + // passBuf[len] = '\0'; + // --len; + // } + + // waitpid (chld, &status, 0); + // if (WEXITSTATUS (status) == 0) { + // settings->password = strdup (passBuf); + // BarUiMsg (settings, MSG_NONE, "Ok.\n"); + // } else { + // BarUiMsg (settings, MSG_NONE, "Error: Exit status %i.\n", WEXITSTATUS (status)); + // return false; + // } + //} + return false; } /* end else passwordCmd */ } @@ -172,7 +149,7 @@ static bool BarMainGetStations (BarApp_t *app) { return ret; } -/* get initial station from autostart setting or user input +/* get initial station from autostart setting or user rl */ static void BarMainGetInitialStation (BarApp_t *app) { /* try to get autostart station */ @@ -194,11 +171,11 @@ static void BarMainGetInitialStation (BarApp_t *app) { } } -/* wait for user input +/* wait for user rl */ static void BarMainHandleUserInput (BarApp_t *app) { char buf[2]; - if (BarReadline (buf, sizeof (buf), NULL, &app->input, + if (BarReadline (buf, sizeof (buf), NULL, app->rl, BAR_RL_FULLRETURN | BAR_RL_NOECHO, 1) > 0) { BarUiDispatch (app, buf[0], app->curStation, app->playlist, true, BAR_DC_GLOBAL); @@ -232,9 +209,8 @@ static void BarMainGetPlaylist (BarApp_t *app) { /* start new player thread */ -static void BarMainStartPlayback (BarApp_t *app, pthread_t *playerThread) { +static void BarMainStartPlayback (BarApp_t *app) { assert (app != NULL); - assert (playerThread != NULL); const PianoSong_t * const curSong = app->playlist; assert (curSong != NULL); @@ -249,86 +225,66 @@ static void BarMainStartPlayback (BarApp_t *app, pthread_t *playerThread) { strncmp (curSong->audioUrl, httpPrefix, strlen (httpPrefix)) != 0) { BarUiMsg (&app->settings, MSG_ERR, "Invalid song url.\n"); } else { - /* setup player */ - memset (&app->player, 0, sizeof (app->player)); - - app->player.url = curSong->audioUrl; - app->player.gain = curSong->fileGain; - app->player.settings = &app->settings; - app->player.songDuration = curSong->length; - pthread_mutex_init (&app->player.pauseMutex, NULL); - pthread_cond_init (&app->player.pauseCond, NULL); + BarPlayer2Open(app->player, curSong->audioUrl); + BarPlayer2SetGain(app->player, curSong->fileGain); /* throw event */ BarUiStartEventCmd (&app->settings, "songstart", app->curStation, curSong, &app->player, app->ph.stations, PIANO_RET_OK, CURLE_OK); - /* prevent race condition, mode must _not_ be DEAD if - * thread has been started */ - app->player.mode = PLAYER_WAITING; - /* start player */ - pthread_create (playerThread, NULL, BarPlayerThread, - &app->player); + BarPlayer2Play(app->player); } } /* player is done, clean up */ -static void BarMainPlayerCleanup (BarApp_t *app, pthread_t *playerThread) { - void *threadRet; - +static void BarMainPlayerCleanup (BarApp_t *app) { BarUiStartEventCmd (&app->settings, "songfinish", app->curStation, app->playlist, &app->player, app->ph.stations, PIANO_RET_OK, CURLE_OK); - /* FIXME: pthread_join blocks everything if network connection - * is hung up e.g. */ - pthread_join (*playerThread, &threadRet); - pthread_cond_destroy (&app->player.pauseCond); - pthread_mutex_destroy (&app->player.pauseMutex); - - if (threadRet == (void *) PLAYER_RET_OK) { - app->playerErrors = 0; - } else if (threadRet == (void *) PLAYER_RET_SOFTFAIL) { - ++app->playerErrors; - if (app->playerErrors >= app->settings.maxPlayerErrors) { - /* don't continue playback if thread reports too many error */ - app->curStation = NULL; - } - } else { - app->curStation = NULL; - } - - memset (&app->player, 0, sizeof (app->player)); + BarPlayer2Finish(app->player); + + //if (threadRet == (void *) PLAYER_RET_OK) { + // app->playerErrors = 0; + //} else if (threadRet == (void *) PLAYER_RET_SOFTFAIL) { + // ++app->playerErrors; + // if (app->playerErrors >= app->settings.maxPlayerErrors) { + // /* don't continue playback if thread reports too many error */ + // app->curStation = NULL; + // } + //} else { + // app->curStation = NULL; + //} } /* print song duration */ static void BarMainPrintTime (BarApp_t *app) { - unsigned int songRemaining; + double songPlayed, songDuration, songRemaining; char sign; - if (app->player.songPlayed <= app->player.songDuration) { - songRemaining = app->player.songDuration - app->player.songPlayed; + songDuration = BarPlayer2GetDuration(app->player); + songPlayed = BarPlayer2GetTime(app->player); + + if (songPlayed <= songDuration) { + songRemaining = songDuration - songPlayed; sign = '-'; } else { /* longer than expected */ - songRemaining = app->player.songPlayed - app->player.songDuration; + songRemaining = songPlayed - songDuration; sign = '+'; } BarUiMsg (&app->settings, MSG_TIME, "%c%02u:%02u/%02u:%02u\r", - sign, songRemaining / 60, songRemaining % 60, - app->player.songDuration / 60, - app->player.songDuration % 60); + sign, (int)songRemaining / 60, (int)songRemaining % 60, + (int)songDuration / 60, (int)songDuration % 60); } /* main loop */ static void BarMainLoop (BarApp_t *app) { - pthread_t playerThread; - - if (!BarMainGetLoginCredentials (&app->settings, &app->input)) { + if (!BarMainGetLoginCredentials (&app->settings, app->rl)) { return; } @@ -342,19 +298,15 @@ static void BarMainLoop (BarApp_t *app) { BarMainGetInitialStation (app); - /* little hack, needed to signal: hey! we need a playlist, but don't - * free anything (there is nothing to be freed yet) */ - memset (&app->player, 0, sizeof (app->player)); - while (!app->doQuit) { /* song finished playing, clean up things/scrobble song */ - if (app->player.mode == PLAYER_FINISHED) { - BarMainPlayerCleanup (app, &playerThread); + if (BarPlayer2IsStopped(app->player)) { + BarMainPlayerCleanup (app); } /* check whether player finished playing and start playing new * song */ - if (app->player.mode == PLAYER_DEAD && app->curStation != NULL) { + if (BarPlayer2IsFinished(app->player) && app->curStation != NULL) { /* what's next? */ if (app->playlist != NULL) { PianoSong_t *histsong = app->playlist; @@ -367,21 +319,17 @@ static void BarMainLoop (BarApp_t *app) { } /* song ready to play */ if (app->playlist != NULL) { - BarMainStartPlayback (app, &playerThread); + BarMainStartPlayback (app); } } BarMainHandleUserInput (app); /* show time */ - if (app->player.mode == PLAYER_PLAYING) { + if (BarPlayer2IsPlaying(app->player) || BarPlayer2IsPaused(app->player)) { BarMainPrintTime (app); } } - - if (app->player.mode != PLAYER_DEAD) { - pthread_join (playerThread, NULL); - } } int main (int argc, char **argv) { @@ -389,17 +337,10 @@ int main (int argc, char **argv) { memset (&app, 0, sizeof (app)); - /* save terminal attributes, before disabling echoing */ - BarTermInit (); - - /* signals */ - signal (SIGPIPE, SIG_IGN); + BarConsoleInit (); /* init some things */ - gcry_check_version (NULL); - gcry_control (GCRYCTL_DISABLE_SECMEM, 0); - gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); - BarPlayerInit (); + BarPlayer2Init (&app.player); BarSettingsInit (&app.settings); BarSettingsRead (&app.settings); @@ -414,7 +355,7 @@ int main (int argc, char **argv) { } BarUiMsg (&app.settings, MSG_NONE, - "Welcome to " PACKAGE " (" VERSION ")! "); + "Welcome to " PACKAGE " (" VERSION ")!\n"); if (app.settings.keys[BAR_KS_HELP] == BAR_KS_DISABLED) { BarUiMsg (&app.settings, MSG_NONE, "\n"); } else { @@ -427,38 +368,11 @@ int main (int argc, char **argv) { app.http = curl_easy_init (); assert (app.http != NULL); - /* init fds */ - FD_ZERO(&app.input.set); - app.input.fds[0] = STDIN_FILENO; - FD_SET(app.input.fds[0], &app.input.set); - - /* open fifo read/write so it won't EOF if nobody writes to it */ - assert (sizeof (app.input.fds) / sizeof (*app.input.fds) >= 2); - app.input.fds[1] = open (app.settings.fifo, O_RDWR); - if (app.input.fds[1] != -1) { - struct stat s; - - /* check for file type, must be fifo */ - fstat (app.input.fds[1], &s); - if (!S_ISFIFO (s.st_mode)) { - BarUiMsg (&app.settings, MSG_ERR, "File at %s is not a fifo\n", app.settings.fifo); - close (app.input.fds[1]); - app.input.fds[1] = -1; - } else { - FD_SET(app.input.fds[1], &app.input.set); - BarUiMsg (&app.settings, MSG_INFO, "Control fifo at %s opened\n", - app.settings.fifo); - } - } - app.input.maxfd = app.input.fds[0] > app.input.fds[1] ? app.input.fds[0] : - app.input.fds[1]; - ++app.input.maxfd; + BarReadlineInit (&app.rl); BarMainLoop (&app); - if (app.input.fds[1] != -1) { - close (app.input.fds[1]); - } + BarReadlineDestroy (app.rl); /* write statefile */ BarSettingsWrite (app.curStation, &app.settings); @@ -468,11 +382,9 @@ int main (int argc, char **argv) { PianoDestroyPlaylist (app.playlist); curl_easy_cleanup (app.http); curl_global_cleanup (); - BarPlayerDestroy (); + BarPlayer2Destroy (app.player); BarSettingsDestroy (&app.settings); - - /* restore terminal attributes, zsh doesn't need this, bash does... */ - BarTermRestore (); + BarConsoleDestroy (); return 0; } diff --git a/src/main.h b/src/main.h index c8134ae..41413d9 100644 --- a/src/main.h +++ b/src/main.h @@ -28,21 +28,21 @@ THE SOFTWARE. #include -#include "player.h" +#include "player2.h" #include "settings.h" #include "ui_readline.h" typedef struct { PianoHandle_t ph; CURL *http; - player_t player; + player2_t player; BarSettings_t settings; /* first item is current song */ PianoSong_t *playlist; PianoSong_t *songHistory; PianoStation_t *curStation; char doQuit; - BarReadlineFds_t input; + BarReadline_t rl; unsigned int playerErrors; } BarApp_t; diff --git a/src/player.c b/src/player.c deleted file mode 100644 index 0ba0f36..0000000 --- a/src/player.c +++ /dev/null @@ -1,451 +0,0 @@ -/* -Copyright (c) 2008-2014 - Lars-Dominik Braun - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -/* receive/play audio stream */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#ifdef HAVE_LIBAVFILTER_AVCODEC_H -/* required by ffmpeg1.2 for avfilter_copy_buf_props */ -#include -#endif -#include -#include -#ifndef HAVE_AV_TIMEOUT -#include -#endif - -#include "player.h" -#include "ui.h" -#include "ui_types.h" - -/* default sample format */ -const enum AVSampleFormat avformat = AV_SAMPLE_FMT_S16; - -static void printError (const BarSettings_t * const settings, - const char * const msg, int ret) { - char avmsg[128]; - av_strerror (ret, avmsg, sizeof (avmsg)); - BarUiMsg (settings, MSG_ERR, "%s (%s)\n", msg, avmsg); -} - -/* global initialization - * - * XXX: in theory we can select the filters/formats we want to support, but - * this does not work in practise. - */ -void BarPlayerInit () { - ao_initialize (); - av_register_all (); - avfilter_register_all (); - avformat_network_init (); -} - -void BarPlayerDestroy () { - avformat_network_deinit (); - avfilter_uninit (); - ao_shutdown (); -} - -/* Update volume filter - */ -void BarPlayerSetVolume (player_t * const player) { - assert (player != NULL); - - if (player->mode != PLAYER_PLAYING) { - return; - } - - int ret; -#ifdef HAVE_AVFILTER_GRAPH_SEND_COMMAND - /* ffmpeg and libav disagree on the type of this option (string vs. double) - * -> print to string and let them parse it again */ - char strbuf[16]; - snprintf (strbuf, sizeof (strbuf), "%fdB", - player->settings->volume + player->gain); - assert (player->fgraph != NULL); - if ((ret = avfilter_graph_send_command (player->fgraph, "volume", "volume", - strbuf, NULL, 0, 0)) < 0) { -#else - /* convert from decibel */ - const double volume = pow (10, (player->settings->volume + player->gain) / 20); - /* libav does not provide other means to set this right now. it might not - * even work everywhere. */ - assert (player->fvolume != NULL); - if ((ret = av_opt_set_double (player->fvolume->priv, "volume", volume, - 0)) != 0) { -#endif - printError (player->settings, "Cannot set volume", ret); - } -} - -#define softfail(msg) \ - printError (player->settings, msg, ret); \ - return false; - -#ifndef HAVE_AV_TIMEOUT -/* interrupt callback for libav, which lacks a timeout option - * - * obviously calling ping() a lot of times and then calling av_gettime here - * again is rather inefficient. - */ -static int intCb (void * const data) { - player_t * const player = data; - assert (player != NULL); - /* 10 seconds timeout (usec) */ - return (av_gettime () - player->ping) > 10*1000000; -} - -#define ping() player->ping = av_gettime () -#else -#define ping() -#endif - -static bool openStream (player_t * const player) { - assert (player != NULL); - /* no leak? */ - assert (player->fctx == NULL); - - int ret; - - /* stream setup */ - AVDictionary *options = NULL; -#ifdef HAVE_AV_TIMEOUT - /* 10 seconds timeout on TCP r/w */ - av_dict_set (&options, "timeout", "10000000", 0); -#else - /* libav does not support the timeout option above. the workaround stores - * the current time with ping() now and then, registers an interrupt - * callback (below) and compares saved/current time in this callback. it’s - * not bullet-proof, but seems to work fine for av_read_frame. */ - player->fctx = avformat_alloc_context (); - player->fctx->interrupt_callback.callback = intCb; - player->fctx->interrupt_callback.opaque = player; -#endif - - assert (player->url != NULL); - ping (); - if ((ret = avformat_open_input (&player->fctx, player->url, NULL, &options)) < 0) { - softfail ("Unable to open audio file"); - } - - ping (); - if ((ret = avformat_find_stream_info (player->fctx, NULL)) < 0) { - softfail ("find_stream_info"); - } - - /* ignore all streams, undone for audio stream below */ - for (size_t i = 0; i < player->fctx->nb_streams; i++) { - player->fctx->streams[i]->discard = AVDISCARD_ALL; - } - - ping (); - player->streamIdx = av_find_best_stream (player->fctx, AVMEDIA_TYPE_AUDIO, - -1, -1, NULL, 0); - if (player->streamIdx < 0) { - softfail ("find_best_stream"); - } - - player->st = player->fctx->streams[player->streamIdx]; - AVCodecContext * const cctx = player->st->codec; - player->st->discard = AVDISCARD_DEFAULT; - - /* decoder setup */ - AVCodec * const decoder = avcodec_find_decoder (cctx->codec_id); - if (decoder == NULL) { - softfail ("find_decoder"); - } - - if ((ret = avcodec_open2 (cctx, decoder, NULL)) < 0) { - softfail ("codec_open2"); - } - - if (player->lastTimestamp > 0) { - ping (); - av_seek_frame (player->fctx, player->streamIdx, player->lastTimestamp, 0); - } - - player->songPlayed = 0; - player->songDuration = av_q2d (player->st->time_base) * - (double) player->st->duration; - - return true; -} - -/* setup filter chain - */ -static bool openFilter (player_t * const player) { - /* filter setup */ - char strbuf[256]; - int ret = 0; - AVCodecContext * const cctx = player->st->codec; - - if ((player->fgraph = avfilter_graph_alloc ()) == NULL) { - softfail ("graph_alloc"); - } - - /* abuffer */ - AVRational time_base = player->st->time_base; - - /* Workaround for a bug in libav-11, which reports an invalid channel - * layout mp3 files */ - if (cctx->channel_layout == 0) { - cctx->channel_layout = av_get_default_channel_layout (cctx->channels); - } - - snprintf (strbuf, sizeof (strbuf), - "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64, - time_base.num, time_base.den, cctx->sample_rate, - av_get_sample_fmt_name (cctx->sample_fmt), - cctx->channel_layout); - if ((ret = avfilter_graph_create_filter (&player->fabuf, - avfilter_get_by_name ("abuffer"), NULL, strbuf, NULL, - player->fgraph)) < 0) { - softfail ("create_filter abuffer"); - } - - /* volume */ - if ((ret = avfilter_graph_create_filter (&player->fvolume, - avfilter_get_by_name ("volume"), NULL, NULL, NULL, - player->fgraph)) < 0) { - softfail ("create_filter volume"); - } - - /* aformat: convert float samples into something more usable */ - AVFilterContext *fafmt = NULL; - snprintf (strbuf, sizeof (strbuf), "sample_fmts=%s", - av_get_sample_fmt_name (avformat)); - if ((ret = avfilter_graph_create_filter (&fafmt, - avfilter_get_by_name ("aformat"), NULL, strbuf, NULL, - player->fgraph)) < 0) { - softfail ("create_filter aformat"); - } - - /* abuffersink */ - if ((ret = avfilter_graph_create_filter (&player->fbufsink, - avfilter_get_by_name ("abuffersink"), NULL, NULL, NULL, - player->fgraph)) < 0) { - softfail ("create_filter abuffersink"); - } - - /* connect filter: abuffer -> volume -> aformat -> abuffersink */ - if (avfilter_link (player->fabuf, 0, player->fvolume, 0) != 0 || - avfilter_link (player->fvolume, 0, fafmt, 0) != 0 || - avfilter_link (fafmt, 0, player->fbufsink, 0) != 0) { - softfail ("filter_link"); - } - - if ((ret = avfilter_graph_config (player->fgraph, NULL)) < 0) { - softfail ("graph_config"); - } - - return true; -} - -/* setup libao - */ -static bool openDevice (player_t * const player) { - AVCodecContext * const cctx = player->st->codec; - - ao_sample_format aoFmt; - memset (&aoFmt, 0, sizeof (aoFmt)); - aoFmt.bits = av_get_bytes_per_sample (avformat) * 8; - assert (aoFmt.bits > 0); - aoFmt.channels = cctx->channels; - aoFmt.rate = cctx->sample_rate; - aoFmt.byte_format = AO_FMT_NATIVE; - - int driver = ao_default_driver_id (); - if ((player->aoDev = ao_open_live (driver, &aoFmt, NULL)) == NULL) { - BarUiMsg (player->settings, MSG_ERR, "Cannot open audio device.\n"); - return false; - } - - return true; -} - -/* decode and play stream. returns 0 or av error code. - */ -static int play (player_t * const player) { - assert (player != NULL); - - AVPacket pkt; - av_init_packet (&pkt); - pkt.data = NULL; - pkt.size = 0; - - AVFrame *frame = NULL, *filteredFrame = NULL; - frame = avcodec_alloc_frame (); - assert (frame != NULL); - filteredFrame = avcodec_alloc_frame (); - assert (filteredFrame != NULL); - - while (!player->doQuit) { - ping (); - int ret = av_read_frame (player->fctx, &pkt); - if (ret < 0) { - av_free_packet (&pkt); - return ret; - } else if (pkt.stream_index != player->streamIdx) { - av_free_packet (&pkt); - continue; - } - - AVPacket pkt_orig = pkt; - - /* pausing */ - pthread_mutex_lock (&player->pauseMutex); - if (player->doPause) { - av_read_pause (player->fctx); - do { - pthread_cond_wait (&player->pauseCond, &player->pauseMutex); - } while (player->doPause); - av_read_play (player->fctx); - } - pthread_mutex_unlock (&player->pauseMutex); - - while (pkt.size > 0 && !player->doQuit) { - int got_frame = 0; - - const int decoded = avcodec_decode_audio4 (player->st->codec, - frame, &got_frame, &pkt); - if (decoded < 0) { - /* skip this one */ - break; - } - - if (got_frame != 0) { - /* XXX: suppresses warning from resample filter */ - if (frame->pts == (int64_t) AV_NOPTS_VALUE) { - frame->pts = 0; - } - ret = av_buffersrc_write_frame (player->fabuf, frame); - assert (ret >= 0); - - while (true) { - AVFilterBufferRef *audioref = NULL; -#ifdef HAVE_AV_BUFFERSINK_GET_BUFFER_REF - /* ffmpeg’s compatibility layer is broken in some releases */ - if (av_buffersink_get_buffer_ref (player->fbufsink, - &audioref, 0) < 0) { -#else - if (av_buffersink_read (player->fbufsink, &audioref) < 0) { -#endif - /* try again next frame */ - break; - } - - ret = avfilter_copy_buf_props (filteredFrame, audioref); - assert (ret >= 0); - - const int numChannels = av_get_channel_layout_nb_channels ( - filteredFrame->channel_layout); - const int bps = av_get_bytes_per_sample(filteredFrame->format); - ao_play (player->aoDev, (char *) filteredFrame->data[0], - filteredFrame->nb_samples * numChannels * bps); - - avfilter_unref_bufferp (&audioref); - } - } - - pkt.data += decoded; - pkt.size -= decoded; - }; - - av_free_packet (&pkt_orig); - - player->songPlayed = av_q2d (player->st->time_base) * (double) pkt.pts; - player->lastTimestamp = pkt.pts; - } - - avcodec_free_frame (&filteredFrame); - avcodec_free_frame (&frame); - - return 0; -} - -static void finish (player_t * const player) { - ao_close (player->aoDev); - player->aoDev = NULL; - if (player->fgraph != NULL) { - avfilter_graph_free (&player->fgraph); - player->fgraph = NULL; - } - if (player->st != NULL && player->st->codec != NULL) { - avcodec_close (player->st->codec); - player->st = NULL; - } - if (player->fctx != NULL) { - avformat_close_input (&player->fctx); - } -} - -/* player thread; for every song a new thread is started - * @param audioPlayer structure - * @return PLAYER_RET_* - */ -void *BarPlayerThread (void *data) { - assert (data != NULL); - - player_t * const player = data; - intptr_t pret = PLAYER_RET_OK; - - bool retry; - do { - retry = false; - if (openStream (player)) { - if (openFilter (player) && openDevice (player)) { - player->mode = PLAYER_PLAYING; - BarPlayerSetVolume (player); - retry = play (player) == AVERROR_INVALIDDATA; - } else { - /* filter missing or audio device busy */ - pret = PLAYER_RET_HARDFAIL; - } - } else { - /* stream not found */ - pret = PLAYER_RET_SOFTFAIL; - } - player->mode = PLAYER_WAITING; - finish (player); - } while (retry); - - player->mode = PLAYER_FINISHED; - - return (void *) pret; -} - diff --git a/src/player.h b/src/player.h deleted file mode 100644 index e4d9f5b..0000000 --- a/src/player.h +++ /dev/null @@ -1,91 +0,0 @@ -/* -Copyright (c) 2008-2014 - Lars-Dominik Braun - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -#ifndef SRC_PLAYER_H_CN979RE9 -#define SRC_PLAYER_H_CN979RE9 - -#include "config.h" - -/* required for freebsd */ -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "settings.h" - -typedef struct { - /* protected by pauseMutex */ - volatile bool doQuit; - volatile bool doPause; - pthread_mutex_t pauseMutex; - pthread_cond_t pauseCond; - - enum { - /* not running */ - PLAYER_DEAD = 0, - /* running, but not ready to play music yet */ - PLAYER_WAITING, - /* currently playing a song */ - PLAYER_PLAYING, - /* finished playing a song */ - PLAYER_FINISHED, - } mode; - - /* libav */ - AVFilterContext *fvolume; - AVFilterGraph *fgraph; - AVFormatContext *fctx; - AVStream *st; - AVFilterContext *fbufsink, *fabuf; - int streamIdx; - int64_t lastTimestamp; -#ifndef HAVE_AV_TIMEOUT - int64_t ping; -#endif - - ao_device *aoDev; - - /* settings */ - double gain; - char *url; - const BarSettings_t *settings; - - /* measured in seconds */ - volatile unsigned int songDuration; - volatile unsigned int songPlayed; -} player_t; - -enum {PLAYER_RET_OK = 0, PLAYER_RET_HARDFAIL = 1, PLAYER_RET_SOFTFAIL = 2}; - -void *BarPlayerThread (void *data); -void BarPlayerSetVolume (player_t * const player); -void BarPlayerInit (); -void BarPlayerDestroy (); - -#endif /* SRC_PLAYER_H_CN979RE9 */ diff --git a/src/player2.c b/src/player2.c new file mode 100644 index 0000000..906fa39 --- /dev/null +++ b/src/player2.c @@ -0,0 +1,364 @@ +/* +Copyright (c) 2008-2014 + Lars-Dominik Braun + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +/* receive/play audio stream */ + +#include "config.h" +#include "player2.h" +#define COBJMACROS +#define INITGUID +#include +#include +#pragma comment(lib, "strmiids.lib") + +# define WM_GRAPH_EVENT (WM_APP + 1) + +enum { NO_GRAPH, RUNNING, PAUSED, STOPPED }; + +struct _player_t { + int state; + IGraphBuilder* graph; + IMediaControl* control; + IMediaEventEx* event; + IBasicAudio* audio; + IMediaSeeking* media; + float volume; // dB + float gain; // dB +}; + +static void BarPlayer2ApplyVolume(player2_t player) { + long v = (long)((player->volume + player->gain) * 100.0f); + + if (!player->audio) + return; + + if (v < -10000) + v = -10000; + if (v > 0) + v = 0; + + IBasicAudio_put_Volume(player->audio, v); +} + +static void BarPlayer2TearDown(player2_t player) { + /* TODO: send final event */ + + if (player->graph) { + IGraphBuilder_Release(player->graph); + player->graph = NULL; + } + + if (player->control) { + IMediaControl_Release(player->control); + player->control = NULL; + } + + if (player->event) { + IMediaEventEx_Release(player->event); + player->event = NULL; + } + + if (player->audio) { + IBasicAudio_Release(player->audio); + player->audio = NULL; + } + + if (player->media) { + IMediaSeeking_Release(player->media); + player->media = NULL; + } + + player->state = NO_GRAPH; +} + +static HRESULT BarPlayer2Build(player2_t player) { + HRESULT hr; + + BarPlayer2TearDown(player); + + hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, &IID_IGraphBuilder, &player->graph); + if (FAILED(hr)) + return hr; + + hr = IGraphBuilder_QueryInterface(player->graph, &IID_IMediaControl, &player->control); + if (FAILED(hr)) + return hr; + + hr = IGraphBuilder_QueryInterface(player->graph, &IID_IMediaEventEx, &player->event); + if (FAILED(hr)) + return hr; + + hr = IGraphBuilder_QueryInterface(player->graph, &IID_IBasicAudio, &player->audio); + if (FAILED(hr)) + return hr; + + hr = IGraphBuilder_QueryInterface(player->graph, &IID_IMediaSeeking, &player->media); + if (FAILED(hr)) + return hr; + + hr = IMediaEventEx_SetNotifyWindow(player->event, (OAHWND)NULL, WM_GRAPH_EVENT, (LONG_PTR)player); + if (FAILED(hr)) + return hr; + + player->state = STOPPED; + + return S_OK; +} + +static HRESULT BarPlayer2AddFilterByCLSID(IGraphBuilder *pGraph, REFGUID clsid, IBaseFilter **ppF, LPCWSTR wszName) { + IBaseFilter *pFilter = NULL; + HRESULT hr; + *ppF = 0; + + hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, &IID_IBaseFilter, &pFilter); + if (FAILED(hr)) + goto done; + + hr = IGraphBuilder_AddFilter(pGraph, pFilter, wszName); + if (FAILED(hr)) + goto done; + + *ppF = pFilter; + + IBaseFilter_AddRef(*ppF); + +done: + if (pFilter) + IBaseFilter_Release(pFilter); + + return hr; +} + +static HRESULT BarPlayer2Render(player2_t player, IBaseFilter* source) { + BOOL bRenderedAnyPin = FALSE; + + IPin* pin = NULL; + IEnumPins *enumPins = NULL; + IBaseFilter *audioRenderer = NULL; + IFilterGraph2* filter = NULL; + + HRESULT hr; + + hr = IGraphBuilder_QueryInterface(player->graph, &IID_IFilterGraph2, &filter); + if (FAILED(hr)) + return hr; + + hr = BarPlayer2AddFilterByCLSID(player->graph, &CLSID_DSoundRender, &audioRenderer, L"Audio Renderer"); + if (FAILED(hr)) + goto done; + + hr = IBaseFilter_EnumPins(source, &enumPins); + if (FAILED(hr)) + goto done; + + while (S_OK == IEnumPins_Next(enumPins, 1, &pin, NULL)) + { + HRESULT hr2 = IFilterGraph2_RenderEx(filter, pin, AM_RENDEREX_RENDERTOEXISTINGRENDERERS, NULL); + + IPin_Release(pin); + if (SUCCEEDED(hr2)) + bRenderedAnyPin = TRUE; + } + +done: + if (enumPins) + IEnumPins_Release(enumPins); + if (enumPins) + IBaseFilter_Release(audioRenderer); + if (enumPins) + IFilterGraph2_Release(filter); + + if (SUCCEEDED(hr) && !bRenderedAnyPin) + hr = VFW_E_CANNOT_RENDER; + + return hr; +} + +bool BarPlayer2Init(player2_t* player) { + + if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED))) + return false; + + player2_t out = malloc(sizeof(struct _player_t)); + if (!out) + return false; + + memset(out, 0, sizeof(struct _player_t)); + + *player = out; + + return true; +} + +void BarPlayer2Destroy(player2_t player) { + BarPlayer2TearDown(player); + free(player); +} + +void BarPlayer2SetVolume(player2_t player, float volume) { + player->volume = volume; + BarPlayer2ApplyVolume(player); +} + +float BarPlayer2GetVolume(player2_t player) { + return player->volume; +} + +void BarPlayer2SetGain(player2_t player, float gain) { + player->gain = gain; + BarPlayer2ApplyVolume(player); +} + +float BarPlayer2GetGain(player2_t player) { + return player->gain; +} + +double BarPlayer2GetDuration(player2_t player) { + LONGLONG time; + if (SUCCEEDED(IMediaSeeking_GetDuration(player->media, &time))) + return time / 10000000.0; + else + return 0; +} + +double BarPlayer2GetTime(player2_t player) { + LONGLONG time; + if (SUCCEEDED(IMediaSeeking_GetCurrentPosition(player->media, &time))) + return time / 10000000.0; + else + return 0; +} + +bool BarPlayer2Open(player2_t player, const char* url) { + IBaseFilter* source = NULL; + HRESULT hr; + wchar_t* wideUrl = NULL; + size_t urlSize; + int result; + + hr = BarPlayer2Build(player); + if (FAILED(hr)) + goto done; + + urlSize = strlen(url); + result = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, url, urlSize, NULL, 0); + wideUrl = malloc((result + 1) * sizeof(wchar_t)); + if (!wideUrl) { + hr = E_OUTOFMEMORY; + goto done; + } + memset(wideUrl, 0, (result + 1) * sizeof(wchar_t)); + + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, url, urlSize, wideUrl, result); + + hr = IGraphBuilder_AddSourceFilter(player->graph, wideUrl, NULL, &source); + if (FAILED(hr)) + goto done; + + hr = BarPlayer2Render(player, source); + + BarPlayer2ApplyVolume(player); + +done: + if (wideUrl) + free(wideUrl); + if (FAILED(hr)) + BarPlayer2TearDown(player); + if (source) + IBaseFilter_Release(source); + + return SUCCEEDED(hr); +} + +bool BarPlayer2Play(player2_t player) { + HRESULT hr; + + if (player->state != PAUSED && player->state != STOPPED) + return false; /* wrong state */ + + hr = IMediaControl_Run(player->control); + if (SUCCEEDED(hr)) + player->state = RUNNING; + + return SUCCEEDED(hr); +} + +bool BarPlayer2Pause(player2_t player) { + HRESULT hr; + + if (player->state != RUNNING) + return false; /* wrong state */ + + hr = IMediaControl_Pause(player->control); + if (SUCCEEDED(hr)) + player->state = PAUSED; + + return SUCCEEDED(hr); +} + +bool BarPlayer2Stop(player2_t player) { + HRESULT hr; + + if (player->state != RUNNING && player->state != PAUSED) + return false; /* wrong state */ + + hr = IMediaControl_Stop(player->control); + if (SUCCEEDED(hr)) + player->state = STOPPED; + + return SUCCEEDED(hr); +} + +bool BarPlayer2Finish(player2_t player) { + if (!player->control) + return false; + + BarPlayer2TearDown(player); + return true; +} + +bool BarPlayer2IsPlaying(player2_t player) { + return player->state == RUNNING; +} + +bool BarPlayer2IsPaused(player2_t player) { + return player->state == PAUSED; +} + +bool BarPlayer2IsStopped(player2_t player) { + return player->state == STOPPED; +} + +bool BarPlayer2IsFinished(player2_t player) { + LONGLONG time; + LONGLONG duration; + + if (!player->media) + return true; + + if (FAILED(IMediaSeeking_GetDuration(player->media, &duration)) || + FAILED(IMediaSeeking_GetCurrentPosition(player->media, &time))) + return true; + + return time >= duration; +} diff --git a/src/player2.h b/src/player2.h new file mode 100644 index 0000000..ac40eb4 --- /dev/null +++ b/src/player2.h @@ -0,0 +1,52 @@ +/* +Copyright (c) 2008-2014 + Lars-Dominik Braun + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#ifndef SRC_PLAYER2_H_CN979RE9 +#define SRC_PLAYER2_H_CN979RE9 + +#include "config.h" + +#include + +typedef struct _player_t *player2_t; + +bool BarPlayer2Init (player2_t*); +void BarPlayer2Destroy (player2_t); +void BarPlayer2SetVolume (player2_t,float); +float BarPlayer2GetVolume (player2_t); +void BarPlayer2SetGain (player2_t, float); +float BarPlayer2GetGain (player2_t); +double BarPlayer2GetDuration (player2_t); +double BarPlayer2GetTime (player2_t); +bool BarPlayer2Open (player2_t, const char*); +bool BarPlayer2Play (player2_t); +bool BarPlayer2Pause (player2_t); +bool BarPlayer2Stop (player2_t); +bool BarPlayer2Finish (player2_t); +bool BarPlayer2IsPlaying (player2_t); +bool BarPlayer2IsPaused (player2_t); +bool BarPlayer2IsStopped (player2_t); +bool BarPlayer2IsFinished (player2_t); + +#endif /* SRC_PLAYER2_H_CN979RE9 */ + diff --git a/src/settings.c b/src/settings.c index 11a2982..7ed10f5 100644 --- a/src/settings.c +++ b/src/settings.c @@ -25,40 +25,48 @@ THE SOFTWARE. #include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#include - #include "settings.h" #include "config.h" #include "ui_dispatch.h" +#include +#include + +#define PACKAGE_CONFIG PACKAGE ".cfg" +#define PACKAGE_STATE PACKAGE ".state" +#define PACKAGE_PIPE PACKAGE ".ctrl" #define streq(a, b) (strcmp (a, b) == 0) -/* Get current user’s home directory - */ -static char *BarSettingsGetHome () { - char *home; +static char* strndup(const char *s, size_t n) +{ + char *result; + const char* end = memchr(s, 0, n); + size_t len = end ? (size_t)(end - s) : n; - /* try environment variable */ - if ((home = getenv ("HOME")) != NULL && strlen (home) > 0) { - return strdup (home); - } + if (len < n) + n = len; - /* try passwd mechanism */ - struct passwd * const pw = getpwuid (getuid ()); - if (pw != NULL && pw->pw_dir != NULL && strlen (pw->pw_dir) > 0) { - return strdup (pw->pw_dir); - } + result = (char *)malloc(n + 1); + if (!result) + return 0; - return NULL; + result[n] = '\0'; + + return (char*)memcpy(result, s, n); +} + +/* Get current user’s home directory + */ +static char *BarSettingsGetHome () { + char* exec = NULL; + char* delimiter = NULL; + + _get_pgmptr (&exec); + delimiter = strrchr (exec, '\\'); + if (delimiter) + return strndup (exec, delimiter - exec); + else + return NULL; } /* Get XDG config directory, which is set by BarSettingsRead (if not set) @@ -73,7 +81,7 @@ static char *BarGetXdgConfigDir (const char * const filename) { const size_t len = (strlen (xdgConfigDir) + 1 + strlen (filename) + 1); char * const concat = malloc (len * sizeof (*concat)); - snprintf (concat, len, "%s/%s", xdgConfigDir, filename); + snprintf (concat, len, "%s\\%s", xdgConfigDir, filename); return concat; } @@ -140,13 +148,13 @@ void BarSettingsDestroy (BarSettings_t *settings) { * @return nothing yet */ void BarSettingsRead (BarSettings_t *settings) { - char * const configfiles[] = {PACKAGE "/state", PACKAGE "/config"}; + char * const configfiles[] = { PACKAGE_STATE, PACKAGE_CONFIG }; char * const userhome = BarSettingsGetHome (); assert (userhome != NULL); /* set xdg config path (if not set) */ - char * const defaultxdg = malloc (strlen (userhome) + strlen ("/.config") + 1); - sprintf (defaultxdg, "%s/.config", userhome); - setenv ("XDG_CONFIG_HOME", defaultxdg, 0); + char * const defaultxdg = malloc (strlen ("XDG_CONFIG_HOME=") + strlen (userhome) + 1); + sprintf (defaultxdg, "XDG_CONFIG_HOME=%s", userhome); + _putenv (defaultxdg); free (defaultxdg); assert (sizeof (settings->keys) / sizeof (*settings->keys) == @@ -172,7 +180,7 @@ void BarSettingsRead (BarSettings_t *settings) { settings->device = strdup ("android-generic"); settings->inkey = strdup ("R=U!LH$O2B#"); settings->outkey = strdup ("6#26FRL$ZWD"); - settings->fifo = BarGetXdgConfigDir (PACKAGE "/ctl"); + settings->fifo = BarGetXdgConfigDir (PACKAGE_PIPE); assert (settings->fifo != NULL); settings->msgFormat[MSG_NONE].prefix = NULL; @@ -365,11 +373,6 @@ void BarSettingsRead (BarSettings_t *settings) { } } - /* ffmpeg does not support setting an http proxy explicitly */ - if (settings->proxy != NULL) { - setenv ("http_proxy", settings->proxy, 1); - } - free (userhome); } diff --git a/src/settings.h b/src/settings.h index d35a64c..d0fdbfa 100644 --- a/src/settings.h +++ b/src/settings.h @@ -28,6 +28,8 @@ THE SOFTWARE. #include +#include "ui_types.h" + /* update structure in ui_dispatch.h if you add shortcuts here */ typedef enum { BAR_KS_HELP = 0, @@ -76,8 +78,6 @@ typedef enum { BAR_SORT_COUNT = 6, } BarStationSorting_t; -#include "ui_types.h" - typedef struct { char *prefix; char *postfix; @@ -107,8 +107,6 @@ typedef struct { BarMsgFormatStr_t msgFormat[MSG_COUNT]; } BarSettings_t; -#include - void BarSettingsInit (BarSettings_t *); void BarSettingsDestroy (BarSettings_t *); void BarSettingsRead (BarSettings_t *); diff --git a/src/terminal.c b/src/terminal.c deleted file mode 100644 index 2715d89..0000000 --- a/src/terminal.c +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright (c) 2008-2014 - Lars-Dominik Braun - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -#include -#include -#include -#include -#include - -#include "terminal.h" - -/* need a global variable here, since these functions get called from a signal - * handler */ -static struct termios restore; - -/* init terminal attributes when continuing, assuming the shell modified them; - * tcget/setattr and signal are async signal safe */ -static void BarTermHandleCont (int sig) { - BarTermInit (); -} - -void BarTermInit () { - struct termios newopt; - - tcgetattr (STDIN_FILENO, &restore); - memcpy (&newopt, &restore, sizeof (newopt)); - - /* disable echoing and line buffer */ - newopt.c_lflag &= ~(ECHO | ICANON); - tcsetattr (STDIN_FILENO, TCSANOW, &newopt); - - signal (SIGCONT, BarTermHandleCont); -} - -void BarTermRestore () { - tcsetattr (STDIN_FILENO, TCSANOW, &restore); -} - diff --git a/src/terminal.h b/src/terminal.h deleted file mode 100644 index c43d01b..0000000 --- a/src/terminal.h +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright (c) 2008-2010 - Lars-Dominik Braun - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -#ifndef SRC_TERMINAL_H_WY8F3MNH -#define SRC_TERMINAL_H_WY8F3MNH - -void BarTermInit (); -void BarTermRestore (); - -#endif /* SRC_TERMINAL_H_WY8F3MNH */ diff --git a/src/ui.c b/src/ui.c index 779dbf8..962f94c 100644 --- a/src/ui.c +++ b/src/ui.c @@ -25,22 +25,9 @@ THE SOFTWARE. #include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include /* tolower() */ - -/* waitpid () */ -#include -#include - #include "ui.h" #include "ui_readline.h" +#include typedef int (*BarSortFunc_t) (const void *, const void *); @@ -105,6 +92,7 @@ void BarUiMsg (const BarSettings_t *settings, const BarUiMsg_t type, case MSG_QUESTION: case MSG_LIST: /* print ANSI clear line */ + fputs ("\033[2K", stdout); break; @@ -438,7 +426,7 @@ PianoStation_t *BarUiSelectStation (BarApp_t *app, PianoStation_t *stations, BarUiMsg (&app->settings, MSG_NONE, "%zi\n", lastDisplayed); retStation = sortedStations[lastDisplayed]; } else { - if (BarReadlineStr (buf, sizeof (buf), &app->input, + if (BarReadlineStr (buf, sizeof (buf), app->rl, BAR_RL_DEFAULT) == 0) { break; } @@ -468,7 +456,7 @@ PianoStation_t *BarUiSelectStation (BarApp_t *app, PianoStation_t *stations, * @return pointer to selected item in song list or NULL */ PianoSong_t *BarUiSelectSong (const BarSettings_t *settings, - PianoSong_t *startSong, BarReadlineFds_t *input) { + PianoSong_t *startSong, BarReadline_t rl) { PianoSong_t *tmpSong = NULL; char buf[100]; @@ -478,7 +466,7 @@ PianoSong_t *BarUiSelectSong (const BarSettings_t *settings, BarUiListSongs (settings, startSong, buf); BarUiMsg (settings, MSG_QUESTION, "Select song: "); - if (BarReadlineStr (buf, sizeof (buf), input, BAR_RL_DEFAULT) == 0) { + if (BarReadlineStr (buf, sizeof (buf), rl, BAR_RL_DEFAULT) == 0) { return NULL; } @@ -516,7 +504,7 @@ PianoArtist_t *BarUiSelectArtist (BarApp_t *app, PianoArtist_t *startArtist) { } BarUiMsg (&app->settings, MSG_QUESTION, "Select artist: "); - if (BarReadlineStr (buf, sizeof (buf), &app->input, + if (BarReadlineStr (buf, sizeof (buf), app->rl, BAR_RL_DEFAULT) == 0) { return NULL; } @@ -546,7 +534,7 @@ char *BarUiSelectMusicId (BarApp_t *app, PianoStation_t *station, PianoSong_t *tmpSong; BarUiMsg (&app->settings, MSG_QUESTION, "%s", msg); - if (BarReadlineStr (lineBuf, sizeof (lineBuf), &app->input, + if (BarReadlineStr (lineBuf, sizeof (lineBuf), app->rl, BAR_RL_DEFAULT) > 0) { PianoReturn_t pRet; CURLcode wRet; @@ -566,7 +554,7 @@ char *BarUiSelectMusicId (BarApp_t *app, PianoStation_t *station, searchResult.artists != NULL) { /* songs and artists found */ BarUiMsg (&app->settings, MSG_QUESTION, "Is this an [a]rtist or [t]rack name? "); - BarReadline (selectBuf, sizeof (selectBuf), "at", &app->input, + BarReadline (selectBuf, sizeof (selectBuf), "at", app->rl, BAR_RL_FULLRETURN, -1); if (*selectBuf == 'a') { tmpArtist = BarUiSelectArtist (app, searchResult.artists); @@ -575,7 +563,7 @@ char *BarUiSelectMusicId (BarApp_t *app, PianoStation_t *station, } } else if (*selectBuf == 't') { tmpSong = BarUiSelectSong (&app->settings, searchResult.songs, - &app->input); + app->rl); if (tmpSong != NULL) { musicId = strdup (tmpSong->musicId); } @@ -583,7 +571,7 @@ char *BarUiSelectMusicId (BarApp_t *app, PianoStation_t *station, } else if (searchResult.songs != NULL) { /* songs found */ tmpSong = BarUiSelectSong (&app->settings, searchResult.songs, - &app->input); + app->rl); if (tmpSong != NULL) { musicId = strdup (tmpSong->musicId); } @@ -751,103 +739,103 @@ size_t BarUiListSongs (const BarSettings_t *settings, */ void BarUiStartEventCmd (const BarSettings_t *settings, const char *type, const PianoStation_t *curStation, const PianoSong_t *curSong, - const player_t * const player, PianoStation_t *stations, + const player2_t * const player, PianoStation_t *stations, PianoReturn_t pRet, CURLcode wRet) { - pid_t chld; - int pipeFd[2]; + //pid_t chld; + //int pipeFd[2]; - if (settings->eventCmd == NULL) { + //if (settings->eventCmd == NULL) { /* nothing to do... */ return; - } - - if (pipe (pipeFd) == -1) { - BarUiMsg (settings, MSG_ERR, "Cannot create eventcmd pipe. (%s)\n", strerror (errno)); - return; - } - - chld = fork (); - if (chld == 0) { - /* child */ - close (pipeFd[1]); - dup2 (pipeFd[0], fileno (stdin)); - execl (settings->eventCmd, settings->eventCmd, type, (char *) NULL); - BarUiMsg (settings, MSG_ERR, "Cannot start eventcmd. (%s)\n", strerror (errno)); - close (pipeFd[0]); - exit (1); - } else if (chld == -1) { - BarUiMsg (settings, MSG_ERR, "Cannot fork eventcmd. (%s)\n", strerror (errno)); - } else { - /* parent */ - int status; - PianoStation_t *songStation = NULL; - FILE *pipeWriteFd; - - close (pipeFd[0]); - - pipeWriteFd = fdopen (pipeFd[1], "w"); - - if (curSong != NULL && stations != NULL && curStation->isQuickMix) { - songStation = PianoFindStationById (stations, curSong->stationId); - } - - fprintf (pipeWriteFd, - "artist=%s\n" - "title=%s\n" - "album=%s\n" - "coverArt=%s\n" - "stationName=%s\n" - "songStationName=%s\n" - "pRet=%i\n" - "pRetStr=%s\n" - "wRet=%i\n" - "wRetStr=%s\n" - "songDuration=%u\n" - "songPlayed=%u\n" - "rating=%i\n" - "detailUrl=%s\n", - curSong == NULL ? "" : curSong->artist, - curSong == NULL ? "" : curSong->title, - curSong == NULL ? "" : curSong->album, - curSong == NULL ? "" : curSong->coverArt, - curStation == NULL ? "" : curStation->name, - songStation == NULL ? "" : songStation->name, - pRet, - PianoErrorToStr (pRet), - wRet, - curl_easy_strerror (wRet), - player->songDuration, - player->songPlayed, - curSong == NULL ? PIANO_RATE_NONE : curSong->rating, - curSong == NULL ? "" : curSong->detailUrl - ); - - if (stations != NULL) { - /* send station list */ - PianoStation_t **sortedStations = NULL; - size_t stationCount; - sortedStations = BarSortedStations (stations, &stationCount, - settings->sortOrder); - assert (sortedStations != NULL); - - fprintf (pipeWriteFd, "stationCount=%zd\n", stationCount); - - for (size_t i = 0; i < stationCount; i++) { - const PianoStation_t *currStation = sortedStations[i]; - fprintf (pipeWriteFd, "station%zd=%s\n", i, - currStation->name); - } - free (sortedStations); - } else { - const char * const msg = "stationCount=0\n"; - fwrite (msg, sizeof (*msg), strlen (msg), pipeWriteFd); - } - - /* closes pipeFd[1] as well */ - fclose (pipeWriteFd); - /* wait to get rid of the zombie */ - waitpid (chld, &status, 0); - } + //} + + //if (pipe (pipeFd) == -1) { + // BarUiMsg (settings, MSG_ERR, "Cannot create eventcmd pipe. (%s)\n", strerror (errno)); + // return; + //} + + //chld = fork (); + //if (chld == 0) { + // /* child */ + // close (pipeFd[1]); + // dup2 (pipeFd[0], fileno (stdin)); + // execl (settings->eventCmd, settings->eventCmd, type, (char *) NULL); + // BarUiMsg (settings, MSG_ERR, "Cannot start eventcmd. (%s)\n", strerror (errno)); + // close (pipeFd[0]); + // exit (1); + //} else if (chld == -1) { + // BarUiMsg (settings, MSG_ERR, "Cannot fork eventcmd. (%s)\n", strerror (errno)); + //} else { + // /* parent */ + // int status; + // PianoStation_t *songStation = NULL; + // FILE *pipeWriteFd; + + // close (pipeFd[0]); + + // pipeWriteFd = fdopen (pipeFd[1], "w"); + + // if (curSong != NULL && stations != NULL && curStation->isQuickMix) { + // songStation = PianoFindStationById (stations, curSong->stationId); + // } + + // fprintf (pipeWriteFd, + // "artist=%s\n" + // "title=%s\n" + // "album=%s\n" + // "coverArt=%s\n" + // "stationName=%s\n" + // "songStationName=%s\n" + // "pRet=%i\n" + // "pRetStr=%s\n" + // "wRet=%i\n" + // "wRetStr=%s\n" + // "songDuration=%u\n" + // "songPlayed=%u\n" + // "rating=%i\n" + // "detailUrl=%s\n", + // curSong == NULL ? "" : curSong->artist, + // curSong == NULL ? "" : curSong->title, + // curSong == NULL ? "" : curSong->album, + // curSong == NULL ? "" : curSong->coverArt, + // curStation == NULL ? "" : curStation->name, + // songStation == NULL ? "" : songStation->name, + // pRet, + // PianoErrorToStr (pRet), + // wRet, + // curl_easy_strerror (wRet), + // player->songDuration, + // player->songPlayed, + // curSong == NULL ? PIANO_RATE_NONE : curSong->rating, + // curSong == NULL ? "" : curSong->detailUrl + // ); + + // if (stations != NULL) { + // /* send station list */ + // PianoStation_t **sortedStations = NULL; + // size_t stationCount; + // sortedStations = BarSortedStations (stations, &stationCount, + // settings->sortOrder); + // assert (sortedStations != NULL); + + // fprintf (pipeWriteFd, "stationCount=%zd\n", stationCount); + + // for (size_t i = 0; i < stationCount; i++) { + // const PianoStation_t *currStation = sortedStations[i]; + // fprintf (pipeWriteFd, "station%zd=%s\n", i, + // currStation->name); + // } + // free (sortedStations); + // } else { + // const char * const msg = "stationCount=0\n"; + // fwrite (msg, sizeof (*msg), strlen (msg), pipeWriteFd); + // } + // + // /* closes pipeFd[1] as well */ + // fclose (pipeWriteFd); + // /* wait to get rid of the zombie */ + // waitpid (chld, &status, 0); + //} } /* prepend song to history diff --git a/src/ui.h b/src/ui.h index f49ec62..67b6155 100644 --- a/src/ui.h +++ b/src/ui.h @@ -29,7 +29,7 @@ THE SOFTWARE. #include #include "settings.h" -#include "player.h" +#include "player2.h" #include "main.h" #include "ui_readline.h" #include "ui_types.h" @@ -40,7 +40,7 @@ void BarUiMsg (const BarSettings_t *, const BarUiMsg_t, const char *, ...) __att PianoStation_t *BarUiSelectStation (BarApp_t *, PianoStation_t *, const char *, BarUiSelectStationCallback_t, bool); PianoSong_t *BarUiSelectSong (const BarSettings_t *, PianoSong_t *, - BarReadlineFds_t *); + BarReadline_t); PianoArtist_t *BarUiSelectArtist (BarApp_t *, PianoArtist_t *); char *BarUiSelectMusicId (BarApp_t *, PianoStation_t *, const char *); void BarUiPrintStation (const BarSettings_t *, PianoStation_t *); @@ -48,7 +48,7 @@ void BarUiPrintSong (const BarSettings_t *, const PianoSong_t *, const PianoStation_t *); size_t BarUiListSongs (const BarSettings_t *, const PianoSong_t *, const char *); void BarUiStartEventCmd (const BarSettings_t *, const char *, - const PianoStation_t *, const PianoSong_t *, const player_t *, + const PianoStation_t *, const PianoSong_t *, const player2_t * const, PianoStation_t *, PianoReturn_t, CURLcode); int BarUiPianoCall (BarApp_t * const, PianoRequestType_t, void *, PianoReturn_t *, CURLcode *); diff --git a/src/ui_act.c b/src/ui_act.c index e8452e6..920ca6b 100644 --- a/src/ui_act.c +++ b/src/ui_act.c @@ -49,14 +49,10 @@ THE SOFTWARE. /* helper to _really_ skip a song (unlock mutex, quit player) * @param player handle */ -static inline void BarUiDoSkipSong (player_t * const player) { +static inline void BarUiDoSkipSong (player2_t player) { assert (player != NULL); - pthread_mutex_lock (&player->pauseMutex); - player->doQuit = true; - player->doPause = false; - pthread_cond_broadcast (&player->pauseCond); - pthread_mutex_unlock (&player->pauseMutex); + BarPlayer2Stop(player); } /* transform station if necessary to allow changes like rename, rate, ... @@ -148,7 +144,7 @@ BarUiActCallback(BarUiActBanSong) { BarUiMsg (&app->settings, MSG_INFO, "Banning song... "); if (BarUiActDefaultPianoCall (PIANO_REQUEST_RATE_SONG, &reqData) && selSong == app->playlist) { - BarUiDoSkipSong (&app->player); + BarUiDoSkipSong (app->player); } BarUiActDefaultEventcmd ("songban"); } @@ -183,7 +179,7 @@ BarUiActCallback(BarUiActCreateStationFromSong) { reqData.type = PIANO_MUSICTYPE_INVALID; BarUiMsg (&app->settings, MSG_QUESTION, "Create station from [s]ong or [a]rtist? "); - BarReadline (selectBuf, sizeof (selectBuf), "sa", &app->input, + BarReadline (selectBuf, sizeof (selectBuf), "sa", app->rl, BAR_RL_FULLRETURN, -1); switch (selectBuf[0]) { case 's': @@ -213,7 +209,7 @@ BarUiActCallback(BarUiActAddSharedStation) { reqData.type = PIANO_MUSICTYPE_INVALID; BarUiMsg (&app->settings, MSG_QUESTION, "Station id: "); - if (BarReadline (stationId, sizeof (stationId), "0123456789", &app->input, + if (BarReadline (stationId, sizeof (stationId), "0123456789", app->rl, BAR_RL_DEFAULT, -1) > 0) { BarUiMsg (&app->settings, MSG_INFO, "Adding shared station... "); BarUiActDefaultPianoCall (PIANO_REQUEST_CREATE_STATION, &reqData); @@ -231,11 +227,11 @@ BarUiActCallback(BarUiActDeleteStation) { BarUiMsg (&app->settings, MSG_QUESTION, "Really delete \"%s\"? [yN] ", selStation->name); - if (BarReadlineYesNo (false, &app->input)) { + if (BarReadlineYesNo (false, app->rl)) { BarUiMsg (&app->settings, MSG_INFO, "Deleting station... "); if (BarUiActDefaultPianoCall (PIANO_REQUEST_DELETE_STATION, selStation) && selStation == app->curStation) { - BarUiDoSkipSong (&app->player); + BarUiDoSkipSong (app->player); PianoDestroyPlaylist (PianoListNextP (app->playlist)); app->playlist->head.next = NULL; BarUiHistoryPrepend (app, app->playlist); @@ -296,7 +292,7 @@ BarUiActCallback(BarUiActStationFromGenre) { do { /* select category or exit */ BarUiMsg (&app->settings, MSG_QUESTION, "Select category: "); - if (BarReadlineInt (&i, &app->input) == 0) { + if (BarReadlineInt (&i, app->rl) == 0) { return; } curCat = PianoListGetP (app->ph.genreStations, i); @@ -312,7 +308,7 @@ BarUiActCallback(BarUiActStationFromGenre) { do { BarUiMsg (&app->settings, MSG_QUESTION, "Select genre: "); - if (BarReadlineInt (&i, &app->input) == 0) { + if (BarReadlineInt (&i, app->rl) == 0) { return; } curGenre = PianoListGetP (curCat->genres, i); @@ -349,6 +345,7 @@ BarUiActCallback(BarUiActDebug) { /* print debug-alike infos */ BarUiMsg (&app->settings, MSG_NONE, + "\033[2K" "album:\t%s\n" "artist:\t%s\n" "audioFormat:\t%i\n" @@ -407,34 +404,28 @@ BarUiActCallback(BarUiActLoveSong) { /* skip song */ BarUiActCallback(BarUiActSkipSong) { - BarUiDoSkipSong (&app->player); + BarUiDoSkipSong (app->player); } /* play */ BarUiActCallback(BarUiActPlay) { - pthread_mutex_lock (&app->player.pauseMutex); - app->player.doPause = false; - pthread_cond_broadcast (&app->player.pauseCond); - pthread_mutex_unlock (&app->player.pauseMutex); + BarPlayer2Play(app->player); } /* pause */ BarUiActCallback(BarUiActPause) { - pthread_mutex_lock (&app->player.pauseMutex); - app->player.doPause = true; - pthread_cond_broadcast (&app->player.pauseCond); - pthread_mutex_unlock (&app->player.pauseMutex); + BarPlayer2Pause(app->player); } /* toggle pause */ BarUiActCallback(BarUiActTogglePause) { - pthread_mutex_lock (&app->player.pauseMutex); - app->player.doPause = !app->player.doPause; - pthread_cond_broadcast (&app->player.pauseCond); - pthread_mutex_unlock (&app->player.pauseMutex); + if (BarPlayer2IsPlaying(app->player)) + BarPlayer2Play(app->player); + else + BarPlayer2Pause(app->player); } /* rename current station @@ -447,7 +438,7 @@ BarUiActCallback(BarUiActRenameStation) { assert (selStation != NULL); BarUiMsg (&app->settings, MSG_QUESTION, "New name: "); - if (BarReadlineStr (lineBuf, sizeof (lineBuf), &app->input, BAR_RL_DEFAULT) > 0) { + if (BarReadlineStr (lineBuf, sizeof (lineBuf), app->rl, BAR_RL_DEFAULT) > 0) { PianoRequestDataRenameStation_t reqData; if (!BarTransformIfShared (app, selStation)) { return; @@ -470,7 +461,7 @@ BarUiActCallback(BarUiActSelectStation) { if (newStation != NULL) { app->curStation = newStation; BarUiPrintStation (&app->settings, app->curStation); - BarUiDoSkipSong (&app->player); + BarUiDoSkipSong (app->player); if (app->playlist != NULL) { PianoDestroyPlaylist (PianoListNextP (app->playlist)); app->playlist->head.next = NULL; @@ -491,7 +482,7 @@ BarUiActCallback(BarUiActTempBanSong) { BarUiMsg (&app->settings, MSG_INFO, "Putting song on shelf... "); if (BarUiActDefaultPianoCall (PIANO_REQUEST_ADD_TIRED_SONG, selSong) && selSong == app->playlist) { - BarUiDoSkipSong (&app->player); + BarUiDoSkipSong (app->player); } BarUiActDefaultEventcmd ("songshelf"); } @@ -573,7 +564,7 @@ BarUiActCallback(BarUiActSelectQuickMix) { */ BarUiActCallback(BarUiActQuit) { app->doQuit = true; - BarUiDoSkipSong (&app->player); + BarUiDoSkipSong (app->player); } /* song history @@ -584,7 +575,7 @@ BarUiActCallback(BarUiActHistory) { if (app->songHistory != NULL) { histSong = BarUiSelectSong (&app->settings, app->songHistory, - &app->input); + app->rl); if (histSong != NULL) { BarKeyShortcutId_t action; PianoStation_t *songStation = PianoFindStationById (app->ph.stations, @@ -600,7 +591,7 @@ BarUiActCallback(BarUiActHistory) { BarUiMsg (&app->settings, MSG_QUESTION, "What to do with this song? "); - if (BarReadline (buf, sizeof (buf), NULL, &app->input, + if (BarReadline (buf, sizeof (buf), NULL, app->rl, BAR_RL_FULLRETURN, -1) > 0) { /* actions assume that selStation is the song's original * station */ @@ -625,7 +616,7 @@ BarUiActCallback(BarUiActBookmark) { assert (selSong != NULL); BarUiMsg (&app->settings, MSG_QUESTION, "Bookmark [s]ong or [a]rtist? "); - BarReadline (selectBuf, sizeof (selectBuf), "sa", &app->input, + BarReadline (selectBuf, sizeof (selectBuf), "sa", app->rl, BAR_RL_FULLRETURN, -1); if (selectBuf[0] == 's') { BarUiMsg (&app->settings, MSG_INFO, "Bookmarking song... "); @@ -642,21 +633,21 @@ BarUiActCallback(BarUiActBookmark) { */ BarUiActCallback(BarUiActVolDown) { --app->settings.volume; - BarPlayerSetVolume (&app->player); + BarPlayer2SetVolume (app->player, (float)app->settings.volume); } /* increase volume */ BarUiActCallback(BarUiActVolUp) { ++app->settings.volume; - BarPlayerSetVolume (&app->player); + BarPlayer2SetVolume (app->player, (float)app->settings.volume); } /* reset volume */ BarUiActCallback(BarUiActVolReset) { app->settings.volume = 0; - BarPlayerSetVolume (&app->player); + BarPlayer2SetVolume (app->player, (float)app->settings.volume); } static const char *boolToYesNo (const bool value) { @@ -691,7 +682,7 @@ BarUiActCallback(BarUiActSettings) { int val; BarUiMsg (&app->settings, MSG_QUESTION, "Change setting: "); - if (BarReadlineInt (&val, &app->input) == 0) { + if (BarReadlineInt (&val, app->rl) == 0) { break; } @@ -700,7 +691,7 @@ BarUiActCallback(BarUiActSettings) { /* username */ char buf[80]; BarUiMsg (&app->settings, MSG_QUESTION, "New username: "); - if (BarReadlineStr (buf, sizeof (buf), &app->input, + if (BarReadlineStr (buf, sizeof (buf), app->rl, BAR_RL_DEFAULT) > 0) { reqData.newUsername = strdup (buf); modified = true; @@ -712,7 +703,7 @@ BarUiActCallback(BarUiActSettings) { /* password */ char buf[80]; BarUiMsg (&app->settings, MSG_QUESTION, "New password: "); - if (BarReadlineStr (buf, sizeof (buf), &app->input, + if (BarReadlineStr (buf, sizeof (buf), app->rl, BAR_RL_NOECHO) > 0) { reqData.newPassword = strdup (buf); modified = true; @@ -728,7 +719,7 @@ BarUiActCallback(BarUiActSettings) { "Enable explicit content filter? [yn] "); reqData.explicitContentFilter = BarReadlineYesNo (settings.explicitContentFilter, - &app->input) ? PIANO_TRUE : PIANO_FALSE; + app->rl) ? PIANO_TRUE : PIANO_FALSE; modified = true; break; } @@ -822,7 +813,7 @@ BarUiActCallback(BarUiActManageStation) { } BarUiMsg (&app->settings, MSG_QUESTION, "%s", question); - if (BarReadline (selectBuf, sizeof (selectBuf), allowedActions, &app->input, + if (BarReadline (selectBuf, sizeof (selectBuf), allowedActions, app->rl, BAR_RL_FULLRETURN, -1)) { if (selectBuf[0] == 'a') { PianoArtist_t *artist = BarUiSelectArtist (app, @@ -839,7 +830,7 @@ BarUiActCallback(BarUiActManageStation) { } } else if (selectBuf[0] == 's') { PianoSong_t *song = BarUiSelectSong (&app->settings, - reqData.info.songSeeds, &app->input); + reqData.info.songSeeds, app->rl); if (song != NULL) { PianoRequestDataDeleteSeed_t subReqData; @@ -866,7 +857,7 @@ BarUiActCallback(BarUiActManageStation) { } } else if (selectBuf[0] == 'f') { PianoSong_t *song = BarUiSelectSong (&app->settings, - reqData.info.feedback, &app->input); + reqData.info.feedback, app->rl); if (song != NULL) { BarUiMsg (&app->settings, MSG_INFO, "Deleting feedback... "); BarUiActDefaultPianoCall (PIANO_REQUEST_DELETE_FEEDBACK, song); diff --git a/src/ui_readline.c b/src/ui_readline.c index eeb5c12..12f4ac8 100644 --- a/src/ui_readline.c +++ b/src/ui_readline.c @@ -21,13 +21,75 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include +//#include +//#include +//#include +//#include +//#include + +#include "ui_readline.h" +#include "console.h" #include -#include -#include #include -#include "ui_readline.h" +static inline char* BarReadlineNextUtf8 (char* ptr) { + if ((*ptr & 0x80) == 0) + return ptr + 1; + else if ((*ptr & 0xE0) == 0xC0) + return ptr + 2; + else if ((*ptr & 0xF0) == 0xE0) + return ptr + 3; + else if ((*ptr & 0xF8) == 0xF0) + return ptr + 4; + else + return ptr; +} + +static inline char* BarReadlinePriorUtf8 (char* ptr) { + while ((*(--ptr) & 0xC0) == 0x80) + /*continue*/; + + return ptr; +} + +static inline int BarReadlineEncodeUtf8 (int codePoint, char* utf8) { + if (codePoint < 0x80) { + utf8[0] = (char)codePoint; + return 1; + } + else if (codePoint < 0x0800) { + utf8[0] = (char)(0xC0 | ((codePoint >> 6) & 0x1F)); + utf8[1] = (char)(0x80 | ((codePoint >> 0) & 0x3F)); + return 2; + } + else if (codePoint < 0x10000) { + utf8[0] = (char)(0xE0 | ((codePoint >> 12) & 0x0F)); + utf8[1] = (char)(0x80 | ((codePoint >> 6) & 0x3F)); + utf8[2] = (char)(0x80 | ((codePoint >> 0) & 0x3F)); + return 3; + } + else if (codePoint < 0x110000) { + utf8[0] = (char)(0xF0 | ((codePoint >> 18) & 0x07)); + utf8[1] = (char)(0x80 | ((codePoint >> 12) & 0x3F)); + utf8[2] = (char)(0x80 | ((codePoint >> 6) & 0x3F)); + utf8[2] = (char)(0x80 | ((codePoint >> 0) & 0x3F)); + return 4; + } + else + return 0; +} + +struct _BarReadline_t { + DWORD DefaultAttr; +}; + +void BarReadlineInit(BarReadline_t* rl) { + static struct _BarReadline_t instance; + *rl = &instance; +} + +void BarReadlineDestroy(BarReadline_t rl) { +} /* return size of previous UTF-8 character */ @@ -46,152 +108,206 @@ static size_t BarReadlinePrevUtf8 (char *ptr) { * @param buffer * @param buffer size * @param accept these characters - * @param input fds + * @param readline * @param flags * @param timeout (seconds) or -1 (no timeout) * @return number of bytes read from stdin */ size_t BarReadline (char *buf, const size_t bufSize, const char *mask, - BarReadlineFds_t *input, const BarReadlineFlags_t flags, int timeout) { - size_t bufLen = 0; - unsigned char escapeState = 0; - fd_set set; - const bool echo = !(flags & BAR_RL_NOECHO); - - assert (buf != NULL); - assert (bufSize > 0); - assert (input != NULL); - - memset (buf, 0, bufSize); - - /* if fd is a fifo fgetc will always return EOF if nobody writes to - * it, stdin will block */ - while (1) { - int curFd = -1; - unsigned char chr; - struct timeval timeoutstruct; - - /* select modifies set and timeout */ - memcpy (&set, &input->set, sizeof (set)); - timeoutstruct.tv_sec = timeout; - timeoutstruct.tv_usec = 0; - - if (select (input->maxfd, &set, NULL, NULL, - (timeout == -1) ? NULL : &timeoutstruct) <= 0) { - /* fail or timeout */ - break; - } + BarReadline_t input, const BarReadlineFlags_t flags, int timeout) { + HANDLE handle = BarConsoleGetStdIn(); + DWORD timeStamp, waitResult; + int bufPos = 0, bufLen = 0; + char* bufOut = buf; - assert (sizeof (input->fds) / sizeof (*input->fds) == 2); - if (FD_ISSET(input->fds[0], &set)) { - curFd = input->fds[0]; - } else if (input->fds[1] != -1 && FD_ISSET(input->fds[1], &set)) { - curFd = input->fds[1]; - } - if (read (curFd, &chr, sizeof (chr)) <= 0) { - /* select() is going wild if fdset contains EOFed stdin, only check - * for stdin, fifo is "reopened" as soon as another writer is - * available - * FIXME: ugly */ - if (curFd == STDIN_FILENO) { - FD_CLR (curFd, &input->set); + const bool overflow = flags & BAR_RL_FULLRETURN; + const bool echo = !(flags & BAR_RL_NOECHO); + + assert(buf != NULL); + assert(bufSize > 0); + assert(input != NULL); + + memset(buf, 0, bufSize); + + if (timeout != INFINITE) { + // convert timeout to ms + timeout *= 1000; + + // get time stamp, required for simulating non-locking input timeouts + timeStamp = GetTickCount(); + } + else + timeStamp = 0; + + while (true) { + if (timeout != INFINITE) { + DWORD now = GetTickCount(); + if ((int)(now - timeStamp) < timeout) { + timeout -= (int)(now - timeStamp); + timeStamp = now; } - continue; + else + timeout = 0; } - switch (chr) { - /* EOT */ - case 4: - /* return */ - case 10: - if (echo) { - fputs ("\n", stdout); - } - buf[bufLen] = '\0'; - return bufLen; - break; - - /* clear line */ - case 21: - if (echo) { - while (bufLen > 0) { - const size_t moveSize = BarReadlinePrevUtf8 (&buf[bufLen]); - assert (bufLen >= moveSize); - - /* move caret and delete character */ - fputs ("\033[D\033[K", stdout); - bufLen -= moveSize; - } - fflush (stdout); - } - bufLen = 0; - break; - - /* escape */ - case 27: - escapeState = 1; - break; - - /* del */ - case 126: - break; - - /* backspace */ - case 8: /* ASCII BS */ - case 127: /* ASCII DEL */ - if (bufLen > 0) { - size_t moveSize = BarReadlinePrevUtf8 (&buf[bufLen]); - assert (bufLen >= moveSize); - memmove (&buf[bufLen-moveSize], &buf[bufLen], moveSize); - - bufLen -= moveSize; - - /* move caret back and delete last character */ - if (echo) { - fputs ("\033[D\033[K", stdout); - fflush (stdout); - } - } - break; - default: - /* ignore control/escape characters */ - if (chr <= 0x1F) { - break; - } - if (escapeState == 2) { - escapeState = 0; - break; - } - if (escapeState == 1 && chr == '[') { - escapeState = 2; - break; - } - /* don't accept chars not in mask */ - if (mask != NULL && !strchr (mask, chr)) { - break; - } - /* don't write beyond buffer's limits */ - if (bufLen < bufSize-1) { - buf[bufLen] = chr; - ++bufLen; - if (echo) { - putchar (chr); - fflush (stdout); - } - /* buffer full => return if requested */ - if (bufLen >= bufSize-1 && (flags & BAR_RL_FULLRETURN)) { + waitResult = WaitForSingleObject(handle, timeout); + + if (WAIT_OBJECT_0 == waitResult) { + INPUT_RECORD inputRecords[8]; + INPUT_RECORD* record; + DWORD recordsRead, i; + + ReadConsoleInput(handle, inputRecords, sizeof(inputRecords) / sizeof(*inputRecords), &recordsRead); + + for (i = 0, record = inputRecords; i < recordsRead; ++i, ++record) + { + int codePoint, keyCode; + + if ((record->EventType != KEY_EVENT) || !record->Event.KeyEvent.bKeyDown) + continue; + + keyCode = record->Event.KeyEvent.wVirtualKeyCode; + codePoint = record->Event.KeyEvent.uChar.UnicodeChar; + + switch (keyCode) { + case VK_LEFT: + if (bufPos > 0) + { + if (echo) { + BarConsoleMoveCursor(-1); + } + bufOut = BarReadlinePriorUtf8(bufOut); + --bufPos; + } + break; + + case VK_RIGHT: + if (bufPos < bufLen) + { + if (echo) { + BarConsoleMoveCursor(1); + } + bufOut = BarReadlineNextUtf8(bufOut); + ++bufPos; + } + break; + + case VK_HOME: if (echo) { - fputs ("\n", stdout); + BarConsoleMoveCursor(-bufPos); + } + bufPos = 0; + bufOut = buf; + break; + + case VK_END: + if (echo) { + BarConsoleMoveCursor(bufLen - bufPos); + } + bufPos = bufLen; + bufOut = buf + strlen(buf); + break; + + case VK_BACK: + if (bufPos > 0) { + int moveSize; + + char* oldBufOut = bufOut; + bufOut = BarReadlinePriorUtf8(bufOut); + moveSize = strlen(bufOut) - (oldBufOut - bufOut); + memmove(bufOut, oldBufOut, moveSize); + bufOut[moveSize] = '\0'; + + if (echo) { + BarConsoleMoveCursor(-1); + BarConsoleEraseCharacter(); + } + + --bufPos; + --bufLen; + } + break; + + case VK_DELETE: + if (bufPos < bufLen) { + int moveSize; + + if (echo) { + BarConsoleEraseCharacter(); + } + + char* nextCharOut = BarReadlineNextUtf8(bufOut); + moveSize = strlen(bufOut) - (bufOut - nextCharOut); + memmove(bufOut, nextCharOut, moveSize); + bufOut[moveSize] = '\0'; + + --bufLen; } - buf[bufLen] = '\0'; + break; + + case VK_RETURN: + if (echo) + fputc('\n', stdout); return bufLen; - } + + default: { + char encodedCodePoint[5]; + int encodedCodePointLength; + + /* + if (keyCode == VK_MEDIA_PLAY_PAUSE) { + codePoint = 'p'; + PlaySoundA("SystemNotification", NULL, SND_ASYNC); + } + else if (keyCode == VK_MEDIA_NEXT_TRACK) { + codePoint = 'n'; + PlaySoundA("SystemNotification", NULL, SND_ASYNC); + } + */ + + if (codePoint <= 0x1F) + break; + + /* don't accept chars not in mask */ + if (mask != NULL && (codePoint > 255 || !strchr(mask, (char)codePoint))) + break; + + encodedCodePointLength = BarReadlineEncodeUtf8(codePoint, encodedCodePoint); + encodedCodePoint[encodedCodePointLength] = '\0'; + + if (bufLen + encodedCodePointLength < (int)bufSize) + { + strncpy(bufOut, encodedCodePoint, encodedCodePointLength); + + if (echo) { + fputs(encodedCodePoint, stdout); + fflush(stdout); + } + + bufOut += encodedCodePointLength; + ++bufPos; + ++bufLen; + + if ((bufLen >= (int)(bufSize - 1)) && overflow) + { + if (echo) + fputc('\n', stdout); + return bufLen; + } + } + } + break; } - break; - } /* end switch */ - } /* end while */ - buf[0] = '\0'; - return 0; + } + } + else if (WAIT_TIMEOUT == waitResult) + break; + else + /* TODO: Handle errors. */ + break; + } + + return bufLen; } /* Read string from stdin @@ -200,7 +316,7 @@ size_t BarReadline (char *buf, const size_t bufSize, const char *mask, * @return number of bytes read from stdin */ size_t BarReadlineStr (char *buf, const size_t bufSize, - BarReadlineFds_t *input, const BarReadlineFlags_t flags) { + BarReadline_t input, const BarReadlineFlags_t flags) { return BarReadline (buf, bufSize, NULL, input, flags, -1); } @@ -208,7 +324,7 @@ size_t BarReadlineStr (char *buf, const size_t bufSize, * @param write result into this variable * @return number of bytes read from stdin */ -size_t BarReadlineInt (int *ret, BarReadlineFds_t *input) { +size_t BarReadlineInt (int *ret, BarReadline_t input) { int rlRet = 0; char buf[16]; @@ -222,7 +338,7 @@ size_t BarReadlineInt (int *ret, BarReadlineFds_t *input) { /* Yes/No? * @param default (user presses enter) */ -bool BarReadlineYesNo (bool def, BarReadlineFds_t *input) { +bool BarReadlineYesNo (bool def, BarReadline_t input) { char buf[2]; BarReadline (buf, sizeof (buf), "yYnN", input, BAR_RL_FULLRETURN, -1); if (*buf == 'y' || *buf == 'Y' || (def == true && *buf == '\0')) { diff --git a/src/ui_readline.h b/src/ui_readline.h index cf8ed52..9343e1a 100644 --- a/src/ui_readline.h +++ b/src/ui_readline.h @@ -24,8 +24,10 @@ THE SOFTWARE. #ifndef SRC_UI_READLINE_H_IFRX74VM #define SRC_UI_READLINE_H_IFRX74VM +#include "config.h" + #include -#include +#include typedef enum { BAR_RL_DEFAULT = 0, @@ -33,18 +35,16 @@ typedef enum { BAR_RL_NOECHO = 2, /* don't echo to stdout */ } BarReadlineFlags_t; -typedef struct { - fd_set set; - int maxfd; - int fds[2]; -} BarReadlineFds_t; +typedef struct _BarReadline_t *BarReadline_t; +void BarReadlineInit(BarReadline_t*); +void BarReadlineDestroy(BarReadline_t); size_t BarReadline (char *, const size_t, const char *, - BarReadlineFds_t *, const BarReadlineFlags_t, int); + BarReadline_t, const BarReadlineFlags_t, int); size_t BarReadlineStr (char *, const size_t, - BarReadlineFds_t *, const BarReadlineFlags_t); -size_t BarReadlineInt (int *, BarReadlineFds_t *); -bool BarReadlineYesNo (bool, BarReadlineFds_t *); + BarReadline_t, const BarReadlineFlags_t); +size_t BarReadlineInt (int *, BarReadline_t); +bool BarReadlineYesNo (bool, BarReadline_t); #endif /* SRC_UI_READLINE_H_IFRX74VM */ -- cgit v1.2.3