[vlc-devel] [PATCH] lua: merge telnet interface into rc

Pierre Ynard linkfanel at yahoo.fr
Tue Mar 15 23:05:28 CET 2011


The crappy input buffering code of rc is replaced by the (now) decent
one of telnet. A new telnet transport is relatively cleanly added, and
VLM commands are made available (over any transport). Example of use:

vlc -Irc --rc-host "telnet://localhost:4212"

This is to be completed with the removal of the telnet script
and re-aliasing of the telnet interface. Do we care to maintain
compatibility of the --lua-config parameter? Given that there is no way
to alias/wrap lua scripts, it will be a bit tedious.


diff --git a/share/lua/intf/modules/host.lua b/share/lua/intf/modules/host.lua
index 4601b25..cbef3c6 100644
--- a/share/lua/intf/modules/host.lua
+++ b/share/lua/intf/modules/host.lua
@@ -64,7 +64,7 @@ For complete examples see existing VLC Lua interface modules (ie telnet.lua)
 module("host",package.seeall)
 
 status = { init = 0, read = 1, write = 2, password = 3 }
-client_type = { net = 1, stdio = 2, fifo = 3 }
+client_type = { net = 1, stdio = 2, fifo = 3, telnet = 4 }
 
 function host()
     -- private data
@@ -116,7 +116,8 @@ function host()
         end
         for i, c in pairs(clients) do
             if c == client then
-                if client.type == client_type.net then
+                if client.type == client_type.net
+                or client.type == client_type.telnet then
                     if client.wfd ~= client.rfd then
                         vlc.net.close( client.rfd )
                     end
@@ -145,7 +146,7 @@ function host()
     local function new_client( h, fd, wfd, t )
         if fd < 0 then return end
         local w, r
-        if t == client_type.net then
+        if t == client_type.net or t == client_type.telnet then
             w = send
             r = recv
         else if t == client_type.stdio or t == client_type.fifo then
@@ -175,7 +176,7 @@ function host()
     end
 
     -- public methods
-    local function _listen_tcp( h, host, port )
+    local function _listen_tcp( h, host, port, telnet )
         if listeners.tcp and listeners.tcp[host]
                          and listeners.tcp[host][port] then
             error("Already listening on tcp host `"..host..":"..tostring(port).."'")
@@ -186,15 +187,18 @@ function host()
         if not listeners.tcp[host] then
             listeners.tcp[host] = {}
         end
-        local listener = vlc.net.listen_tcp( host, port )
-        listeners.tcp[host][port] = listener
+        listeners.tcp[host][port] = true
         if not listeners.tcp.list then
             -- FIXME: if host == "list" we'll have a problem
             listeners.tcp.list = {}
             local m = { __mode = "v" } -- week values
             setmetatable( listeners.tcp.list, m )
         end
-        table.insert( listeners.tcp.list, listener )
+        local listener = vlc.net.listen_tcp( host, port )
+        local type = telnet and client_type.telnet or client_type.net;
+        table.insert( listeners.tcp.list, { data = listener,
+                                            type = type,
+                                          } )
     end
 
     local function _listen_stdio( h )
@@ -217,7 +221,7 @@ function host()
                 h:listen_stdio()
             else
                 u = vlc.net.url_parse( url )
-                h:listen_tcp( u.host, u.port )
+                h:listen_tcp( u.host, u.port, (u.protocol == "telnet") )
             end
         end
     end
@@ -237,7 +241,7 @@ function host()
         filter_client( pollfds, status.write, vlc.net.POLLOUT )
         if listeners.tcp then
             for _, listener in pairs(listeners.tcp.list) do
-                for _, fd in pairs({listener:fds()}) do
+                for _, fd in pairs({listener.data:fds()}) do
                     pollfds[fd] = vlc.net.POLLIN
                 end
             end
@@ -257,10 +261,10 @@ function host()
             end
             if listeners.tcp then
                 for _, listener in pairs(listeners.tcp.list) do
-                    for _, fd in pairs({listener:fds()}) do
+                    for _, fd in pairs({listener.data:fds()}) do
                         if pollfds[fd] == vlc.net.POLLIN then
-                            local afd = listener:accept()
-                            new_client( h, afd, afd, client_type.net )
+                            local afd = listener.data:accept()
+                            new_client( h, afd, afd, listener.type )
                             break
                         end
                     end
diff --git a/share/lua/intf/rc.lua b/share/lua/intf/rc.lua
index e780048..4c32848 100644
--- a/share/lua/intf/rc.lua
+++ b/share/lua/intf/rc.lua
@@ -1,10 +1,11 @@
 --[==========================================================================[
  rc.lua: remote control module for VLC
 --[==========================================================================[
- Copyright (C) 2007-2009 the VideoLAN team
+ Copyright (C) 2007-2011 the VideoLAN team
  $Id$
 
  Authors: Antoine Cellerier <dionoea at videolan dot org>
+          Pierre Ynard
 
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
@@ -25,14 +26,17 @@ description=
 [============================================================================[
  Remote control interface for VLC
 
- This is a modules/control/rc.c look alike (with a bunch of new features)
+ This is a modules/control/rc.c look alike (with a bunch of new features).
+ It also provides a VLM interface copied from the telnet interface.
 
  Use on local term:
     vlc -I rc
  Use on tcp connection:
     vlc -I rc --lua-config "rc={host='localhost:4212'}"
- Use on multiple hosts (term + 2 tcp ports):
-    vlc -I rc --lua-config "rc={hosts={'*console','localhost:4212','localhost:5678'}}"
+ Use on telnet connection:
+    vlc -I rc --lua-config "rc={host='telnet://localhost:4212'}"
+ Use on multiple hosts (term + plain tcp port + telnet):
+    vlc -I rc --lua-config "rc={hosts={'*console','localhost:4212','telnet://localhost:5678'}}"
 
  Note:
     -I rc and -I luarc are aliases for -I lua --lua-intf rc
@@ -40,6 +44,7 @@ description=
  Configuration options setable throught the --lua-config option are:
     * hosts: A list of hosts to listen on.
     * host: A host to listen on. (won't be used if `hosts' is set)
+    * password: The password used for telnet clients.
  The following can be set using the --lua-config option or in the interface
  itself using the `set' command:
     * prompt: The prompt.
@@ -129,8 +134,18 @@ function alias(client,value)
     end
 end
 
+function lock(name,client)
+    if client.type == host.client_type.telnet then
+        client:switch_status( host.status.password )
+        client.buffer = ""
+    else
+        client:append("Error: the prompt can only be locked when logged in through telnet")
+    end
+end
+
 function logout(name,client)
-    if client.type == host.client_type.net then
+    if client.type == host.client_type.net
+    or client.type == host.client_type.telnet then
         client:send("Bye-bye!\r\n")
         client:del()
     else
@@ -146,7 +161,8 @@ function shutdown(name,client)
 end
 
 function quit(name,client)
-    if client.type == host.client_type.net then
+    if client.type == host.client_type.net
+    or client.type == host.client_type.telnet then
         logout(name,client)
     else
         shutdown(name,client)
@@ -279,6 +295,12 @@ function print_text(label,text)
 end
 
 function help(name,client,arg)
+    if arg == nil then
+        client:append("+----[ VLM commands ]")
+        local message, vlc_err = vlm:execute_command("help")
+        vlm_message_to_string( client, message, "|" )
+    end
+
     local width = client.env.width
     local long = (name == "longhelp")
     local extra = ""
@@ -551,6 +573,7 @@ commands_ordered = {
     { "license"; { func = print_text("License message",vlc.misc.license()); help = "print VLC's license message"; adv = true } };
     { "help"; { func = help; args = "[pattern]"; help = "a help message"; aliases = { "?" } } };
     { "longhelp"; { func = help; args = "[pattern]"; help = "a longer help message" } };
+    { "lock"; { func = lock; help = "lock the telnet prompt" } };
     { "logout"; { func = logout; help = "exit (if in a socket connection)" } };
     { "quit"; { func = quit; help = "quit VLC (or logout if in a socket connection)" } };
     { "shutdown"; { func = shutdown; help = "shutdown VLC" } };
@@ -589,6 +612,21 @@ function split_input(input)
     end
 end
 
+function vlm_message_to_string(client,message,prefix)
+    local prefix = prefix or ""
+    if message.value then
+        client:append(prefix .. message.name .. " : " .. message.value)
+    else
+        client:append(prefix .. message.name)
+    end
+    if message.children then
+        for i,c in ipairs(message.children) do
+            vlm_message_to_string(client,c,prefix.."    ")
+        end
+    end
+end
+
+--[[ Command dispatch ]]
 function call_command(cmd,client,arg)
     if type(commands[cmd]) == type("") then
         cmd = commands[cmd]
@@ -605,6 +643,15 @@ function call_command(cmd,client,arg)
     end
 end
 
+function call_vlm_command(cmd,client,arg)
+    if arg ~= nil then
+        cmd = cmd.." "..arg
+    end
+    local message, vlc_err = vlm:execute_command( cmd )
+    vlm_message_to_string( client, message )
+    return vlc_err
+end
+
 function call_libvlc_command(cmd,client,arg)
     local ok, vlcerr = pcall( vlc.var.libvlc_command, cmd, arg )
     if not ok then
@@ -631,23 +678,98 @@ function call_object_command(cmd,client,arg)
     return vlcerr
 end
 
---[[ Setup host ]]
-require("host")
-h = host.host()
--- No auth
-h.status_callbacks[host.status.password] = function(client)
+function client_command( client )
+    local cmd,arg = split_input(client.buffer)
+    client.buffer = ""
+
+    if commands[cmd] then
+        call_command(cmd,client,arg)
+    elseif string.sub(cmd,0,1)=='@'
+    and call_object_command(string.sub(cmd,2,#cmd),client,arg) == 0 then
+        --
+    elseif call_vlm_command(cmd,client,arg) == 0 then
+        --
+    elseif client.type == host.client_type.stdio
+    and call_libvlc_command(cmd,client,arg) == 0 then
+        --
+    else
+        local choices = {}
+        if client.env.autocompletion ~= 0 then
+            for v,_ in common.pairs_sorted(commands) do
+                if string.sub(v,0,#cmd)==cmd then
+                    table.insert(choices, v)
+                end
+            end
+        end
+        if #choices == 1 and client.env.autoalias ~= 0 then
+            -- client:append("Aliasing to \""..choices[1].."\".")
+            cmd = choices[1]
+            call_command(cmd,client,arg)
+        else
+            client:append("Unknown command `"..cmd.."'. Type `help' for help.")
+            if #choices ~= 0 then
+                client:append("Possible choices are:")
+                local cols = math.floor(client.env.width/(client.env.colwidth+1))
+                local fmt = "%-"..client.env.colwidth.."s"
+                for i = 1, #choices do
+                    choices[i] = string.format(fmt,choices[i])
+                end
+                for i = 1, #choices, cols do
+                    local j = i + cols - 1
+                    if j > #choices then j = #choices end
+                    client:append("  "..table.concat(choices," ",i,j))
+                end
+            end
+        end
+    end
+end
+
+--[[ Some telnet command special characters ]]
+WILL = "\251" -- Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option.
+WONT = "\252" -- Indicates the refusal to perform, or continue performing, the indicated option.
+DO   = "\253" -- Indicates the request that the other party perform, or confirmation that you are expecting the other party to perform, the indicated option.
+DONT = "\254" -- Indicates the demand that the other party stop performing, or confirmation that you are no longer expecting the other party to perform, the indicated option.
+IAC  = "\255" -- Interpret as command
+
+ECHO = "\001"
+
+function telnet_commands( client )
+    -- remove telnet command replies from the client's data
+    client.buffer = string.gsub( client.buffer, IAC.."["..DO..DONT..WILL..WONT.."].", "" )
+end
+
+--[[ Client status change callbacks ]]
+function on_password( client )
     client.env = common.table_copy( env )
-    if client.env.welcome ~= "" then
-        client:send( client.env.welcome .. "\r\n")
+    if client.type == host.client_type.telnet then
+        client:send( "Password: " ..IAC..WILL..ECHO )
+    else
+        if client.env.welcome ~= "" then
+            client:send( client.env.welcome .. "\r\n")
+        end
+        client:switch_status( host.status.read )
     end
-    client:switch_status(host.status.read)
 end
 -- Print prompt when switching a client's status to `read'
-h.status_callbacks[host.status.read] = function(client)
+function on_read( client )
     client:send( client.env.prompt )
 end
+function on_write( client )
+end
+
+--[[ Setup host ]]
+require("host")
+h = host.host()
+
+h.status_callbacks[host.status.password] = on_password
+h.status_callbacks[host.status.read] = on_read
+h.status_callbacks[host.status.write] = on_write
 
 h:listen( config.hosts or config.host or "*console" )
+password = config.password or "admin"
+
+--[[ Launch vlm ]]
+vlm = vlc.vlm()
 
 --[[ The main loop ]]
 while not vlc.misc.should_die() do
@@ -661,60 +783,53 @@ while not vlc.misc.should_die() do
 
     for _, client in pairs(read) do
         local input = client:recv(1000)
-        local done = false
-        if string.match(input,"\n$") then
-            client.buffer = string.gsub(client.buffer..input,"\r?\n$","")
-            done = true
-        elseif input == ""
-           or  (client.type == host.client_type.net and input == "\004") then
+
+        if input == "" -- the telnet client program has left
+            or ((client.type == host.client_type.net
+                 or client.type == host.client_type.telnet)
+                and input == "\004") then
             -- Caught a ^D
-            client.buffer = "quit"
-            done = true
+            client.cmds = "quit\n"
         else
-            client.buffer = client.buffer .. input
+            client.cmds = client.cmds .. input
         end
-        if done then
-            local cmd,arg = split_input(client.buffer)
-            client.buffer = ""
-            client:switch_status(host.status.write)
-            if commands[cmd] then
-                call_command(cmd,client,arg)
-            elseif string.sub(cmd,0,1)=='@'
-            and call_object_command(string.sub(cmd,2,#cmd),client,arg) == 0 then
-                --
-            elseif client.type == host.client_type.stdio
-            and call_libvlc_command(cmd,client,arg) == 0 then
-                --
-            else
-                local choices = {}
-                if client.env.autocompletion ~= 0 then
-                    for v,_ in common.pairs_sorted(commands) do
-                        if string.sub(v,0,#cmd)==cmd then
-                            table.insert(choices, v)
-                        end
-                    end
-                end
-                if #choices == 1 and client.env.autoalias ~= 0 then
-                    -- client:append("Aliasing to \""..choices[1].."\".")
-                    cmd = choices[1]
-                    call_command(cmd,client,arg)
+
+        client.buffer = ""
+        -- split the command at the first '\n'
+        while string.find(client.cmds, "\n") do
+            -- save the buffer to send to the client
+            local saved_buffer = client.buffer
+
+            -- get the next command
+            local index = string.find(client.cmds, "\n")
+            client.buffer = string.gsub(string.sub(client.cmds, 0, index - 1), "^%s*(.-)%s*$", "%1")
+            client.cmds = string.sub(client.cmds, index + 1)
+
+            -- Remove telnet commands from the command line
+            if client.type == host.client_type.telnet then
+                telnet_commands( client )
+            end
+
+            -- Run the command
+            if client.status == host.status.password then
+                if client.buffer == password then
+                    client:send( IAC..WONT..ECHO.."\r\nWelcome, Master\r\n" )
+                    client.buffer = ""
+                    client:switch_status( host.status.write )
+                elseif client.buffer == "quit" then
+                    client_command( client )
                 else
-                    client:append("Unknown command `"..cmd.."'. Type `help' for help.")
-                    if #choices ~= 0 then
-                        client:append("Possible choices are:")
-                        local cols = math.floor(client.env.width/(client.env.colwidth+1))
-                        local fmt = "%-"..client.env.colwidth.."s"
-                        for i = 1, #choices do
-                            choices[i] = string.format(fmt,choices[i])
-                        end
-                        for i = 1, #choices, cols do
-                            local j = i + cols - 1
-                            if j > #choices then j = #choices end
-                            client:append("  "..table.concat(choices," ",i,j))
-                        end
-                    end
+                    client:send( "\r\nWrong password\r\nPassword: " )
+                    client.buffer = ""
                 end
+            else
+                client:switch_status( host.status.write )
+                client_command( client )
             end
+            client.buffer = saved_buffer .. client.buffer
         end
     end
 end
+
+--[[ Clean up ]]
+vlm = nil


Regards,

-- 
Pierre Ynard
"Une âme dans un corps, c'est comme un dessin sur une feuille de papier."



More information about the vlc-devel mailing list