Skip to content

Instantly share code, notes, and snippets.

@maghul
Created March 25, 2015 11:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maghul/4ddaa5879ef0ece5dbe3 to your computer and use it in GitHub Desktop.
Save maghul/4ddaa5879ef0ece5dbe3 to your computer and use it in GitHub Desktop.
Discovery using UDP in GJS/Gnome-Shell
// ------
// Broadcasts discovery requests for Logitech Media Server and listens to replies and publish messages
// will send all updates as a hash to the callback.
// The content of the hash is
// IPAD: The address of the server
// NAME: The name of the server
// JSON: The port to connect to for the JSON API
// UUID: The unique identifier of the server instance.
// ------
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const ByteArray = imports.byteArray;
const Mainloop = imports.mainloop;
// Create a Discovery request. Variadaic function which takes
// a list of request strings and formats them for LMS server.
function appreq()
{
var ba= GLib.ByteArray.new(256);
ba[0]= 0x65; // 'e'=Start of extended request
for(var jj=0; jj<arguments.length; jj++)
{
var req= arguments[jj];
for ( var ii=0; (ii<req.length); ++ii) {
var ch= req.charCodeAt(ii);
ba[ba.length]= ch;
}
ba[ba.length]= 0;
}
return ba;
}
// Slice a ByteArray and return as a string.
function sliceToString( bb, s, e )
{
var rv = "";
for ( var ii=s; (ii<e); ++ii) {
rv += String.fromCharCode(bb[ii]);
}
return rv;
}
// Parse a discovery response.
// Will currently only handle extedended responses. (messages beginning with 'E')
function parseResponse( resp, n, src )
{
var rv= {};
if (resp[0]==0x45) {
rv["src"]= src;
rv["IPAD"]= src.get_address().to_string();
for ( var ii=1; (ii<n); ) {
var rtypa= sliceToString(resp,ii,ii+4);
var rlen= resp[ii+4];
var rdata= sliceToString(resp,ii+5, ii+5+rlen);
rv[rtypa]= rdata;
ii += 5+rlen;
}
return rv;
}
return null;
}
function dump(bb,s,e)
{
var rv = "";
for ( var ii=s; (ii<e); ++ii) {
rv += " "+bb[ii];
}
log( rv);
}
function scanForLMS(context, timer, callback)
{
let socket = new Gio.Socket({family: Gio.SocketFamily.IPV4, protocol: Gio.SocketProtocol.UDP, type: Gio.SocketType.DATAGRAM});
let address = new Gio.InetSocketAddress({
address: Gio.InetAddress.new_any(Gio.SocketFamily.IPV4),
port: 3483,
});
let dest = new Gio.InetSocketAddress({
address: Gio.InetAddress.new_from_string("255.255.255.255"),
port: 3483,
});
socket.init(null);
socket.bind( address, true);
socket.set_broadcast(true);
socket.set_blocking(false);
let wcond= GLib.IOCondition.IN | GLib.IOCondition.OUT;
var rx_fn= function () {
let buf = GLib.ByteArray.new(256);
for (let ii=0; (ii<256); ++ii) {
buf[ii]= 0;
}
buf[256]= 0;
let [n, src]= socket.receive_from(buf, null, dest );
let rv= parseResponse(buf, n, src);
if (rv) {
callback(rv);
}
return true;
};
var tx_fn= function () {
let n= socket.send_to( dest, appreq( "IPAD", "NAME", "JSON", "UUID" ), null);
return false;
};
var timeout_fn= function() {
var wsource = socket.create_source(GLib.IOCondition.OUT,null);
wsource.set_callback( tx_fn );
wsource.attach(context);
return true;
};
var source = socket.create_source(GLib.IOCondition.IN,null);
source.set_callback( rx_fn );
source.attach(context);
timeout_fn(); // Send first message immediately
let timeout= GLib.timeout_source_new(timer)
timeout.set_callback(timeout_fn);
timeout.attach(context);
}
// "Client" code
function cb(rv)
{
log( "cb:rv="+JSON.stringify(rv) );
}
function main() {
var mainloop= GLib.MainLoop.new(null, true);
var context= mainloop.get_context();
scanForLMS(context, 5000, cb);
mainloop.run();
log( " done..." );
}
main();
This is an example of UDP discovery specifically for discovering a
Logitech Media Server (LMS) on a local network.
This does *not* work since
1) Socket.create_source doesn't exist in typelib as it is marked
with (skip) in Glib/gio/gsocket.c
2) GJS will clone array arguments before passing them to the C-code which
will make the call to Socket.receive_from work and return the number of
bytes received as well as the source of the packet. The buffer content
will be unchanged as buffer actually read into is a freed clone.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment