Seite 1 von 1

an /tmp/Socket Befehle schicken oder was reinschreiben

Verfasst: 16.12.2025 16:42
von jogo
Diese Thematik ist neu für mich.
Habe einen Tip gefunden, um mpv (den ich via RunProgram() gestartet habe) während einer Audio-Wiedergabe einige Befehle zu schicken. ZB. Pause/Play (ist eigentlich der einzige, den ich benötige (vorerst)).
Dafür setzte ich beim Aufruf von mpv den Parameter:

Code: Alles auswählen

--input-ipc-server=/tmp/mpvsocket
Und wie der Json Befehl für Pause heißt, habe auch rausgekriegt:

Code: Alles auswählen

{"command": ["set_property", "pause", true]}
mpv erstellt auch artig eine Datei in /tmp/ die "mpvsocket" heißt.
So! Bis hierher kann ich alles nachvollziehen ;) Und wie genau kann jetzt mit Purebasic in diese "mpvsocket" das Kommando reinschreiben oder was auslesen (falls ich eine Antwort von mpv erhalte)?
Das wird ja vermutlich nicht so trivial einfach mit OpenFile() und WriteStringN() erledigt sein.
Hat jemand dazu Erfahrung, damit ich überhaupt erstmal weiß, mit was ich es hier eigentlich zu tun habe? :)

Re: an /tmp/Socket Befehle schicken oder was reinschreiben

Verfasst: 16.12.2025 17:04
von NicTheQuick
Mit OpenFile() geht das nicht, mit OpenNetworkConnection() vielleicht, aber dafür bräuchte es wohl ein neues Flag und nicht nur TCP oder UDP.

Es würde normalerweise mit diesen Befehlen gehen:

Code: Alles auswählen

ImportC ""
  socket(domain.l, type.l, protocol.l)
  connect(fd.l, *addr, addrlen.l)
  read(fd.l, *buf, len.l)
  write(fd.l, *buf, len.l)
  close(fd.l)
EndImport
Aber PureBasic mag `read` nicht, weil das so heißt wie das Schlüsselwort `Read`. 🤔

Ich hab leider gerade wenig Zeit mir das genauer anzuschauen, aber vielleicht schubst es dich ja in die richtige Richtung.

Re: an /tmp/Socket Befehle schicken oder was reinschreiben

Verfasst: 16.12.2025 17:23
von jogo
ah Danke, irgendwie klang dieses "socked" auch nach Netzwerk, aber ich dachte, es wäre Zufall. Ich teste mal mit deinem Ansatz etwas rum. ;)
NicTheQuick hat geschrieben: 16.12.2025 17:04 ...mit OpenNetworkConnection() vielleicht, aber dafür bräuchte es wohl ein neues Flag und nicht nur TCP oder UDP.
ok - ich weiß nicht, was da dranhängt, aber wäre das was für die Ideen/Wunschliste an Fred? Oder ist Kommunikation über Sockets eher selten?

Re: an /tmp/Socket Befehle schicken oder was reinschreiben

Verfasst: 16.12.2025 18:22
von NicTheQuick
In der Linux-Welt sind Sockets schon recht häufig. Ruf mal `netstat -x` auf, das listet dir alle aktuell existierenden Sockets auf. Viele Komponenten des Systems kommunizieren auf diese Weise untereinander.
Also ja, ein Feature-Request an Fred wäre interessant, aber ich schätze da wird nicht viel passieren, weil es das unter Windows so nicht gibt.

Re: an /tmp/Socket Befehle schicken oder was reinschreiben

Verfasst: 17.12.2025 00:16
von jogo
'read' habe ich erstmal auskommentiert. Für Play/Pause ist das ja nicht zwingend - ich hör ja, ob es funktioniert ;).
bin in der Verwendung nicht ganz klar.

Code: Alles auswählen

ImportC ""
  socket(domain.l, type.l, protocol.l)
  connect(fd.l, *addr, addrlen.l) 
  read(fd.l, *buf, len.l)
  write(fd.l, *buf, len.l)
  close(fd.l)
EndImport
connect()
fd.l = Verbindung ID --> wenn ja, wo erhalte ich sie?
*addr = Zeiger auf Speicherbereich mit "/tmp/mpvsocket"
addrlen.l = Länge davon

write()
fd.l = Verbindung ID?
*buf = Zeiger auf Speicherbereich mit "{"command": ["set_property", "pause", true]}"
len.l = Länge davon

Was macht socket() und wie wende ich es an?

Re: an /tmp/Socket Befehle schicken oder was reinschreiben

Verfasst: 18.12.2025 20:27
von jogo
Hab etwas geforscht, wie man diese C-Funktionen verwendet und ich denke, ich bin bin auf einen guten Weg.
Ich hänge jetzt beim Senden fest - eigentlich das, wo ich glaubte, es ist am einfachsten (von der Logik her).
Schlimmer war, rauszufinden wie man den Socket überhaupt baut. Aber bis zum Verbinden habe ich es erstmal geschafft.
Könntet ihr vllt. mal schauen, warum das Senden schiefgeht? Den mpv-Aufruf habe oben reingeschrieben (fürs Terminal):

Code: Alles auswählen

;Verbindung mit mpv-Soket aufbauen, über importierte c-Funktionen
; mpv-Aufruf: mpv --input-ipc-server=/tmp/mpvsocket /pfad/zum/audioverzeichnis/
Global.s addr, buf
Global.l fd, addrlen, domain, type, protocol
Global.i verb
;Was ich rausgekriegt habe:
;Der mpv IPC-Socket ist ein Unix Domain Socket (AF_UNIX).
;AF_UNIX ist typischerweise 1
;SOCK_STREAM ist 1 (für TCP-ähnliche Verbindung)
;protocol ist 0 (Standard)

addr = "/tmp/mpvsocket"
addrlen = Len(addr)
domain = 1
type = 1
protocol = 0

ImportC ""
  socket(domain.l, type.l, protocol.l)
  connect(fd.l, *addr, addrlen.l)
  ;Read(fd.l, *buf, len.l)  ;gibt fehlermeldung
  write(fd.l, *buf, len.l)
  close(fd.l)
EndImport

Procedure mpv_befehl(akt.i)
  ;akt: 0=play 1=pause  
  Select akt
    Case 0 ;play
      buf = "{" + #DQUOTE$ + "command" + #DQUOTE$ + ": [" + #DQUOTE$ + "set_property" + #DQUOTE$ + ", " + #DQUOTE$ + "pause" + #DQUOTE$ + ", false]}" + Chr(10)    
    Case 1 ;pause
      buf = "{" + #DQUOTE$ + "command" + #DQUOTE$ + ": [" + #DQUOTE$ + "set_property" + #DQUOTE$ + ", " + #DQUOTE$ + "pause" + #DQUOTE$ + ", true]}" + Chr(10)   
  EndSelect
  Debug write(fd,@buf,Len(buf))    
EndProcedure


;fenster und bedienelemente bauen
OpenWindow(0, 50, #PB_Ignore, 150, 40, "mpvR",#PB_Window_Tool)
StickyWindow(0,#True) ;fenster fest im vordergrund

ButtonGadget(1, 0, 5, 45, 25, "Pause",#PB_Button_Toggle)
DisableGadget(1, #True)  ; pause/play erstmal deaktivieren, bis ich die socketverbindung hergestellt habe

fd = socket(domain,type,protocol) ;socket bauen
If fd                             ;wenn socket ok
  Debug "Socket OK"
  
  verb = connect(fd,@addr,addrlen)  ;verbindung herstellen
  If verb                           ;wenn verbunden  
    Debug "Verbindung OK"
    DisableGadget(1, #False)   ;taste freigeben
  Else
    Debug "keine Verbindung"
    SetGadgetText(1,"???")
  EndIf ;wenn verbunden
  
Else
  Debug "Socket wurde nicht gebaut"
EndIf ;wenn socked ok

;-    beginn ereignisschleife -------------------
Repeat ;ereignisschleife   
  Select WaitWindowEvent()       
    Case #PB_Event_CloseWindow   ;wenn x gedrückt  
      Break                      ;aus schleife hüpfen       
      
    Case #PB_Event_Gadget        ;wenn checkbox/button o.ä.
      Select EventGadget()       ;welches gadget genau?
        Case 1                   ;pause/Play taste
          mpv_befehl(GetGadgetState(1)) ;GetGadgetState(1) liefert 1, wenn gedrückt -> Pause // oder 0, wenn nicht gedrückt -> play
          
      EndSelect ;welches gadget   
  EndSelect     ;welches fensterereignis       
ForEver 

End

Re: an /tmp/Socket Befehle schicken oder was reinschreiben

Verfasst: 19.12.2025 11:38
von NicTheQuick
Cool, dass du voran gekommen bist. Ich hab leider nicht mehr danach gucken können.

Ohne es getestet zu haben, schätze ich liegt es daran, dass du vergisst, das Purebasic-Strings Unicode (2 Bytes pro Zeichen) sind. Mit `len(buf)` erhältst du zwar die Zeichenanzahl des Strings, aber er besteht ja aus doppelt so vielen Bytes.

Ich würde deshalb auf `UTF8()` zurückgreifen:

Code: Alles auswählen

Procedure mpv_befehl(akt.i)
	;akt: 0=play 1=pause  
	Select akt
		Case 0 ;play
			buf = "{" + #DQUOTE$ + "command" + #DQUOTE$ + ": [" + #DQUOTE$ + "set_property" + #DQUOTE$ + ", " + #DQUOTE$ + "pause" + #DQUOTE$ + ", false]}" + Chr(10)    
		Case 1 ;pause
			buf = "{" + #DQUOTE$ + "command" + #DQUOTE$ + ": [" + #DQUOTE$ + "set_property" + #DQUOTE$ + ", " + #DQUOTE$ + "pause" + #DQUOTE$ + ", true]}" + Chr(10)   
	EndSelect
	Protected *utf8_command = UTF8(buf)
	Debug write(fd, *utf8_command, MemorySize(*utf8_command))
	FreeMemory(*utf8_command)
EndProcedure

Re: an /tmp/Socket Befehle schicken oder was reinschreiben

Verfasst: 19.12.2025 18:47
von jogo
Danke, natürlich gleich probiert. Senden liefert weiterhin -1. Es besteht jedoch ein kleiner Unterschied - im utf8-Speicher wird ein Byte mehr gezählt.
Andersrum habe ich ja beim Bauen des Sockets auch einfach den String gezählt und den Zeiger übergeben, was auch funktionierte (hoffentlich).
Zumindest erhalte ich 11 zurück und beim Verbinden zB. 4294967295.
Hier die Version mit deinem Vorschlag und paar Debug-Ausgaben:

Code: Alles auswählen

;Verbindung mit mpv-Soket aufbauen, über importierte c-Funktionen
; mpv-Aufruf: mpv --input-ipc-server=/tmp/mpvsocket /pfad/zum/audioverzeichnis/
Global.s addr, buf
Global.l fd, addrlen, domain, type, protocol
Global.i verb

;Was ich rausgekriegt habe:
;Der mpv IPC-Socket ist ein Unix Domain Socket (AF_UNIX).
;AF_UNIX ist typischerweise 1
;SOCK_STREAM ist 1 (für TCP-ähnliche Verbindung)
;protocol ist 0 (Standard)

addr = "/tmp/mpvsocket"
addrlen = Len(addr)
domain = 1
type = 1
protocol = 0

ImportC ""
  socket(domain.l, type.l, protocol.l)
  connect(fd.l, *addr, addrlen.l)
  ;Read(fd.l, *buf, len.l)  ;gibt fehlermeldung
  write(fd.l, *buf, len.l)
  close(fd.l)
EndImport

Procedure mpv_befehl(akt.i)
  ;akt: 0=play 1=pause   
  Select akt
    Case 0 ;play
      buf = "{" + #DQUOTE$ + "command" + #DQUOTE$ + ": [" + #DQUOTE$ + "set_property" + #DQUOTE$ + ", " + #DQUOTE$ + "pause" + #DQUOTE$ + ", false]}" + Chr(10)    
    Case 1 ;pause
      buf = "{" + #DQUOTE$ + "command" + #DQUOTE$ + ": [" + #DQUOTE$ + "set_property" + #DQUOTE$ + ", " + #DQUOTE$ + "pause" + #DQUOTE$ + ", true]}" + Chr(10)   
  EndSelect  
     Debug "String-Bytes: " + Len(buf)
  ;Debug write(fd,@buf,Len(buf))  
  Protected *utf8_command = UTF8(buf)
     Debug "Memory-Bytes: " + MemorySize(*utf8_command)
  Debug write(fd, *utf8_command, MemorySize(*utf8_command))
	FreeMemory(*utf8_command)
EndProcedure


;fenster und bedienelemente bauen
OpenWindow(0, 50, #PB_Ignore, 150, 40, "mpvR",#PB_Window_Tool)
StickyWindow(0,#True) ;fenster fest im vordergrund

ButtonGadget(1, 0, 5, 45, 25, "Pause",#PB_Button_Toggle)
DisableGadget(1, #True)  ; pause/play erstmal deaktivieren, bis ich die socketverbindung hergestellt habe

fd = socket(domain,type,protocol) ;socket bauen
If fd                             ;wenn socket ok
  Debug "Socket OK: " + Str(fd)
  
  verb = connect(fd,@addr,addrlen)  ;verbindung herstellen
  If verb                           ;wenn verbunden  
    Debug "Verbindung OK: " + Str(verb)
    DisableGadget(1, #False)   ;taste freigeben
  Else
    Debug "keine Verbindung"
    SetGadgetText(1,"???")
  EndIf ;wenn verbunden
  
Else
  Debug "Socket wurde nicht gebaut"
EndIf ;wenn socked ok

;-    beginn ereignisschleife -------------------
Repeat ;ereignisschleife   
  Select WaitWindowEvent()       
    Case #PB_Event_CloseWindow   ;wenn x gedrückt  
      close(fd)                  ;socket schließen
      Break                      ;aus schleife hüpfen       
      
    Case #PB_Event_Gadget        ;wenn checkbox/button o.ä. gedruckt wurde
      Select EventGadget()       ;auswerten, welches gadget genau gedrückt wurde
        Case 1                   ;pause/Play taste
          mpv_befehl(GetGadgetState(1)) ;GetGadgetState(1) liefert 1, wenn gedrückt -> Pause // oder 0, wenn nicht gedrückt -> play
          
      EndSelect ;welches gadget   
  EndSelect     ;welches fensterereignis       
ForEver 

End
PS: habe auch etwas mit dem Kommandozeilen-Tool socat probiert. Es überbrückt einen Unix Domain Socket mit einer TCP-Verbindung.
Mit viel probieren hat das auch funktioniert. Muß halt zusätzlich installiert und mit RunProgram() gestartet werden. Besser gefallen würde mir jedoch die PB interne Lösung ;)

Re: an /tmp/Socket Befehle schicken oder was reinschreiben

Verfasst: 07.01.2026 17:21
von jogo
ich hab leider nicht geschafft, eine funktionierende Socket-Verbindung mit den C-Befehlen zu bauen. Irgendwann hab ich mal den KI-Chat von Ecosia gefragt, aber dann wurde alles sehr (für mich) kompliziert und hat trotzdem nicht funktioniert. Eigentlich das gleiche Ergebnis, welches ich zuvor erreicht habe.
Jetzt hab ich mich für die Verwendung von socat entschieden. Mußte mich da auch reinwühlen, aber läuft :)
Hier mal ein lauffähiger Test/Demo Code - vllt. etwas zu ausführlich kommentiert, aber kann man ja entfernen. Ich brauch das, damit ich später noch durchsehe ;)

Code: Alles auswählen

;- top --
; Beispiel: Steuerung von mpv über TCP-IPC mit PureBasic 6.21 // Zum Testen mpv im Terminal starten:
; mpv --input-ipc-server=/tmp/mpvsocket /pfad/zum/audioverzeichnis/           //mpvsocket ist ein variabeler Name, darf pro Instanz nur 1x vorkommen //einach name ändern
; socat muß installiert sein. Check im Terminal mit 'which socat'. Sonst 'sudo apt install socat'.
; socat starten: socat TCP-LISTEN:50101,reuseaddr, UNIX-CONNECT:/tmp/mpvsocket        //die Port-Nr. darf pro Instanz nur 1x vorkommen //einfach hochzählen
;       listen: 	Wird bei TCP-Lauschen verwendet, um den Socket in den Lausch-Modus zu versetzen.
;    reuseaddr: 	Erlaubt das sofortige Wiederverwenden einer lokalen Adresse (Port), auch wenn sie noch in TIME_WAIT ist.
;         fork: 	Erlaubt mehrere gleichzeitige Verbindungen, indem für jede Verbindung ein neuer Prozess gestartet wird.
;                 habe fork weggelassen, weil sich socat dann automatisch beendet, wenn die Verbindung wieder geschlossen wird //mit fork bleibt socat aktiv
;                 falls jedoch weitere Pcs im Netzwerk auf den gleichen mpv-Socket zugreifen wollen, muß fork gesetzt werden und am Zielsystem manuell beendet werden
; wenn mpv läuft, startet dieses Programm socat und erstellt eine TCP-Verbindung mit den mpv-socket
; ich sende JSON-Befehle zum Pausieren und Fortsetzen der Wiedergabe und zum Beenden.
;Dynamische/Private Ports 	49152 – 65535 	Für private oder temporäre Zwecke vorgesehen.
; ---------------------------------------------------------

Define.s mpvpfd = "/tmp/mpvsocket"
Define.i mpvport = 50101, so_ID, sid

Procedure.i mpv_socat(port.i,pfd.s)
  ;socat TCP-LISTEN:50100,reuseaddr, UNIX-CONNECT:/tmp/mpvsocket //beide adressen müssen mit einem Leerzeichen getrennt sein // die argumente für eine adresse mit komma OHNE leerzeichen
  ;Dies muß nur auf dem pc gemacht werden, auf dem mpv mit der option --input-ipc-server=/tmp/mpvsocket gestartet wurde 
  ;// bei fernsteuerung vom anderen pc muß nur tcp zur ip des mpv-pc verbunden werden und auf dem mpv-pc socat mit der zusätzlichen option fork gestartet werden
  ;Param: tcp-port // mpv-socket-pfad
ProcedureReturn RunProgram("socat","TCP-LISTEN:" + port + "," + "reuseaddr" + ", " + "UNIX-CONNECT:" + pfd,"") ;alle argumente einzeln, falls ich einige Parameter ändern möchte
EndProcedure

Procedure.i mpv_socket(status.i,port_soid.i,ip.s="127.0.0.1")
  ;verbindet oder trennt mpv-socket-verbindungen
  ;param: status:1=verbinden 0=trennen// port_soid: port-nr, wenn verbunden werden soll, Socket-ID, wenn getrennt werden soll// ip: IPadresse 127.0.0.1 für local 
  ;       ;beim beenden (status=0) kann die IP beim Aufruf weggelassen werden. Wird sie auch beim verbinden (status=1) weggelassen, wird local 127.0.0.1 verwendet
  Select status ;verbindeen oder trennen?
    Case 1      ;verbinden
      ProcedureReturn OpenNetworkConnection(ip, port_soid, #PB_Network_TCP)
    Case 0     ;trennen
      If  IsProgram(sid) ;wenn socat noch läuft
        CloseNetworkConnection(port_soid) 
      EndIf   
      ProcedureReturn 0 ;beim beende wird immer 0 zurückgegeben, weil dies auch nicht ausgewertet wird
  EndSelect 
  ;Rückgabe: bei erfolg die socketID oder: 0, bei verbindungs-fehler und beim beenden
EndProcedure

Procedure mpv_command(soid.i,funkt.i)
  ;sendet das json kommando an mpv-socket// param: soid: socked-ID// funkt: 1=pause, 0=play
  Protected result.i
  Select funkt ;was soll mpv machen?
    Case 1     ;pause       
     result = SendNetworkString(soid, "{" + #DQUOTE$ + "command" + #DQUOTE$ + ": [" + #DQUOTE$ + "set_property" + #DQUOTE$ + ", " + #DQUOTE$ + "pause" + #DQUOTE$ + ", true]}" + Chr(10)) 
     If result ;wenn gesendet
       SetGadgetText(1,"Play") ;toggelbutton neu beschriften
     Else
       SetGadgetText(1,"???") ;sonst mitteilen
     EndIf  
     
    Case 0     ;play
     result = SendNetworkString(soid, "{" + #DQUOTE$ + "command" + #DQUOTE$ + ": [" + #DQUOTE$ + "set_property" + #DQUOTE$ + ", " + #DQUOTE$ + "pause" + #DQUOTE$ + ", false]}" + Chr(10))    
     If result ;wenn gesendet
       SetGadgetText(1,"Pause") ;toggelbutton neu beschriften
     Else
       SetGadgetText(1,"???") ;sonst mitteilen
     EndIf
     
   Case 2      ;beenden
     result = SendNetworkString(soid, "{" + #DQUOTE$ + "command" + #DQUOTE$ + ": [" + #DQUOTE$ + "quit" + #DQUOTE$ + "]}" + Chr(10)) 
     If result ;wenn gesendet
       SetGadgetText(1,"???") ;mitteilen
       DisableGadget(1, #True); pause/play deaktivieren
       DisableGadget(2, #True); Ende deaktivieren
     EndIf 
       
   EndSelect
   ;weitere Kommandos sind möglich:
    ;ein titel zurück:
    ;result = SendNetworkString(soid, "{" + #DQUOTE$ + "command" + #DQUOTE$ + ": [" + #DQUOTE$ + "playlist_prev" + #DQUOTE$ + "]}" + Chr(10)) 
    ;ein titel vor:      
    ;result = SendNetworkString(soid, "{" + #DQUOTE$ + "command" + #DQUOTE$ + ": [" + #DQUOTE$ + "playlist_next" + #DQUOTE$ + "]}" + Chr(10))   
EndProcedure

Procedure fensterelemente()
  ;fenster und bedienelemente bauen
  OpenWindow(0, 50, #PB_Ignore, 150, 40, "mpvR",#PB_Window_Tool)
  StickyWindow(0,#True) ;fenster fest im vordergrund
 
  ButtonGadget(1, 0, 5, 45, 25, "Pause",#PB_Button_Toggle)
  ButtonGadget(2, 47, 5, 45, 25, "Ende")
  ButtonGadget(3, 94, 5, 45, 25, "---",#PB_Button_Toggle)
  
  DisableGadget(1, #True)  ; pause/play erstmal deaktivieren, bis ich die socketverbindung hergestellt habe
  DisableGadget(2, #True)  ; -"-
EndProcedure

fensterelemente()
sid = mpv_socat(mpvport,mpvpfd) ;mpv-socked mit tcp auf port 50100 verknüpfen // port der tcp-verbindung // mpv-socket-pfad (wird beim mpv-start als parameter definiert)
Delay(200) ;tick warten
so_ID = mpv_socket(1,mpvport)  ;//1=verbinden,Port
If so_ID ;wenn verbunden  
  DisableGadget(1, #False)
  DisableGadget(2, #False)
Else
  SetGadgetText(1,"???")
  SetGadgetText(2,"???")
EndIf 

;-    beginn ereignisschleife -------------------
 Repeat ;ereignisschleife
   
   Select WaitWindowEvent()
       
     Case #PB_Event_CloseWindow   ;wenn x gedrückt     
       mpv_socket(0,so_ID)        ;tcp-verbindung trennen // 0=trennen, tcp-verbindungs-ID
       Break                      ;aus schleife hüpfen       
       
     Case #PB_Event_Gadget        ;wenn checkbox/button o.ä. gedruckt wurde
       Select EventGadget()       ;auswerten, welches gadget genau gedrückt wurde
         Case 1                   ;pause/Play taste
          mpv_command(so_ID,GetGadgetState(1)) ;GetGadgetState(1) liefert 1, wenn gedrückt -> Pause // oder 0, wenn nicht gedrückt -> play

         Case 2                   ;zweite taste mpv beenden
           mpv_command(so_ID,2)

         Case 3                   ;dritte taste
           Debug "wupp_3 = " + Str(GetGadgetState(3))
           
        EndSelect ;welches gadget   
        
   EndSelect ;welches fensterereignis       
 ForEver 

End