diff --git a/cimp.bas b/cimp.bas index ad57fae..8d0ee67 100644 --- a/cimp.bas +++ b/cimp.bas @@ -3,6 +3,11 @@ declare library"terminkey" sub echooff() sub echoon() function termwidth() + function ipc_init&() + function ipc_check_message%(byval buf as _offset, byval max_len as long) + sub ipc_send_message(buf as string) + sub ipc_cleanup() + sub get_absolute_path(rel_path as string, byval abs_path as _offset, byval max_len as long) end declare $console:only @@ -22,6 +27,65 @@ if command$=""then goto quit end if +dim ipc_status as long +ipc_status = ipc_init + +if ipc_status = 0 then + ' --- CLIENT MODE --- + dim cmd_msg as string + + if command$(1) = "--next" then + cmd_msg = "NEXT" + elseif command$(1) = "--prev" then + cmd_msg = "PREV" + elseif command$(1) = "-v" or command$(1) = "--volume" then + cmd_msg = "VOL:" + command$(2) + elseif command$(1) = "--add" then + cmd_msg = "ADD:" + get_abs_path(command$(2)) + elseif command$(1) = "--playlist" then + cmd_msg = "GET_PLAYLIST" + else + ' Default behavior: Resolve target file/playlist and replace active queue + cmd_msg = "PLAY:" + get_abs_path(command$(1)) + end if + + ' Send instruction to the main player instance + ipc_send_message cmd_msg + + ' --- Two-way Client feedback for --playlist --- + if cmd_msg = "GET_PLAYLIST" then + ipc_cleanup + _delay 0.1 + + dim client_listen as long + client_listen = ipc_init + if client_listen = 1 then + dim reply_buf as string + reply_buf = space$(4096) + chr$(0) + dim start_wait as double + start_wait = timer + + do while timer - start_wait < 2 + dim reply_len as long + reply_len = ipc_check_message(_offset(reply_buf), 4096) + if reply_len > 0 then + print left$(reply_buf, reply_len) + exit do + end if + _limit 30 + loop + ipc_cleanup + end if + end if + + system ' Exit client instance completely +elseif ipc_status = -1 then + print "Error initializing Inter-Process Communication." + goto quit +end if + +' --- SERVER MODE (Main Player Execution continues below) --- + echooff cursoroff chdir _startdir$ @@ -258,10 +322,76 @@ while keyin<>27 _limit 30 if _exit then goto quit -wend + +' --- Poll Client Messages --- + dim incoming_buf as string + incoming_buf = space$(512) + chr$(0) + dim msg_len as long + msg_len = ipc_check_message(_offset(incoming_buf), 512) + + if msg_len > 0 then + dim client_cmd as string + client_cmd = left$(incoming_buf, msg_len) + + if client_cmd = "NEXT" then + playnext = 1 + elseif client_cmd = "PREV" then + playnext = -1 + elseif left$(client_cmd, 4) = "VOL:" then + volume = val(mid$(client_cmd, 5)) / 100 + if volume > 1 then volume = 1 + if volume < 0 then volume = 0 + _sndvol musichandle, volume + elseif left$(client_cmd, 4) = "ADD:" then + dim new_file as string + new_file = mid$(client_cmd, 5) + if _fileexists(new_file) then + if lcase$(right$(new_file, 4)) = ".m3u" then + parsem3u new_file, file() + else + redim _preserve file(ubound(file) + 1) as string + file(ubound(file)) = new_file + end if + end if + elseif left$(client_cmd, 5) = "PLAY:" then + dim replace_file as string + replace_file = mid$(client_cmd, 6) + if _fileexists(replace_file) then + redim file(0) as string + if lcase$(right$(replace_file, 4)) = ".m3u" then + parsem3u replace_file, file() + else + file(0) = replace_file + end if + i = 0 + playnext = 1 + end if + elseif client_cmd = "GET_PLAYLIST" then + dim p_idx as long + dim playlist_payload as string + playlist_payload = "=== Current Playlist ===" + chr$(10) + for p_idx = lbound(file) to ubound(file) + if p_idx = i then + playlist_payload = playlist_payload + "-> " + file(p_idx) + chr$(10) + else + playlist_payload = playlist_payload + " " + file(p_idx) + chr$(10) + end if + next p_idx + + ipc_cleanup + _delay 0.1 + ipc_send_message playlist_payload + _delay 0.1 + dim reinit as long + reinit = ipc_init + end if + end if + ' --- End Poll Client Messages --- + wend quit: _sndclose musichandle +ipc_cleanup print clearrest print clearrest; print cursorback; @@ -496,3 +626,16 @@ function getcodepoint& (utf8char$) getcodepoint&=(b1 and &h07)*262144+(b2 and &h3f)*4096+(b3 and &h3f)*64+(b4 and &h3f) end select end function + +function get_abs_path$ (relative_path as string) + dim abs_buf as string + abs_buf = space$(512) + chr$(0) ' Allocate buffer space for the C function + get_absolute_path relative_path, _offset(abs_buf), 512 + dim null_pos as long + null_pos = instr(abs_buf, chr$(0)) + if null_pos > 0 then + get_abs_path$ = _trim$(left$(abs_buf, null_pos - 1)) + else + get_abs_path$ = _trim$(abs_buf) + end if +end function diff --git a/terminkey.h b/terminkey.h index d8e7ade..81e35a4 100644 --- a/terminkey.h +++ b/terminkey.h @@ -3,11 +3,23 @@ #ifdef _WIN32 #include #include + #include + #include + #include + #include #else #include #include #include #include + #include + #include + #include + #include + #include + #include + #include + #include #endif // Special key codes (unified across platforms) @@ -21,8 +33,16 @@ void echooff(); void echoon(); int termwidth(); +int ipc_init(); +int ipc_check_message(uintptr_t buffer_ptr, int max_len); +void ipc_send_message(const char* message); +void ipc_cleanup(); +void get_absolute_path(const char* rel_path, uintptr_t abs_path_ptr, int max_len); + #ifdef _WIN32 +static HANDLE hServerPipe = INVALID_HANDLE_VALUE; + int terminkey() { if (_kbhit()) { int ch = _getch(); @@ -65,8 +85,91 @@ int termwidth() { return -1; } +int ipc_init() { + hServerPipe = CreateNamedPipe( + "\\\\.\\pipe\\cimp_ipc", + PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_NOWAIT, + 1, + 4096, + 4096, + 0, + NULL + ); + if (hServerPipe == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + if (err == ERROR_ACCESS_DENIED || err == ERROR_PIPE_BUSY) { + return 0; // Already running + } + return -1; // Other error + } + ConnectNamedPipe(hServerPipe, NULL); + return 1; +} + +int ipc_check_message(uintptr_t buffer_ptr, int max_len) { + if (hServerPipe == INVALID_HANDLE_VALUE) return 0; + + char* buffer = (char*)buffer_ptr; + DWORD bytesRead = 0; + BOOL connected = ConnectNamedPipe(hServerPipe, NULL) ? + TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); + + if (connected) { + BOOL success = ReadFile(hServerPipe, buffer, max_len - 1, &bytesRead, NULL); + if (success && bytesRead > 0) { + buffer[bytesRead] = '\0'; + DisconnectNamedPipe(hServerPipe); + ConnectNamedPipe(hServerPipe, NULL); + return (int)bytesRead; + } + DisconnectNamedPipe(hServerPipe); + ConnectNamedPipe(hServerPipe, NULL); + } + return 0; +} + +void ipc_send_message(const char* message) { + HANDLE hPipe = CreateFile( + "\\\\.\\pipe\\cimp_ipc", + GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + 0, + NULL + ); + if (hPipe != INVALID_HANDLE_VALUE) { + DWORD bytesWritten = 0; + WriteFile(hPipe, message, strlen(message), &bytesWritten, NULL); + CloseHandle(hPipe); + } +} + +void ipc_cleanup() { + if (hServerPipe != INVALID_HANDLE_VALUE) { + CloseHandle(hServerPipe); + hServerPipe = INVALID_HANDLE_VALUE; + } +} + +void get_absolute_path(const char* rel_path, uintptr_t abs_path_ptr, int max_len) { + char* abs_path = (char*)abs_path_ptr; + _fullpath(abs_path, rel_path, max_len); +} + #else +static int server_fd = -1; +static char socket_path[256] = ""; + +static void get_socket_path() { + if (socket_path[0] == '\0') { + uid_t uid = getuid(); + snprintf(socket_path, sizeof(socket_path), "/tmp/cimp_ipc_%u.sock", (unsigned int)uid); + } +} + int terminkey() { struct termios oldt, newt; int ch; @@ -126,4 +229,124 @@ int termwidth() { return -1; } +int ipc_init() { + get_socket_path(); + + int test_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (test_fd >= 0) { + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + if (connect(test_fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) { + close(test_fd); + return 0; // Already running + } + close(test_fd); + } + + unlink(socket_path); + + server_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (server_fd < 0) { + return -1; + } + + int flags = fcntl(server_fd, F_GETFL, 0); + fcntl(server_fd, F_SETFL, flags | O_NONBLOCK); + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + + if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + close(server_fd); + server_fd = -1; + return -1; + } + + if (listen(server_fd, 5) < 0) { + close(server_fd); + server_fd = -1; + unlink(socket_path); + return -1; + } + + return 1; +} + +int ipc_check_message(uintptr_t buffer_ptr, int max_len) { + if (server_fd < 0) return 0; + + char* buffer = (char*)buffer_ptr; + int client_fd = accept(server_fd, NULL, NULL); + if (client_fd < 0) { + return 0; + } + + int total_read = 0; + while (total_read < max_len - 1) { + int r = read(client_fd, buffer + total_read, max_len - 1 - total_read); + if (r <= 0) break; + total_read += r; + } + buffer[total_read] = '\0'; + close(client_fd); + return total_read; +} + +void ipc_send_message(const char* message) { + get_socket_path(); + int client_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (client_fd < 0) return; + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + + if (connect(client_fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) { + int total_written = 0; + int len = strlen(message); + while (total_written < len) { + int w = write(client_fd, message + total_written, len - total_written); + if (w <= 0) break; + total_written += w; + } + } + close(client_fd); +} + +void ipc_cleanup() { + if (server_fd >= 0) { + close(server_fd); + server_fd = -1; + } + get_socket_path(); + unlink(socket_path); +} + +void get_absolute_path(const char* rel_path, uintptr_t abs_path_ptr, int max_len) { + char* abs_path = (char*)abs_path_ptr; + char resolved[PATH_MAX]; + if (realpath(rel_path, resolved) != NULL) { + strncpy(abs_path, resolved, max_len); + abs_path[max_len - 1] = '\0'; + } else { + if (rel_path[0] == '/') { + strncpy(abs_path, rel_path, max_len); + abs_path[max_len - 1] = '\0'; + } else { + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd)) != NULL) { + snprintf(abs_path, max_len, "%s/%s", cwd, rel_path); + } else { + strncpy(abs_path, rel_path, max_len); + abs_path[max_len - 1] = '\0'; + } + } + } +} + #endif diff --git a/test_ipc.bas b/test_ipc.bas new file mode 100644 index 0000000..7402f88 --- /dev/null +++ b/test_ipc.bas @@ -0,0 +1,44 @@ +$console:only +declare library "terminkey" + function ipc_init&() + function ipc_check_message%(byval buf as _offset, byval max_len as long) + sub ipc_send_message(buf as string) + sub ipc_cleanup() + sub get_absolute_path(rel_path as string, byval abs_path as _offset, byval max_len as long) +end declare + +dim r as long +r = ipc_init +print "ipc_init returned:"; r + +if r = 1 then + print "We are the server! Waiting for a message..." + dim msg as string + msg = space$(100) + chr$(0) + dim start_time as double + start_time = timer + do while timer - start_time < 3 + dim n as long + n = ipc_check_message(_offset(msg), 100) + if n > 0 then + print "Received: "; left$(msg, n) + exit do + end if + _limit 10 + loop + ipc_cleanup +elseif r = 0 then + print "We are the client! Sending a message..." + ipc_send_message "Hello from client!" +else + print "Error initializing IPC" +end if + +dim rel as string +rel = "readme.md" +dim abs_path as string +abs_path = space$(512) + chr$(0) +get_absolute_path rel, _offset(abs_path), 512 +print "Absolute path of '"; rel; "' is: '"; _trim$(abs_path); "'" + +system diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..1355a0c --- /dev/null +++ b/todo.md @@ -0,0 +1,12 @@ +# todo +## ipc +add ipc functionality so it's possible while the program runs to: +- play next and previous track with `cimp --next` and `cimp --prev` +- change volume with `cimp --volume xx` +- add file to playlist with `cimp --add filename.ext` +- if started without `--add` it should clear the playlist and load and play the given file or playlist. +- if given the flag `--playlist` it should output the song array as a playlist. + +the c functions are ready in terminkey.h and an example of how it works is found in test_ipc.bas + +## recurcive add all music in folder