diff options
| -rw-r--r-- | src/console.c | 419 | ||||
| -rw-r--r-- | src/console.h (renamed from src/terminal.h) | 29 | ||||
| -rw-r--r-- | src/main.c | 278 | ||||
| -rw-r--r-- | src/main.h | 6 | ||||
| -rw-r--r-- | src/player.c | 451 | ||||
| -rw-r--r-- | src/player.h | 91 | ||||
| -rw-r--r-- | src/player2.c | 364 | ||||
| -rw-r--r-- | src/player2.h (renamed from src/terminal.c) | 62 | ||||
| -rw-r--r-- | src/settings.c | 75 | ||||
| -rw-r--r-- | src/settings.h | 6 | ||||
| -rw-r--r-- | src/ui.c | 218 | ||||
| -rw-r--r-- | src/ui.h | 6 | ||||
| -rw-r--r-- | src/ui_act.c | 77 | ||||
| -rw-r--r-- | src/ui_readline.c | 396 | ||||
| -rw-r--r-- | src/ui_readline.h | 20 | 
15 files changed, 1378 insertions, 1120 deletions
| 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ń <thedmd@interia.pl> + +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 <stdio.h> +#include <stdbool.h> +#include <fcntl.h> +#include <io.h> +#include <pthread.h> +#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/terminal.h b/src/console.h index c43d01b..4b3a073 100644 --- a/src/terminal.h +++ b/src/console.h @@ -1,6 +1,6 @@  /* -Copyright (c) 2008-2010 -	Lars-Dominik Braun <lars@6xq.net> +Copyright (c) 2015 +	Michał Cichoń <thedmd@interia.pl>  Permission is hereby granted, free of charge, to any person obtaining a copy  of this software and associated documentation files (the "Software"), to deal @@ -21,10 +21,25 @@ 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 +#ifndef SRC_CONSOLE_H_WY8F3MNH +#define SRC_CONSOLE_H_WY8F3MNH -void BarTermInit (); -void BarTermRestore (); +#include "config.h" -#endif /* SRC_TERMINAL_H_WY8F3MNH */ +#include <windows.h> + +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 */ @@ -23,35 +23,11 @@ THE SOFTWARE.  #include "config.h" -/* system includes */ -#include <stdlib.h> -#include <string.h> -#include <stdio.h> -/* fork () */ -#include <unistd.h> -#include <sys/select.h> -#include <time.h> -#include <ctype.h> -/* open () */ -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -/* tcset/getattr () */ -#include <termios.h> -#include <pthread.h>  #include <assert.h> -#include <stdbool.h> -#include <limits.h> -#include <signal.h> -/* waitpid () */ -#include <sys/types.h> -#include <sys/wait.h> - -/* pandora.com library */  #include <piano.h>  #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;  } @@ -28,21 +28,21 @@ THE SOFTWARE.  #include <piano.h> -#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 <lars@6xq.net> - -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 <unistd.h> -#include <string.h> -#include <math.h> -#include <stdint.h> -#include <limits.h> -#include <assert.h> -#include <arpa/inet.h> - -#include <libavcodec/avcodec.h> -#include <libavutil/avutil.h> -#include <libavfilter/avfilter.h> -#include <libavfilter/avfiltergraph.h> -#include <libavfilter/buffersink.h> -#include <libavfilter/buffersrc.h> -#ifdef HAVE_LIBAVFILTER_AVCODEC_H -/* required by ffmpeg1.2 for avfilter_copy_buf_props */ -#include <libavfilter/avcodec.h> -#endif -#include <libavutil/channel_layout.h> -#include <libavutil/opt.h> -#ifndef HAVE_AV_TIMEOUT -#include <libavutil/time.h> -#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 <lars@6xq.net> - -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 <sys/types.h> -#include <pthread.h> -#include <stdint.h> - -#include <ao/ao.h> -#include <libavformat/avformat.h> -#include <libavfilter/avfilter.h> -#include <libavfilter/avfiltergraph.h> -#include <piano.h> - -#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 <lars@6xq.net> + +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 <objbase.h> +#include <dshow.h> +#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/terminal.c b/src/player2.h index 2715d89..ac40eb4 100644 --- a/src/terminal.c +++ b/src/player2.h @@ -21,38 +21,32 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN  THE SOFTWARE.  */ -#include <termios.h> -#include <stdio.h> -#include <unistd.h> -#include <string.h> -#include <signal.h> - -#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); -} +#ifndef SRC_PLAYER2_H_CN979RE9 +#define SRC_PLAYER2_H_CN979RE9 + +#include "config.h" + +#include <stdbool.h> + +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 <string.h> -#include <stdlib.h> -#include <stdio.h> -#include <limits.h> -#include <assert.h> -#include <sys/types.h> -#include <pwd.h> -#include <unistd.h> - -#include <piano.h> -  #include "settings.h"  #include "config.h"  #include "ui_dispatch.h" +#include <stdlib.h> +#include <assert.h> + +#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 <piano.h> +#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 <piano.h> -  void BarSettingsInit (BarSettings_t *);  void BarSettingsDestroy (BarSettings_t *);  void BarSettingsRead (BarSettings_t *); @@ -25,22 +25,9 @@ THE SOFTWARE.  #include "config.h" -#include <stdio.h> -#include <stdarg.h> -#include <unistd.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <strings.h> -#include <assert.h> -#include <ctype.h> /* tolower() */ - -/* waitpid () */ -#include <sys/types.h> -#include <sys/wait.h> -  #include "ui.h"  #include "ui_readline.h" +#include <assert.h>  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 @@ -29,7 +29,7 @@ THE SOFTWARE.  #include <piano.h>  #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 <stdlib.h> +//#include <stdlib.h> +//#include <stdio.h> +//#include <string.h> +//#include <unistd.h> +//#include <assert.h> + +#include "ui_readline.h" +#include "console.h"  #include <stdio.h> -#include <string.h> -#include <unistd.h>  #include <assert.h> -#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 <stdbool.h> -#include <sys/select.h> +#include <stdlib.h>  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 */ | 
