1 --[==========================================================================[
2 host.lua: VLC Lua interface command line host module
3 --[==========================================================================[
4 Copyright (C) 2007-2012 the VideoLAN team
7 Authors: Antoine Cellerier <dionoea at videolan dot org>
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 --]==========================================================================]
24 --[==========================================================================[
30 -- Bypass any authentication
31 function on_password( client )
32 client:switch_status( host.status.read )
34 h.status_callbacks[host.status.password] = on_password
36 h:listen( "localhost:4212" )
37 h:listen( "*console" )
38 --or h:listen( { "localhost:4212", "*console" } )
41 while not vlc.misc.should_die() do
42 -- accept new connections and select active clients
43 local write, read = h:accept_and_select()
45 -- handle clients in write mode
46 for _, client in pairs(write) do
49 client:switch_status( host.status.read )
52 -- handle clients in read mode
53 for _, client in pairs(read) do
54 local str = client:recv(1000)
55 if not str then break end
56 str = string.gsub(str,"\r?\n$","")
57 client.buffer = "Got `"..str.."'.\r\n"
58 client:switch_status( host.status.write )
62 For complete examples see existing VLC Lua interface modules (ie cli.lua)
63 --]==========================================================================]
65 module("host",package.seeall)
67 status = { init = 0, read = 1, write = 2, password = 3 }
68 client_type = { net = 1, stdio = 2, fifo = 3, telnet = 4 }
70 function is_flag_set(val, flag)
71 return (((val - (val % flag)) / flag) % 2 ~= 0)
78 local status_callbacks = {}
81 local function fd_client( client )
82 if client.status == status.read then
89 local function send( client, data, len )
91 return vlc.net.send( client.wfd, data, len )
93 return vlc.net.send( client.wfd, data or client.buffer )
97 local function recv( client, len )
99 return vlc.net.recv( client.rfd, len )
101 return vlc.net.recv( client.rfd )
105 local function write( client, data )
106 return vlc.net.write( client.wfd, data or client.buffer )
109 local function read( client, len )
111 return vlc.net.read( client.rfd, len )
113 return vlc.net.read( client.rfd )
117 local function write_console( client, data )
118 -- FIXME: this method shouldn't be needed. vlc.net.write should just work
119 io.write(data or client.buffer)
120 return string.len(data or client.buffer)
123 local function read_console( client, len )
124 -- Read stdin from a windows console (beware: select/poll doesn't work!)
125 return vlc.win.console_read()
128 local function del_client( client, nostdioerror )
129 --client:send("Cleaning up.\r\n")
130 if client.type == client_type.stdio then
131 if not nostdioerror then
132 client:send( "Cannot delete stdin/stdout client.\n" )
134 elseif clients[client] then
135 if client.type == client_type.net
136 or client.type == client_type.telnet then
137 if client.wfd ~= client.rfd then
138 vlc.net.close( client.rfd )
140 vlc.net.close( client.wfd )
142 clients[client] = nil
144 vlc.msg.err("couldn't find client to remove.")
148 local function switch_status( client, s )
149 if client.status == s then return end
151 if status_callbacks[s] then
152 status_callbacks[s]( client )
156 -- append a line to a client's (output) buffer
157 local function append( client, string )
158 client.buffer = client.buffer .. string .. "\r\n"
161 local function new_client( h, fd, wfd, t )
162 if fd < 0 then return end
164 if t == client_type.net or t == client_type.telnet then
167 else if t == client_type.stdio or t == client_type.fifo then
168 if vlc.win and t == client_type.stdio then
169 vlc.win.console_init()
177 error("Unknown client type", t )
180 local client = { -- data
183 status = status.init,
192 switch_status = switch_status,
195 client:send( "VLC media player "..vlc.misc.version().."\n" )
196 clients[client] = client
197 client:switch_status(status.password)
201 local function _listen_tcp( h, host, port, telnet )
202 if listeners.tcp and listeners.tcp[host]
203 and listeners.tcp[host][port] then
204 error("Already listening on tcp host `"..host..":"..tostring(port).."'")
206 if listeners.stdio and vlc.win then
207 error("Cannot listen on console and sockets concurrently on Windows")
209 if not listeners.tcp then
212 if not listeners.tcp[host] then
213 listeners.tcp[host] = {}
215 listeners.tcp[host][port] = true
216 if not listeners.tcp.list then
217 -- FIXME: if host == "list" we'll have a problem
218 listeners.tcp.list = {}
220 local listener = vlc.net.listen_tcp( host, port )
221 local type = telnet and client_type.telnet or client_type.net;
222 table.insert( listeners.tcp.list, { data = listener,
227 local function _listen_stdio( h )
228 if listeners.stdio then
229 error("Already listening on stdio")
231 if listeners.tcp and vlc.win then
232 error("Cannot listen on console and sockets concurrently on Windows")
234 new_client( h, 0, 1, client_type.stdio )
235 listeners.stdio = true
238 local function _listen( h, url )
239 if type(url)==type({}) then
240 for _,u in pairs(url) do
244 vlc.msg.info( "Listening on host \""..url.."\"." )
245 if url == "*console" then
248 u = vlc.net.url_parse( url )
249 h:listen_tcp( u.host, u.port, (u.protocol == "telnet") )
254 local function _accept_and_select( h )
257 if not (vlc.win and listeners.stdio) then
258 local function filter_client( fds, status, event )
259 for _, client in pairs(clients) do
260 if client.status == status then
261 fds[client:fd()] = event
267 filter_client( pollfds, status.read, vlc.net.POLLIN )
268 filter_client( pollfds, status.password, vlc.net.POLLIN )
269 filter_client( pollfds, status.write, vlc.net.POLLOUT )
270 if listeners.tcp then
271 for _, listener in pairs(listeners.tcp.list) do
272 for _, fd in pairs({listener.data:fds()}) do
273 pollfds[fd] = vlc.net.POLLIN
278 local ret = vlc.net.poll( pollfds )
280 for _, client in pairs(clients) do
281 if is_flag_set(pollfds[client:fd()], vlc.net.POLLIN) then
282 table.insert(rclients, client)
283 elseif is_flag_set(pollfds[client:fd()], vlc.net.POLLERR)
284 or is_flag_set(pollfds[client:fd()], vlc.net.POLLHUP)
285 or is_flag_set(pollfds[client:fd()], vlc.net.POLLNVAL) then
287 elseif is_flag_set(pollfds[client:fd()], vlc.net.POLLOUT) then
288 table.insert(wclients, client)
291 if listeners.tcp then
292 for _, listener in pairs(listeners.tcp.list) do
293 for _, fd in pairs({listener.data:fds()}) do
294 if is_flag_set(pollfds[fd], vlc.net.POLLIN) then
295 local afd = listener.data:accept()
296 new_client( h, afd, afd, listener.type )
304 for _, client in pairs(clients) do
305 if client.type == client_type.stdio then
306 if client.status == status.read or client.status == status.password then
307 if vlc.win.console_wait(50) then
308 table.insert(rclients, client)
311 table.insert(wclients, client)
316 return wclients, rclients
319 local function destructor( h )
320 for _,client in pairs(clients) do
325 local function _broadcast( h, msg )
326 for _,client in pairs(clients) do
332 -- We're running Lua 5.1
333 -- See http://lua-users.org/wiki/HiddenFeatures for more info.
334 local proxy = newproxy(true)
335 getmetatable(proxy).__gc = destructor
340 local h = setmetatable(
342 status_callbacks = status_callbacks,
345 listen_tcp = _listen_tcp,
346 listen_stdio = _listen_stdio,
347 accept_and_select = _accept_and_select,
348 broadcast = _broadcast,
351 __gc = destructor, -- Should work in Lua 5.2 without the new proxytrick as __gc is also called on tables (needs to be tested)