[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