1 --[==========================================================================[
2 cli.lua: CLI module for VLC
3 --[==========================================================================[
4 Copyright (C) 2007-2011 the VideoLAN team
7 Authors: Antoine Cellerier <dionoea at videolan dot org>
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 --]==========================================================================]
26 [============================================================================[
27 Command Line Interface for VLC
29 This is a modules/control/rc.c look alike (with a bunch of new features).
30 It also provides a VLM interface copied from the telnet interface.
34 Use on tcp connection:
35 vlc -I cli --lua-config "cli={host='localhost:4212'}"
36 Use on telnet connection:
37 vlc -I cli --lua-config "cli={host='telnet://localhost:4212'}"
38 Use on multiple hosts (term + plain tcp port + telnet):
39 vlc -I cli --lua-config "cli={hosts={'*console','localhost:4212','telnet://localhost:5678'}}"
42 -I cli and -I luacli are aliases for -I lua --lua-intf cli
44 Configuration options setable throught the --lua-config option are:
45 * hosts: A list of hosts to listen on.
46 * host: A host to listen on. (won't be used if `hosts' is set)
47 * password: The password used for telnet clients.
48 The following can be set using the --lua-config option or in the interface
49 itself using the `set' command:
51 * welcome: The welcome message.
52 * width: The default terminal width (used to format text).
53 * autocompletion: When issuing an unknown command, print a list of
54 possible commands to autocomplete with. (0 to disable,
56 * autoalias: If autocompletion returns only one possibility, use it
57 (0 to disable, 1 to enable).
58 * flatplaylist: 0 to disable, 1 to enable.
59 ]============================================================================]
63 skip2 = function(foo) return skip(skip(foo)) end
64 setarg = common.setarg
70 --[[ Setup default environement ]]
71 env = { prompt = "> ";
75 welcome = _("Command Line Interface initialized. Type `help' for help.");
79 --[[ Import custom environement variables from the command line config (if possible) ]]
80 for k,v in pairs(env) do
82 if type(env[k]) == type(config[k]) then
84 vlc.msg.dbg("set environement variable `"..k.."' to "..tostring(env[k]))
86 vlc.msg.err("environement variable `"..k.."' should be of type "..type(env[k])..". config value will be discarded.")
91 --[[ Command functions ]]
92 function set_env(name,client,value)
94 local var,val = split_input(value)
96 local s = string.gsub(val,"\"(.*)\"","%1")
97 if type(client.env[var])==type(1) then
98 client.env[var] = tonumber(s)
103 client:append( tostring(client.env[var]) )
106 for e,v in common.pairs_sorted(client.env) do
107 client:append(e.."="..v)
112 function save_env(name,client,value)
113 env = common.table_copy(client.env)
116 function alias(client,value)
118 local var,val = split_input(value)
119 if commands[var] and type(commands[var]) ~= type("") then
120 client:append("Error: cannot use a primary command as an alias name")
122 if commands[val] then
125 client:append("Error: unknown primary command `"..val.."'.")
129 for c,v in common.pairs_sorted(commands) do
130 if type(v)==type("") then
131 client:append(c.."="..v)
137 function lock(name,client)
138 if client.type == host.client_type.telnet then
139 client:switch_status( host.status.password )
142 client:append("Error: the prompt can only be locked when logged in through telnet")
146 function logout(name,client)
147 if client.type == host.client_type.net
148 or client.type == host.client_type.telnet then
149 client:send("Bye-bye!\r\n")
152 client:append("Error: Can't logout of stdin/stdout. Use quit or shutdown to close VLC.")
156 function shutdown(name,client)
157 client:append("Bye-bye!")
158 h:broadcast("Shutting down.\r\n")
159 vlc.msg.info("Requested shutdown.")
163 function quit(name,client)
164 if client.type == host.client_type.net
165 or client.type == host.client_type.telnet then
168 shutdown(name,client)
172 function add(name,client,arg)
173 -- TODO: parse single and double quotes properly
175 if name == "enqueue" then
176 f = vlc.playlist.enqueue
181 for o in string.gmatch(arg," +:([^ ]*)") do
182 table.insert(options,o)
184 arg = string.gsub(arg," +:.*$","")
185 local uri = vlc.strings.make_uri(arg)
186 f({{path=uri,options=options}})
189 function playlist_is_tree( client )
190 if client.env.flatplaylist == 0 then
197 function playlist(name,client,arg)
198 function playlist0(item,prefix)
199 local prefix = prefix or ""
200 if not item.flags.disabled then
201 local str = "| "..prefix..tostring(item.id).." - "..item.name
202 if item.duration > 0 then
203 str = str.." ("..common.durationtostring(item.duration)..")"
205 if item.nb_played > 0 then
206 str = str.." [played "..tostring(item.nb_played).." time"
207 if item.nb_played > 1 then
214 if item.children then
215 for _, c in ipairs(item.children) do
216 playlist0(c,prefix.." ")
221 local tree = playlist_is_tree(client)
222 if name == "search" then
223 playlist = vlc.playlist.search(arg or "", tree)
225 if tonumber(arg) then
226 playlist = vlc.playlist.get(tonumber(arg), tree)
228 playlist = vlc.playlist.get(arg, tree)
230 playlist = vlc.playlist.get(nil, tree)
233 if name == "search" then
234 client:append("+----[ Search - "..(arg or "`reset'").." ]")
236 client:append("+----[ Playlist - "..playlist.name.." ]")
238 if playlist.children then
239 for _, item in ipairs(playlist.children) do
245 if name == "search" then
246 client:append("+----[ End of search - Use `search' to reset ]")
248 client:append("+----[ End of playlist ]")
252 function playlist_sort(name,client,arg)
254 client:append("Valid sort keys are: id, title, artist, genre, random, duration, album.")
256 local tree = playlist_is_tree(client)
257 vlc.playlist.sort(arg,false,tree)
261 function services_discovery(name,client,arg)
263 if vlc.sd.is_loaded(arg) then
265 client:append(arg.." disabled.")
268 client:append(arg.." enabled.")
271 local sd = vlc.sd.get_services_names()
272 client:append("+----[ Services discovery ]")
273 for n,ln in pairs(sd) do
275 if vlc.sd.is_loaded(n) then
280 client:append("| "..n..": " .. ln .. " (" .. status .. ")")
282 client:append("+----[ End of services discovery ]")
286 function print_text(label,text)
287 return function(name,client)
288 client:append("+----[ "..label.." ]")
290 for line in string.gmatch(text,".-\r?\n") do
291 client:append("| "..string.gsub(line,"\r?\n",""))
294 client:append("+----[ End of "..string.lower(label).." ]")
298 function help(name,client,arg)
300 client:append("+----[ VLM commands ]")
301 local message, vlc_err = vlm:execute_command("help")
302 vlm_message_to_string( client, message, "|" )
305 local width = client.env.width
306 local long = (name == "longhelp")
308 if arg then extra = "matching `" .. arg .. "' " end
309 client:append("+----[ CLI commands "..extra.."]")
310 for i, cmd in ipairs(commands_ordered) do
311 if (cmd == "" or not commands[cmd].adv or long)
312 and (not arg or string.match(cmd,arg)) then
313 local str = "| " .. cmd
315 local val = commands[cmd]
317 for _,a in ipairs(val.aliases) do
318 str = str .. ", " .. a
321 if val.args then str = str .. " " .. val.args end
322 if #str%2 == 1 then str = str .. " " end
323 str = str .. string.rep(" .",(width-(#str+#val.help)-1)/2)
324 str = str .. string.rep(" ",width-#str-#val.help) .. val.help
329 client:append("+----[ end of help ]")
332 function input_info(name,client)
333 local item = vlc.input.item()
334 if(item == nil) then return end
335 local categories = item:info()
336 for cat, infos in pairs(categories) do
337 client:append("+----[ "..cat.." ]")
339 for name, value in pairs(infos) do
340 client:append("| "..name..": "..value)
344 client:append("+----[ end of stream info ]")
347 function stats(name,client)
348 local item = vlc.input.item()
349 if(item == nil) then return end
350 local stats_tab = item:stats()
352 client:append("+----[ begin of statistical info")
353 client:append("+-[Incoming]")
354 client:append("| input bytes read : "..string.format("%8.0f KiB",stats_tab["read_bytes"]/1024))
355 client:append("| input bitrate : "..string.format("%6.0f kb/s",stats_tab["input_bitrate"]*8000))
356 client:append("| demux bytes read : "..string.format("%8.0f KiB",stats_tab["demux_read_bytes"]/1024))
357 client:append("| demux bitrate : "..string.format("%6.0f kb/s",stats_tab["demux_bitrate"]*8000))
358 client:append("| demux corrupted : "..string.format("%5i",stats_tab["demux_corrupted"]))
359 client:append("| discontinuities : "..string.format("%5i",stats_tab["demux_discontinuity"]))
361 client:append("+-[Video Decoding]")
362 client:append("| video decoded : "..string.format("%5i",stats_tab["decoded_video"]))
363 client:append("| frames displayed : "..string.format("%5i",stats_tab["displayed_pictures"]))
364 client:append("| frames lost : "..string.format("%5i",stats_tab["lost_pictures"]))
366 client:append("+-[Audio Decoding]")
367 client:append("| audio decoded : "..string.format("%5i",stats_tab["decoded_audio"]))
368 client:append("| buffers played : "..string.format("%5i",stats_tab["played_abuffers"]))
369 client:append("| buffers lost : "..string.format("%5i",stats_tab["lost_abuffers"]))
371 client:append("+-[Streaming]")
372 client:append("| packets sent : "..string.format("%5i",stats_tab["sent_packets"]))
373 client:append("| bytes sent : "..string.format("%8.0f KiB",stats_tab["sent_bytes"]/1024))
374 client:append("| sending bitrate : "..string.format("%6.0f kb/s",stats_tab["send_bitrate"]*8000))
375 client:append("+----[ end of statistical info ]")
378 function playlist_status(name,client)
379 local item = vlc.input.item()
381 client:append( "( new input: " .. vlc.strings.decode_uri(item:uri()) .. " )" )
383 client:append( "( audio volume: " .. tostring(vlc.volume.get()) .. " )")
384 client:append( "( state " .. vlc.playlist.status() .. " )")
387 function is_playing(name,client)
388 if vlc.input.is_playing() then client:append "1" else client:append "0" end
391 function get_title(name,client)
392 local item = vlc.input.item()
394 client:append(item:name())
400 function ret_print(foo,start,stop)
401 local start = start or ""
402 local stop = stop or ""
403 return function(discard,client,...) client:append(start..tostring(foo(...))..stop) end
406 function get_time(var)
407 return function(name,client)
408 local input = vlc.object.input()
409 client:append(math.floor(vlc.var.get( input, var )))
413 function titlechap(name,client,value)
414 local input = vlc.object.input()
415 local var = string.gsub( name, "_.*$", "" )
417 vlc.var.set( input, var, value )
419 local item = vlc.var.get( input, var )
420 -- Todo: add item name conversion
425 function titlechap_offset(var,offset)
426 local input = vlc.object.input()
427 vlc.var.set( input, var, vlc.var.get( input, var ) + offset )
430 function title_next(name,client,value)
431 titlechap_offset('title', 1)
434 function title_previous(name,client,value)
435 titlechap_offset('title', -1)
438 function chapter_next(name,client,value)
439 titlechap_offset('chapter', 1)
442 function chapter_previous(name,client,value)
443 titlechap_offset('chapter', -1)
446 function seek(name,client,value)
450 function volume(name,client,value)
454 client:append(tostring(vlc.volume.get()))
458 function rate(name,client,value)
459 local input = vlc.object.input()
460 if name == "rate" then
461 vlc.var.set(input, "rate", tonumber(value))
462 elseif name == "normal" then
463 vlc.var.set(input,"rate",1)
465 vlc.var.set(input,"rate-"..name,nil)
469 function frame(name,client)
470 vlc.var.trigger_callback(vlc.object.input(),"frame-next");
473 function listvalue(obj,var)
474 return function(client,value)
476 if obj == "input" then
477 o = vlc.object.input()
478 elseif obj == "aout" then
479 o = vlc.object.aout()
480 elseif obj == "vout" then
481 o = vlc.object.vout()
483 if not o then return end
485 vlc.var.set( o, var, value )
487 local c = vlc.var.get( o, var )
488 local v, l = vlc.var.get_list( o, var )
489 client:append("+----[ "..var.." ]")
490 for i,val in ipairs(v) do
491 local mark = (val==c)and " *" or ""
492 client:append("| "..tostring(val).." - "..tostring(l[i])..mark)
494 client:append("+----[ end of "..var.." ]")
499 function menu(name,client,value)
500 local map = { on='show', off='hide', up='up', down='down', left='prev', right='next', ['select']='activate' }
501 if map[value] and vlc.osd.menu[map[value]] then
502 vlc.osd.menu[map[value]]()
504 client:append("Unknown menu command '"..tostring(value).."'")
508 function hotkey(name, client, value)
510 client:append("Please specify a hotkey (ie key-quit or quit)")
511 elseif not common.hotkey(value) and not common.hotkey("key-"..value) then
512 client:append("Unknown hotkey '"..value.."'")
516 --[[ Declare commands, register their callback functions and provide
519 "<command name>"; { func = <function>; [ args = "<str>"; ] help = "<str>"; [ adv = <bool>; ] [ aliases = { ["<str>";]* }; ] }
522 { "add"; { func = add; args = "XYZ"; help = "add XYZ to playlist" } };
523 { "enqueue"; { func = add; args = "XYZ"; help = "queue XYZ to playlist" } };
524 { "playlist"; { func = playlist; help = "show items currently in playlist" } };
525 { "search"; { func = playlist; args = "[string]"; help = "search for items in playlist (or reset search)" } };
526 { "sort"; { func = playlist_sort; args = "key"; help = "sort the playlist" } };
527 { "sd"; { func = services_discovery; args = "[sd]"; help = "show services discovery or toggle" } };
528 { "play"; { func = skip2(vlc.playlist.play); help = "play stream" } };
529 { "stop"; { func = skip2(vlc.playlist.stop); help = "stop stream" } };
530 { "next"; { func = skip2(vlc.playlist.next); help = "next playlist item" } };
531 { "prev"; { func = skip2(vlc.playlist.prev); help = "previous playlist item" } };
532 { "goto"; { func = skip2(vlc.playlist.goto); help = "goto item at index" } };
533 { "repeat"; { func = skip2(vlc.playlist.repeat_); args = "[on|off]"; help = "toggle playlist repeat" } };
534 { "loop"; { func = skip2(vlc.playlist.loop); args = "[on|off]"; help = "toggle playlist loop" } };
535 { "random"; { func = skip2(vlc.playlist.random); args = "[on|off]"; help = "toggle playlist random" } };
536 { "clear"; { func = skip2(vlc.playlist.clear); help = "clear the playlist" } };
537 { "status"; { func = playlist_status; help = "current playlist status" } };
538 { "title"; { func = titlechap; args = "[X]"; help = "set/get title in current item" } };
539 { "title_n"; { func = title_next; help = "next title in current item" } };
540 { "title_p"; { func = title_previous; help = "previous title in current item" } };
541 { "chapter"; { func = titlechap; args = "[X]"; help = "set/get chapter in current item" } };
542 { "chapter_n"; { func = chapter_next; help = "next chapter in current item" } };
543 { "chapter_p"; { func = chapter_previous; help = "previous chapter in current item" } };
545 { "seek"; { func = seek; args = "X"; help = "seek in seconds, for instance `seek 12'" } };
546 { "pause"; { func = skip2(vlc.playlist.pause); help = "toggle pause" } };
547 { "fastforward"; { func = setarg(common.hotkey,"key-jump+extrashort"); help = "set to maximum rate" } };
548 { "rewind"; { func = setarg(common.hotkey,"key-jump-extrashort"); help = "set to minimum rate" } };
549 { "faster"; { func = rate; help = "faster playing of stream" } };
550 { "slower"; { func = rate; help = "slower playing of stream" } };
551 { "normal"; { func = rate; help = "normal playing of stream" } };
552 { "rate"; { func = rate; args = "[playback rate]"; help = "set playback rate to value" } };
553 { "frame"; { func = frame; help = "play frame by frame" } };
554 { "fullscreen"; { func = skip2(vlc.video.fullscreen); args = "[on|off]"; help = "toggle fullscreen"; aliases = { "f", "F" } } };
555 { "info"; { func = input_info; help = "information about the current stream" } };
556 { "stats"; { func = stats; help = "show statistical information" } };
557 { "get_time"; { func = get_time("time"); help = "seconds elapsed since stream's beginning" } };
558 { "is_playing"; { func = is_playing; help = "1 if a stream plays, 0 otherwise" } };
559 { "get_title"; { func = get_title; help = "the title of the current stream" } };
560 { "get_length"; { func = get_time("length"); help = "the length of the current stream" } };
562 { "volume"; { func = volume; args = "[X]"; help = "set/get audio volume" } };
563 { "volup"; { func = ret_print(vlc.volume.up,"( audio volume: "," )"); args = "[X]"; help = "raise audio volume X steps" } };
564 { "voldown"; { func = ret_print(vlc.volume.down,"( audio volume: "," )"); args = "[X]"; help = "lower audio volume X steps" } };
565 { "adev"; { func = skip(listvalue("aout","audio-device")); args = "[X]"; help = "set/get audio device" } };
566 { "achan"; { func = skip(listvalue("aout","audio-channels")); args = "[X]"; help = "set/get audio channels" } };
567 { "atrack"; { func = skip(listvalue("input","audio-es")); args = "[X]"; help = "set/get audio track" } };
568 { "vtrack"; { func = skip(listvalue("input","video-es")); args = "[X]"; help = "set/get video track" } };
569 { "vratio"; { func = skip(listvalue("vout","aspect-ratio")); args = "[X]"; help = "set/get video aspect ratio" } };
570 { "vcrop"; { func = skip(listvalue("vout","crop")); args = "[X]"; help = "set/get video crop"; aliases = { "crop" } } };
571 { "vzoom"; { func = skip(listvalue("vout","zoom")); args = "[X]"; help = "set/get video zoom"; aliases = { "zoom" } } };
572 { "vdeinterlace"; { func = skip(listvalue("vout","deinterlace")); args = "[X]"; help = "set/get video deintelace" } };
573 { "vdeinterlace_mode"; { func = skip(listvalue("vout","deinterlace-mode")); args = "[X]"; help = "set/get video deintelace mode" } };
574 { "snapshot"; { func = common.snapshot; help = "take video snapshot" } };
575 { "strack"; { func = skip(listvalue("input","spu-es")); args = "[X]"; help = "set/get subtitles track" } };
576 { "hotkey"; { func = hotkey; args = "[hotkey name]"; help = "simulate hotkey press"; adv = true; aliases = { "key" } } };
577 { "menu"; { func = menu; args = "[on|off|up|down|left|right|select]"; help = "use menu"; adv = true } };
579 { "set"; { func = set_env; args = "[var [value]]"; help = "set/get env var"; adv = true } };
580 { "save_env"; { func = save_env; help = "save env vars (for future clients)"; adv = true } };
581 { "alias"; { func = skip(alias); args = "[cmd]"; help = "set/get command aliases"; adv = true } };
582 { "description"; { func = print_text("Description",description); help = "describe this module" } };
583 { "license"; { func = print_text("License message",vlc.misc.license()); help = "print VLC's license message"; adv = true } };
584 { "help"; { func = help; args = "[pattern]"; help = "a help message"; aliases = { "?" } } };
585 { "longhelp"; { func = help; args = "[pattern]"; help = "a longer help message" } };
586 { "lock"; { func = lock; help = "lock the telnet prompt" } };
587 { "logout"; { func = logout; help = "exit (if in a socket connection)" } };
588 { "quit"; { func = quit; help = "quit VLC (or logout if in a socket connection)" } };
589 { "shutdown"; { func = shutdown; help = "shutdown VLC" } };
593 for i, cmd in ipairs( commands_ordered ) do
595 commands[cmd[1]]=cmd[2]
596 if cmd[2].aliases then
597 for _,a in ipairs(cmd[2].aliases) do
602 commands_ordered[i]=cmd[1]
604 --[[ From now on commands_ordered is a list of the different command names
605 and commands is a associative array indexed by the command name. ]]
607 -- Compute the column width used when printing a the autocompletion list
609 for c,_ in pairs(commands) do
610 if #c > env.colwidth then env.colwidth = #c end
612 env.coldwidth = env.colwidth + 1
615 function split_input(input)
616 local input = strip(input)
617 local s = string.find(input," ")
619 return string.sub(input,0,s-1), strip(string.sub(input,s))
625 function vlm_message_to_string(client,message,prefix)
626 local prefix = prefix or ""
627 if message.value then
628 client:append(prefix .. message.name .. " : " .. message.value)
630 client:append(prefix .. message.name)
632 if message.children then
633 for i,c in ipairs(message.children) do
634 vlm_message_to_string(client,c,prefix.." ")
639 --[[ Command dispatch ]]
640 function call_command(cmd,client,arg)
641 if type(commands[cmd]) == type("") then
646 ok, msg = pcall( commands[cmd].func, cmd, client, arg )
648 ok, msg = pcall( commands[cmd].func, cmd, client )
651 local a = arg and " "..arg or ""
652 client:append("Error in `"..cmd..a.."' ".. msg)
656 function call_vlm_command(cmd,client,arg)
660 local message, vlc_err = vlm:execute_command( cmd )
661 vlm_message_to_string( client, message )
665 function call_libvlc_command(cmd,client,arg)
666 local ok, vlcerr = pcall( vlc.var.libvlc_command, cmd, arg )
668 local a = arg and " "..arg or ""
669 client:append("Error in `"..cmd..a.."' ".. vlcerr) -- when pcall fails, the 2nd arg is the error message.
674 function call_object_command(cmd,client,arg)
677 var, val = split_input(arg)
679 local ok, vlcmsg, vlcerr = pcall( vlc.var.command, cmd, var, val )
681 local v = var and " "..var or ""
682 local v2 = val and " "..val or ""
683 client:append("Error in `"..cmd..v..v2.."' ".. vlcmsg) -- when pcall fails the 2nd arg is the error message
686 client:append(vlcmsg)
691 function client_command( client )
692 local cmd,arg = split_input(client.buffer)
695 if commands[cmd] then
696 call_command(cmd,client,arg)
697 elseif string.sub(cmd,0,1)=='@'
698 and call_object_command(string.sub(cmd,2,#cmd),client,arg) == 0 then
700 elseif call_vlm_command(cmd,client,arg) == 0 then
702 elseif client.type == host.client_type.stdio
703 and call_libvlc_command(cmd,client,arg) == 0 then
707 if client.env.autocompletion ~= 0 then
708 for v,_ in common.pairs_sorted(commands) do
709 if string.sub(v,0,#cmd)==cmd then
710 table.insert(choices, v)
714 if #choices == 1 and client.env.autoalias ~= 0 then
715 -- client:append("Aliasing to \""..choices[1].."\".")
717 call_command(cmd,client,arg)
719 client:append("Unknown command `"..cmd.."'. Type `help' for help.")
720 if #choices ~= 0 then
721 client:append("Possible choices are:")
722 local cols = math.floor(client.env.width/(client.env.colwidth+1))
723 local fmt = "%-"..client.env.colwidth.."s"
724 for i = 1, #choices do
725 choices[i] = string.format(fmt,choices[i])
727 for i = 1, #choices, cols do
728 local j = i + cols - 1
729 if j > #choices then j = #choices end
730 client:append(" "..table.concat(choices," ",i,j))
737 --[[ Some telnet command special characters ]]
738 WILL = "\251" -- Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option.
739 WONT = "\252" -- Indicates the refusal to perform, or continue performing, the indicated option.
740 DO = "\253" -- Indicates the request that the other party perform, or confirmation that you are expecting the other party to perform, the indicated option.
741 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.
742 IAC = "\255" -- Interpret as command
746 function telnet_commands( client )
747 -- remove telnet command replies from the client's data
748 client.buffer = string.gsub( client.buffer, IAC.."["..DO..DONT..WILL..WONT.."].", "" )
751 --[[ Client status change callbacks ]]
752 function on_password( client )
753 client.env = common.table_copy( env )
754 if client.type == host.client_type.telnet then
755 client:send( "Password: " ..IAC..WILL..ECHO )
757 if client.env.welcome ~= "" then
758 client:send( client.env.welcome .. "\r\n")
760 client:switch_status( host.status.read )
763 -- Print prompt when switching a client's status to `read'
764 function on_read( client )
765 client:send( client.env.prompt )
767 function on_write( client )
774 h.status_callbacks[host.status.password] = on_password
775 h.status_callbacks[host.status.read] = on_read
776 h.status_callbacks[host.status.write] = on_write
778 h:listen( config.hosts or config.host or "*console" )
779 password = config.password or "admin"
784 --[[ The main loop ]]
785 while not vlc.misc.should_die() do
786 local write, read = h:accept_and_select()
788 for _, client in pairs(write) do
789 local len = client:send()
790 client.buffer = string.sub(client.buffer,len+1)
791 if client.buffer == "" then client:switch_status(host.status.read) end
794 for _, client in pairs(read) do
795 local input = client:recv(1000)
797 if input == nil -- the telnet client program has left
798 or ((client.type == host.client_type.net
799 or client.type == host.client_type.telnet)
800 and input == "\004") then
802 client.cmds = "quit\n"
804 client.cmds = client.cmds .. input
808 -- split the command at the first '\n'
809 while string.find(client.cmds, "\n") do
810 -- save the buffer to send to the client
811 local saved_buffer = client.buffer
813 -- get the next command
814 local index = string.find(client.cmds, "\n")
815 client.buffer = strip(string.sub(client.cmds, 0, index - 1))
816 client.cmds = string.sub(client.cmds, index + 1)
818 -- Remove telnet commands from the command line
819 if client.type == host.client_type.telnet then
820 telnet_commands( client )
824 if client.status == host.status.password then
825 if client.buffer == password then
826 client:send( IAC..WONT..ECHO.."\r\nWelcome, Master\r\n" )
828 client:switch_status( host.status.write )
829 elseif client.buffer == "quit" then
830 client_command( client )
832 client:send( "\r\nWrong password\r\nPassword: " )
836 client:switch_status( host.status.write )
837 client_command( client )
839 client.buffer = saved_buffer .. client.buffer