lua: console: Fix output of non ascii characters on win32
[vlc.git] / share / lua / intf / modules / host.lua
blob7c99778319e1e06f555059a9dc73d49cfc6b9287
1 --[==========================================================================[
2 host.lua: VLC Lua interface command line host module
3 --[==========================================================================[
4 Copyright (C) 2007-2012 the VideoLAN team
5 $Id$
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 --[==========================================================================[
25 Example use:
27 require "host"
28 h = host.host()
30 -- Bypass any authentication
31 function on_password( client )
32 client:switch_status( host.status.read )
33 end
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" } )
40 -- The main loop
41 while true 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
47 client:send()
48 client.buffer = ""
49 client:switch_status( host.status.read )
50 end
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 )
59 end
60 end
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 local bit = 65536
72 while bit > 1 do
73 val = val % bit
74 flag = flag % bit
75 bit = bit / 2
76 if val >= bit and flag >= bit then return true end
77 end
78 return false
79 end
81 function host()
82 -- private data
83 local clients = {}
84 local listeners = {}
85 local status_callbacks = {}
87 -- private methods
88 local function fd_client( client )
89 if client.status == status.read then
90 return client.rfd
91 else -- status.write
92 return client.wfd
93 end
94 end
96 local function send( client, data, len )
97 if len then
98 return vlc.net.send( client.wfd, data, len )
99 else
100 return vlc.net.send( client.wfd, data or client.buffer )
104 local function recv( client, len )
105 if len then
106 return vlc.net.recv( client.rfd, len )
107 else
108 return vlc.net.recv( client.rfd )
112 local function write( client, data )
113 return vlc.net.write( client.wfd, data or client.buffer )
116 local function read( client, len )
117 if len then
118 return vlc.net.read( client.rfd, len )
119 else
120 return vlc.net.read( client.rfd )
124 local function write_console( client, data )
125 -- FIXME: this method shouldn't be needed. vlc.net.write should
126 -- just work
127 vlc.win.console_write(data or client.buffer)
128 return string.len(data or client.buffer)
131 local function read_console( client, len )
132 -- Read stdin from a windows console (beware: select/poll doesn't work!)
133 return vlc.win.console_read()
136 local function del_client( client )
137 if not clients[client] then
138 vlc.msg.err("couldn't find client to remove.")
139 return
142 if client.type == client_type.stdio then
143 h:broadcast("Shutting down.\r\n")
144 vlc.msg.info("Requested shutdown.")
145 vlc.misc.quit()
146 elseif client.type == client_type.net
147 or client.type == client_type.telnet then
148 if client.wfd ~= client.rfd then
149 vlc.net.close( client.rfd )
151 vlc.net.close( client.wfd )
153 clients[client] = nil
156 local function switch_status( client, s )
157 if client.status == s then return end
158 client.status = s
159 if status_callbacks[s] then
160 status_callbacks[s]( client )
164 -- append a line to a client's (output) buffer
165 local function append( client, string )
166 client.buffer = client.buffer .. string .. "\r\n"
169 local function new_client( h, fd, wfd, t )
170 if fd < 0 then return end
171 local w, r
172 if t == client_type.net or t == client_type.telnet then
173 w = send
174 r = recv
175 elseif t == client_type.stdio or t == client_type.fifo then
176 if vlc.win and t == client_type.stdio then
177 vlc.win.console_init()
178 w = write_console
179 r = read_console
180 else
181 w = write
182 r = read
184 else
185 error("Unknown client type", t )
188 local client = { -- data
189 rfd = fd,
190 wfd = wfd or fd,
191 status = status.init,
192 buffer = "",
193 cmds = "",
194 type = t,
195 -- methods
196 fd = fd_client,
197 send = w,
198 recv = r,
199 del = del_client,
200 switch_status = switch_status,
201 append = append,
203 client:send( "VLC media player "..vlc.misc.version().."\n" )
204 clients[client] = client
205 client:switch_status(status.password)
208 -- public methods
209 local function _listen_tcp( h, host, port, telnet )
210 if listeners.tcp and listeners.tcp[host]
211 and listeners.tcp[host][port] then
212 error("Already listening on tcp host `"..host..":"..tostring(port).."'")
214 if listeners.stdio and vlc.win then
215 error("Cannot listen on console and sockets concurrently on Windows")
217 if not listeners.tcp then
218 listeners.tcp = {}
220 if not listeners.tcp[host] then
221 listeners.tcp[host] = {}
223 listeners.tcp[host][port] = true
224 if not listeners.tcp.list then
225 -- FIXME: if host == "list" we'll have a problem
226 listeners.tcp.list = {}
228 local listener = vlc.net.listen_tcp( host, port )
229 local type = telnet and client_type.telnet or client_type.net;
230 table.insert( listeners.tcp.list, { data = listener,
231 type = type,
235 local function _listen_stdio( h )
236 if listeners.stdio then
237 error("Already listening on stdio")
239 if listeners.tcp and vlc.win then
240 error("Cannot listen on console and sockets concurrently on Windows")
242 new_client( h, 0, 1, client_type.stdio )
243 listeners.stdio = true
246 local function _listen( h, url )
247 if type(url)==type({}) then
248 for _,u in pairs(url) do
249 h:listen( u )
251 else
252 vlc.msg.info( "Listening on host \""..url.."\"." )
253 if url == "*console" then
254 h:listen_stdio()
255 else
256 u = vlc.strings.url_parse( url )
257 if url.host == nil then
258 u = vlc.strings.url_parse( "//" .. url )
260 h:listen_tcp( u.host, u.port, (u.protocol == "telnet") )
265 local function _accept_and_select( h )
266 local wclients = {}
267 local rclients = {}
268 if not (vlc.win and listeners.stdio) then
269 local function filter_client( fds, status, event )
270 for _, client in pairs(clients) do
271 if client.status == status then
272 fds[client:fd()] = event
277 local pollfds = {}
278 filter_client( pollfds, status.read, vlc.net.POLLIN )
279 filter_client( pollfds, status.password, vlc.net.POLLIN )
280 filter_client( pollfds, status.write, vlc.net.POLLOUT )
281 if listeners.tcp then
282 for _, listener in pairs(listeners.tcp.list) do
283 for _, fd in pairs({listener.data:fds()}) do
284 pollfds[fd] = vlc.net.POLLIN
289 local ret = vlc.net.poll( pollfds )
290 if ret > 0 then
291 for _, client in pairs(clients) do
292 if is_flag_set(pollfds[client:fd()], vlc.net.POLLIN) then
293 table.insert(rclients, client)
294 elseif is_flag_set(pollfds[client:fd()], vlc.net.POLLERR)
295 or is_flag_set(pollfds[client:fd()], vlc.net.POLLHUP)
296 or is_flag_set(pollfds[client:fd()], vlc.net.POLLNVAL) then
297 client:del()
298 elseif is_flag_set(pollfds[client:fd()], vlc.net.POLLOUT) then
299 table.insert(wclients, client)
302 if listeners.tcp then
303 for _, listener in pairs(listeners.tcp.list) do
304 for _, fd in pairs({listener.data:fds()}) do
305 if is_flag_set(pollfds[fd], vlc.net.POLLIN) then
306 local afd = listener.data:accept()
307 new_client( h, afd, afd, listener.type )
308 break
314 else
315 for _, client in pairs(clients) do
316 if client.type == client_type.stdio then
317 if client.status == status.read or client.status == status.password then
318 if vlc.win.console_wait(50) then
319 table.insert(rclients, client)
321 else
322 table.insert(wclients, client)
327 return wclients, rclients
330 local function destructor( h )
331 for _,client in pairs(clients) do
332 if client.type ~= client_type.stdio then
333 client:del()
338 local function _broadcast( h, msg )
339 for _,client in pairs(clients) do
340 client:send( msg )
344 if setfenv then
345 -- We're running Lua 5.1
346 -- See http://lua-users.org/wiki/HiddenFeatures for more info.
347 local proxy = newproxy(true)
348 getmetatable(proxy).__gc = destructor
349 destructor = proxy
352 -- the instance
353 local h = setmetatable(
354 { -- data
355 status_callbacks = status_callbacks,
356 -- methods
357 listen = _listen,
358 listen_tcp = _listen_tcp,
359 listen_stdio = _listen_stdio,
360 accept_and_select = _accept_and_select,
361 broadcast = _broadcast,
363 { -- metatable
364 __gc = destructor, -- Should work in Lua 5.2 without the new proxytrick as __gc is also called on tables (needs to be tested)
365 __metatable = "",
367 return h