Add Inter-Process Communication

This commit is contained in:
visionmercer 2026-06-23 10:21:17 +02:00
commit 19bf4b5288
4 changed files with 423 additions and 1 deletions

143
cimp.bas
View file

@ -3,6 +3,11 @@ declare library"terminkey"
sub echooff() sub echooff()
sub echoon() sub echoon()
function termwidth() 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 end declare
$console:only $console:only
@ -22,6 +27,65 @@ if command$=""then
goto quit goto quit
end if 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 echooff
cursoroff cursoroff
chdir _startdir$ chdir _startdir$
@ -258,10 +322,76 @@ while keyin<>27
_limit 30 _limit 30
if _exit then goto quit if _exit then goto quit
' --- 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 wend
quit: quit:
_sndclose musichandle _sndclose musichandle
ipc_cleanup
print clearrest print clearrest
print clearrest; print clearrest;
print cursorback; 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) getcodepoint&=(b1 and &h07)*262144+(b2 and &h3f)*4096+(b3 and &h3f)*64+(b4 and &h3f)
end select end select
end function 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

View file

@ -3,11 +3,23 @@
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#include <conio.h> #include <conio.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#else #else
#include <termios.h> #include <termios.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <stdint.h>
#endif #endif
// Special key codes (unified across platforms) // Special key codes (unified across platforms)
@ -21,8 +33,16 @@ void echooff();
void echoon(); void echoon();
int termwidth(); 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 #ifdef _WIN32
static HANDLE hServerPipe = INVALID_HANDLE_VALUE;
int terminkey() { int terminkey() {
if (_kbhit()) { if (_kbhit()) {
int ch = _getch(); int ch = _getch();
@ -65,8 +85,91 @@ int termwidth() {
return -1; 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 #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() { int terminkey() {
struct termios oldt, newt; struct termios oldt, newt;
int ch; int ch;
@ -126,4 +229,124 @@ int termwidth() {
return -1; 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 #endif

44
test_ipc.bas Normal file
View file

@ -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

12
todo.md Normal file
View file

@ -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