]> git.sesse.net Git - vlc/blob - share/lua/intf/rc.lua
Use KiB instead of kB for input statistics
[vlc] / share / lua / intf / rc.lua
1 --[==========================================================================[
2  rc.lua: remote control module for VLC
3 --[==========================================================================[
4  Copyright (C) 2007-2009 the VideoLAN team
5  $Id$
6
7  Authors: Antoine Cellerier <dionoea at videolan dot org>
8
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.
13
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.
18
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 --]==========================================================================]
23
24 description=
25 [============================================================================[
26  Remote control interface for VLC
27
28  This is a modules/control/rc.c look alike (with a bunch of new features)
29
30  Use on local term:
31     vlc -I rc
32  Use on tcp connection:
33     vlc -I rc --lua-config "rc={host='localhost:4212'}"
34  Use on multiple hosts (term + 2 tcp ports):
35     vlc -I rc --lua-config "rc={hosts={'*console','localhost:4212','localhost:5678'}}"
36
37  Note:
38     -I rc and -I luarc are aliases for -I lua --lua-intf rc
39
40  Configuration options setable throught the --lua-config option are:
41     * hosts: A list of hosts to listen on.
42     * host: A host to listen on. (won't be used if `hosts' is set)
43     * eval: Add eval command to evaluate lua expressions. Set to any value to
44             enable.
45  The following can be set using the --lua-config option or in the interface
46  itself using the `set' command:
47     * prompt: The prompt.
48     * welcome: The welcome message.
49     * width: The default terminal width (used to format text).
50     * autocompletion: When issuing an unknown command, print a list of
51                       possible commands to autocomplete with. (0 to disable,
52                       1 to enable).
53     * autoalias: If autocompletion returns only one possibility, use it
54                  (0 to disable, 1 to enable).
55     * flatplaylist: 0 to disable, 1 to enable.
56 ]============================================================================]
57
58 require("common")
59 skip = common.skip
60 skip2 = function(foo) return skip(skip(foo)) end
61 setarg = common.setarg
62 strip = common.strip
63
64 _ = vlc.gettext._
65 N_ = vlc.gettext.N_
66
67 --[[ Setup default environement ]]
68 env = { prompt = "> ";
69         width = 70;
70         autocompletion = 1;
71         autoalias = 1;
72         welcome = _("Remote control interface initialized. Type `help' for help.");
73         flatplaylist = 0;
74       }
75
76 --[[ Import custom environement variables from the command line config (if possible) ]]
77 for k,v in pairs(env) do
78     if config[k] then
79         if type(env[k]) == type(config[k]) then
80             env[k] = config[k]
81             vlc.msg.dbg("set environement variable `"..k.."' to "..tostring(env[k]))
82         else
83             vlc.msg.err("environement variable `"..k.."' should be of type "..type(env[k])..". config value will be discarded.")
84         end
85     end
86 end
87
88 --[[ Command functions ]]
89 function set_env(name,client,value)
90     if value then
91         local var,val = split_input(value)
92         if val then
93             local s = string.gsub(val,"\"(.*)\"","%1")
94             if type(client.env[var])==type(1) then
95                 client.env[var] = tonumber(s)
96             else
97                 client.env[var] = s
98             end
99         else
100             client:append( tostring(client.env[var]) )
101         end
102     else
103         for e,v in common.pairs_sorted(client.env) do
104             client:append(e.."="..v)
105         end
106     end
107 end
108
109 function save_env(name,client,value)
110     env = common.table_copy(client.env)
111 end
112
113 function alias(client,value)
114     if value then
115         local var,val = split_input(value)
116         if commands[var] and type(commands[var]) ~= type("") then
117             client:append("Error: cannot use a primary command as an alias name")
118         else
119             if commands[val] then
120                 commands[var]=val
121             else
122                 client:append("Error: unknown primary command `"..val.."'.")
123             end
124         end
125     else
126         for c,v in common.pairs_sorted(commands) do
127             if type(v)==type("") then
128                 client:append(c.."="..v)
129             end
130         end
131     end
132 end
133
134 function fixme(name,client)
135     client:append( "FIXME: unimplemented command `"..name.."'." )
136 end
137
138 function logout(name,client)
139     if client.type == host.client_type.net then
140         client:send("Bye-bye!")
141         client:del()
142     else
143         client:append("Error: Can't logout of stdin/stdout. Use quit or shutdown to close VLC.")
144     end
145 end
146
147 function shutdown(name,client)
148     client:append("Bye-bye!")
149     h:broadcast("Shutting down.")
150     vlc.msg.info("Requested shutdown.")
151     vlc.misc.quit()
152 end
153
154 function quit(name,client)
155     if client.type == host.client_type.net then
156         logout(name,client)
157     else
158         shutdown(name,client)
159     end
160 end
161
162 function add(name,client,arg)
163     -- TODO: parse single and double quotes properly
164     local f
165     if name == "enqueue" then
166         f = vlc.playlist.enqueue
167     else
168         f = vlc.playlist.add
169     end
170     local options = {}
171     for o in string.gmatch(arg," +:([^ ]*)") do
172         table.insert(options,o)
173     end
174     arg = string.gsub(arg," +:.*$","")
175     f({{path=arg,options=options}})
176 end
177
178 function playlist_is_tree( client )
179     if client.env.flatplaylist == 0 then
180         return true
181     else
182         return false
183     end
184 end
185
186 function playlist(name,client,arg)
187     function playlist0(item,prefix)
188         local prefix = prefix or ""
189         if not item.flags.disabled then
190             local str = "| "..prefix..tostring(item.id).." - "..item.name
191             if item.duration > 0 then
192                 str = str.." ("..common.durationtostring(item.duration)..")"
193             end
194             if item.nb_played > 0 then
195                 str = str.." [played "..tostring(item.nb_played).." time"
196                 if item.nb_played > 1 then
197                     str = str .. "s"
198                 end
199                 str = str .. "]"
200             end
201             client:append(str)
202         end
203         if item.children then
204             for _, c in ipairs(item.children) do
205                 playlist0(c,prefix.."  ")
206             end
207         end
208     end
209     local playlist
210     local tree = playlist_is_tree(client)
211     if name == "search" then
212         playlist = vlc.playlist.search(arg or "", tree)
213     else
214         if tonumber(arg) then
215             playlist = vlc.playlist.get(tonumber(arg), tree)
216         elseif arg then
217             playlist = vlc.playlist.get(arg, tree)
218         else
219             playlist = vlc.playlist.get(nil, tree)
220         end
221     end
222     if name == "search" then
223         client:append("+----[ Search - "..(arg or "`reset'").." ]")
224     else
225         client:append("+----[ Playlist - "..playlist.name.." ]")
226     end
227     if playlist.children then
228         for _, item in ipairs(playlist.children) do
229             playlist0(item)
230         end
231     else
232         playlist0(playlist)
233     end
234     if name == "search" then
235         client:append("+----[ End of search - Use `search' to reset ]")
236     else
237         client:append("+----[ End of playlist ]")
238     end
239 end
240
241 function playlist_sort(name,client,arg)
242     if not arg then
243         client:append("Valid sort keys are: id, title, artist, genre, random, duration, album.")
244     else
245         local tree = playlist_is_tree(client)
246         vlc.playlist.sort(arg,false,tree)
247     end
248 end
249
250 function services_discovery(name,client,arg)
251     if arg then
252         if vlc.sd.is_loaded(arg) then
253             vlc.sd.remove(arg)
254             client:append(arg.." disabled.")
255         else
256             vlc.sd.add(arg)
257             client:append(arg.." enabled.")
258         end
259     else
260         local sd = vlc.sd.get_services_names()
261         client:append("+----[ Services discovery ]")
262         for n,ln in pairs(sd) do
263             local status
264             if vlc.sd.is_loaded(n) then
265                 status = "enabled"
266             else
267                 status = "disabled"
268             end
269             client:append("| "..n..": " .. ln .. " (" .. status .. ")")
270         end
271         client:append("+----[ End of services discovery ]")
272     end
273 end
274
275 function print_text(label,text)
276     return function(name,client)
277         client:append("+----[ "..label.." ]")
278         client:append "|"
279         for line in string.gmatch(text,".-\r?\n") do
280             client:append("| "..string.gsub(line,"\r?\n",""))
281         end
282         client:append "|"
283         client:append("+----[ End of "..string.lower(label).." ]")
284     end
285 end
286
287 function help(name,client,arg)
288     local width = client.env.width
289     local long = (name == "longhelp")
290     local extra = ""
291     if arg then extra = "matching `" .. arg .. "' " end
292     client:append("+----[ Remote control commands "..extra.."]")
293     for i, cmd in ipairs(commands_ordered) do
294         if (cmd == "" or not commands[cmd].adv or long)
295         and (not arg or string.match(cmd,arg)) then
296             local str = "| " .. cmd
297             if cmd ~= "" then
298                 local val = commands[cmd]
299                 if val.aliases then
300                     for _,a in ipairs(val.aliases) do
301                         str = str .. ", " .. a
302                     end
303                 end
304                 if val.args then str = str .. " " .. val.args end
305                 if #str%2 == 1 then str = str .. " " end
306                 str = str .. string.rep(" .",(width-(#str+#val.help)-1)/2)
307                 str = str .. string.rep(" ",width-#str-#val.help) .. val.help
308             end
309             client:append(str)
310         end
311     end
312     client:append("+----[ end of help ]")
313 end
314
315 function input_info(name,client)
316     local item = vlc.item()
317     local categories = item:info()
318     for cat, infos in pairs(categories) do
319         client:append("+----[ "..cat.." ]")
320         client:append("|")
321         for name, value in pairs(infos) do
322             client:append("| "..name..": "..value)
323         end
324         client:append("|")
325     end
326     client:append("+----[ end of stream info ]")
327 end
328
329 function stats(name,client)
330     local item = vlc.item()
331     local stats_tab = item:stats()
332
333     client:append("+----[ begin of statistical info")
334     client:append("+-[Incoming]")
335     client:append("| input bytes read : "..string.format("%8.0f KiB",stats_tab["read_bytes"]/1024))
336     client:append("| input bitrate    :   "..string.format("%6.0f kB/s",stats_tab["input_bitrate"]*8000))
337     client:append("| demux bytes read : "..string.format("%8.0f KiB",stats_tab["demux_read_bytes"]/1024))
338     client:append("| demux bitrate    :   "..string.format("%6.0f kB/s",stats_tab["demux_bitrate"]*8000))
339     client:append("| demux corrupted  :    "..string.format("%5i",stats_tab["demux_corrupted"]))
340     client:append("| discontinuities  :    "..string.format("%5i",stats_tab["demux_discontinuity"]))
341     client:append("|")
342     client:append("+-[Video Decoding]")
343     client:append("| video decoded    :    "..string.format("%5i",stats_tab["decoded_video"]))
344     client:append("| frames displayed :    "..string.format("%5i",stats_tab["displayed_pictures"]))
345     client:append("| frames lost      :    "..string.format("%5i",stats_tab["lost_pictures"]))
346     client:append("|")
347     client:append("+-[Audio Decoding]")
348     client:append("| audio decoded    :    "..string.format("%5i",stats_tab["decoded_audio"]))
349     client:append("| buffers played   :    "..string.format("%5i",stats_tab["played_abuffers"]))
350     client:append("| buffers lost     :    "..string.format("%5i",stats_tab["lost_abuffers"]))
351     client:append("|")
352     client:append("+-[Streaming]")
353     client:append("| packets sent     :    "..string.format("%5i",stats_tab["sent_packets"]))
354     client:append("| bytes sent       : "..string.format("%8.0f KiB",stats_tab["sent_bytes"]/1024))
355     client:append("| sending bitrate  :   "..string.format("%6.0f kb/s",stats_tab["send_bitrate"]*8000))
356     client:append("+----[ end of statistical info ]")
357 end
358
359 function playlist_status(name,client)
360     client:append( "( new input: " .. "FIXME" .. " )" )
361     client:append( "( audio volume: " .. tostring(vlc.volume.get()) .. " )")
362     client:append( "( state " .. vlc.playlist.status() .. " )")
363 end
364
365 function is_playing(name,client)
366     if vlc.input.is_playing() then client:append "1" else client:append "0" end
367 end
368
369 function get_title(name,client)
370     local item = vlc.input.item()
371     if item then
372         client:append(item:name())
373     else
374         client:append("")
375     end
376 end
377
378 function ret_print(foo,start,stop)
379     local start = start or ""
380     local stop = stop or ""
381     return function(discard,client,...) client:append(start..tostring(foo(...))..stop) end
382 end
383
384 function get_time(var)
385     return function(name,client)
386         local input = vlc.object.input()
387         client:append(math.floor(vlc.var.get( input, var )))
388     end
389 end
390
391 function titlechap(name,client,value)
392     local input = vlc.object.input()
393     local var = string.gsub( name, "_.*$", "" )
394     if value then
395         vlc.var.set( input, var, value )
396     else
397         local item = vlc.var.get( input, var )
398         -- Todo: add item name conversion
399         client:append(item)
400     end
401 end
402 function titlechap_offset(client,offset)
403     return function(name,value)
404         local input = vlc.object.input()
405         local var = string.gsub( name, "_.*$", "" )
406         vlc.var.set( input, var, vlc.var.get( input, var )+offset )
407     end
408 end
409
410 function seek(name,client,value)
411     common.seek(value)
412 end
413
414 function volume(name,client,value)
415     if value then
416         common.volume(value)
417     else
418         client:append(tostring(vlc.volume.get()))
419     end
420 end
421
422 function rate(name,client,value)
423     local input = vlc.object.input()
424     if name == "rate" then
425         vlc.var.set(input, "rate", tonumber(value))
426     elseif name == "normal" then
427         vlc.var.set(input,"rate",1)
428     else
429         vlc.var.set(input,"rate-"..name,nil)
430     end
431 end
432
433 function frame(name,client)
434     vlc.var.trigger_callback(vlc.object.input(),"frame-next");
435 end
436
437 function listvalue(obj,var)
438     return function(client,value)
439         local o = vlc.object.find(nil,obj,"anywhere")
440         if not o then return end
441         if value then
442             vlc.var.set( o, var, value )
443         else
444             local c = vlc.var.get( o, var )
445             local v, l = vlc.var.get_list( o, var )
446             client:append("+----[ "..var.." ]")
447             for i,val in ipairs(v) do
448                 local mark = (val==c)and " *" or ""
449                 client:append("| "..tostring(val).." - "..tostring(l[i])..mark)
450             end
451             client:append("+----[ end of "..var.." ]")
452         end
453     end
454 end
455
456 function menu(name,client,value)
457     local map = { on='show', off='hide', up='up', down='down', left='prev', right='next', ['select']='activate' }
458     if map[value] and vlc.osd.menu[map[value]] then
459         vlc.osd.menu[map[value]]()
460     else
461         client:append("Unknown menu command '"..tostring(value).."'")
462     end
463 end
464
465 function hotkey(name, client, value)
466     if not value then
467         client:append("Please specify a hotkey (ie key-quit or quit)")
468     elseif not common.hotkey(value) and not common.hotkey("key-"..value) then
469         client:append("Unknown hotkey '"..value.."'")
470     end
471 end
472
473 function eval(client,val)
474     client:append(tostring(loadstring("return "..val)()))
475 end
476
477 --[[ Declare commands, register their callback functions and provide
478      help strings here.
479      Syntax is:
480      "<command name>"; { func = <function>; [ args = "<str>"; ] help = "<str>"; [ adv = <bool>; ] [ aliases = { ["<str>";]* }; ] }
481      ]]
482 commands_ordered = {
483     { "add"; { func = add; args = "XYZ"; help = "add XYZ to playlist" } };
484     { "enqueue"; { func = add; args = "XYZ"; help = "queue XYZ to playlist" } };
485     { "playlist"; { func = playlist; help = "show items currently in playlist" } };
486     { "search"; { func = playlist; args = "[string]"; help = "search for items in playlist (or reset search)" } };
487     { "sort"; { func = playlist_sort; args = "key"; help = "sort the playlist" } };
488     { "sd"; { func = services_discovery; args = "[sd]"; help = "show services discovery or toggle" } };
489     { "play"; { func = skip2(vlc.playlist.play); help = "play stream" } };
490     { "stop"; { func = skip2(vlc.playlist.stop); help = "stop stream" } };
491     { "next"; { func = skip2(vlc.playlist.next); help = "next playlist item" } };
492     { "prev"; { func = skip2(vlc.playlist.prev); help = "previous playlist item" } };
493     { "goto"; { func = skip2(vlc.playlist.goto); help = "goto item at index" } };
494     { "repeat"; { func = skip2(vlc.playlist.repeat_); args = "[on|off]"; help = "toggle playlist repeat" } };
495     { "loop"; { func = skip2(vlc.playlist.loop); args = "[on|off]"; help = "toggle playlist loop" } };
496     { "random"; { func = skip2(vlc.playlist.random); args = "[on|off]"; help = "toggle playlist random" } };
497     { "clear"; { func = skip2(vlc.playlist.clear); help = "clear the playlist" } };
498     { "status"; { func = playlist_status; help = "current playlist status" } };
499     { "title"; { func = titlechap; args = "[X]"; help = "set/get title in current item" } };
500     { "title_n"; { func = titlechap_offset(1); help = "next title in current item" } };
501     { "title_p"; { func = titlechap_offset(-1); help = "previous title in current item" } };
502     { "chapter"; { func = titlechap; args = "[X]"; help = "set/get chapter in current item" } };
503     { "chapter_n"; { func = titlechap_offset(1); help = "next chapter in current item" } };
504     { "chapter_p"; { func = titlechap_offset(-1); help = "previous chapter in current item" } };
505     { "" };
506     { "seek"; { func = seek; args = "X"; help = "seek in seconds, for instance `seek 12'" } };
507     { "pause"; { func = setarg(common.hotkey,"key-play-pause"); help = "toggle pause" } };
508     { "fastforward"; { func = setarg(common.hotkey,"key-jump+extrashort"); help = "set to maximum rate" } };
509     { "rewind"; { func = setarg(common.hotkey,"key-jump-extrashort"); help = "set to minimum rate" } };
510     { "faster"; { func = rate; help = "faster playing of stream" } };
511     { "slower"; { func = rate; help = "slower playing of stream" } };
512     { "normal"; { func = rate; help = "normal playing of stream" } };
513     { "rate"; { func = rate; args = "[playback rate]"; help = "set playback rate to value" } };
514     { "frame"; { func = frame; help = "play frame by frame" } };
515     { "fullscreen"; { func = skip2(vlc.video.fullscreen); args = "[on|off]"; help = "toggle fullscreen"; aliases = { "f", "F" } } };
516     { "info"; { func = input_info; help = "information about the current stream" } };
517     { "stats"; { func = stats; help = "show statistical information" } };
518     { "get_time"; { func = get_time("time"); help = "seconds elapsed since stream's beginning" } };
519     { "is_playing"; { func = is_playing; help = "1 if a stream plays, 0 otherwise" } };
520     { "get_title"; { func = get_title; help = "the title of the current stream" } };
521     { "get_length"; { func = get_time("length"); help = "the length of the current stream" } };
522     { "" };
523     { "volume"; { func = volume; args = "[X]"; help = "set/get audio volume" } };
524     { "volup"; { func = ret_print(vlc.volume.up,"( audio volume: "," )"); args = "[X]"; help = "raise audio volume X steps" } };
525     { "voldown"; { func = ret_print(vlc.volume.down,"( audio volume: "," )"); args = "[X]"; help = "lower audio volume X steps" } };
526     { "adev"; { func = skip(listvalue("aout","audio-device")); args = "[X]"; help = "set/get audio device" } };
527     { "achan"; { func = skip(listvalue("aout","audio-channels")); args = "[X]"; help = "set/get audio channels" } };
528     { "atrack"; { func = skip(listvalue("input","audio-es")); args = "[X]"; help = "set/get audio track" } };
529     { "vtrack"; { func = skip(listvalue("input","video-es")); args = "[X]"; help = "set/get video track" } };
530     { "vratio"; { func = skip(listvalue("vout","aspect-ratio")); args = "[X]"; help = "set/get video aspect ratio" } };
531     { "vcrop"; { func = skip(listvalue("vout","crop")); args = "[X]"; help = "set/get video crop"; aliases = { "crop" } } };
532     { "vzoom"; { func = skip(listvalue("vout","zoom")); args = "[X]"; help = "set/get video zoom"; aliases = { "zoom" } } };
533     { "snapshot"; { func = common.snapshot; help = "take video snapshot" } };
534     { "strack"; { func = skip(listvalue("input","spu-es")); args = "[X]"; help = "set/get subtitles track" } };
535     { "hotkey"; { func = hotkey; args = "[hotkey name]"; help = "simulate hotkey press"; adv = true; aliases = { "key" } } };
536     { "menu"; { func = menu; args = "[on|off|up|down|left|right|select]"; help = "use menu"; adv = true } };
537     { "" };
538     { "set"; { func = set_env; args = "[var [value]]"; help = "set/get env var"; adv = true } };
539     { "save_env"; { func = save_env; help = "save env vars (for future clients)"; adv = true } };
540     { "alias"; { func = skip(alias); args = "[cmd]"; help = "set/get command aliases"; adv = true } };
541     { "description"; { func = print_text("Description",description); help = "describe this module" } };
542     { "license"; { func = print_text("License message",vlc.misc.license()); help = "print VLC's license message"; adv = true } };
543     { "help"; { func = help; args = "[pattern]"; help = "a help message"; aliases = { "?" } } };
544     { "longhelp"; { func = help; args = "[pattern]"; help = "a longer help message" } };
545     { "logout"; { func = logout; help = "exit (if in a socket connection)" } };
546     { "quit"; { func = quit; help = "quit VLC (or logout if in a socket connection)" } };
547     { "shutdown"; { func = shutdown; help = "shutdown VLC" } };
548     }
549
550 if config.eval then
551     commands_ordered[#commands_ordered] = { "eval"; { func = skip(eval); help = "eval some lua (*debug*)"; adv =true } }
552 end
553
554 commands = {}
555 for i, cmd in ipairs( commands_ordered ) do
556     if #cmd == 2 then
557         commands[cmd[1]]=cmd[2]
558         if cmd[2].aliases then
559             for _,a in ipairs(cmd[2].aliases) do
560                 commands[a]=cmd[1]
561             end
562         end
563     end
564     commands_ordered[i]=cmd[1]
565 end
566 --[[ From now on commands_ordered is a list of the different command names
567      and commands is a associative array indexed by the command name. ]]
568
569 -- Compute the column width used when printing a the autocompletion list
570 env.colwidth = 0
571 for c,_ in pairs(commands) do
572     if #c > env.colwidth then env.colwidth = #c end
573 end
574 env.coldwidth = env.colwidth + 1
575
576 -- Count unimplemented functions
577 do
578     local count = 0
579     local list = "("
580     for c,v in pairs(commands) do
581         if v.func == fixme then
582             count = count + 1
583             if count ~= 1 then
584                 list = list..","
585             end
586             list = list..c
587         end
588     end
589     list = list..")"
590     if count ~= 0 and env.welcome and env.welcome ~= "" then
591         env.welcome = env.welcome .. "\r\nWarning: "..count.." functions are still unimplemented "..list.."."
592     end
593 end
594
595 --[[ Utils ]]
596 function split_input(input)
597     local input = strip(input)
598     local s = string.find(input," ")
599     if s then
600         return string.sub(input,0,s-1), strip(string.sub(input,s))
601     else
602         return input
603     end
604 end
605
606 function call_command(cmd,client,arg)
607     if type(commands[cmd]) == type("") then
608         cmd = commands[cmd]
609     end
610     local ok, msg
611     if arg ~= nil then
612         ok, msg = pcall( commands[cmd].func, cmd, client, arg )
613     else
614         ok, msg = pcall( commands[cmd].func, cmd, client )
615     end
616     if not ok then
617         local a = arg or ""
618         if a ~= "" then a = " " .. a end
619         client:append("Error in `"..cmd..a.."' ".. msg)
620     end
621 end
622
623 function call_libvlc_command(cmd,client,arg)
624     local ok, vlcerr, vlcmsg = pcall( vlc.var.libvlc_command, cmd, arg )
625     if not ok then
626         local a = arg or ""
627         if a ~= "" then a = " " .. a end
628         client:append("Error in `"..cmd..a.."' ".. vlcerr) -- when pcall fails, the 2nd arg is the error message.
629     end
630     return vlcerr
631 end
632
633 function call_object_command(cmd,client,arg)
634     local var, val = split_input(arg)
635     local ok, vlcmsg, vlcerr, vlcerrmsg = pcall( vlc.var.command, cmd, var, val )
636     if not ok then
637         client:append("Error in `"..cmd.." "..var.." "..val.."' ".. vlcmsg) -- when pcall fails the 2nd arg is the error message
638     end
639     if vlcmsg ~= "" then
640         client:append(vlcmsg)
641     end
642     return vlcerr
643 end
644
645 --[[ Setup host ]]
646 require("host")
647 h = host.host()
648 -- No auth
649 h.status_callbacks[host.status.password] = function(client)
650     client.env = common.table_copy( env )
651     if client.env.welcome ~= "" then
652         client:send( client.env.welcome .. "\r\n")
653     end
654     client:switch_status(host.status.read)
655 end
656 -- Print prompt when switching a client's status to `read'
657 h.status_callbacks[host.status.read] = function(client)
658     client:send( client.env.prompt )
659 end
660
661 h:listen( config.hosts or config.host or "*console" )
662
663 --[[ The main loop ]]
664 while not vlc.misc.should_die() do
665     local write, read = h:accept_and_select()
666
667     for _, client in pairs(write) do
668         local len = client:send()
669         client.buffer = string.sub(client.buffer,len+1)
670         if client.buffer == "" then client:switch_status(host.status.read) end
671     end
672
673     for _, client in pairs(read) do
674         local input = client:recv(1000)
675         local done = false
676         if string.match(input,"\n$") then
677             client.buffer = string.gsub(client.buffer..input,"\r?\n$","")
678             done = true
679         elseif client.buffer == ""
680            and ((client.type == host.client_type.stdio and input == "")
681            or  (client.type == host.client_type.net and input == "\004")) then
682             -- Caught a ^D
683             client.buffer = "quit"
684             done = true
685         else
686             client.buffer = client.buffer .. input
687         end
688         if done then
689             local cmd,arg = split_input(client.buffer)
690             client.buffer = ""
691             client:switch_status(host.status.write)
692             if commands[cmd] then
693                 call_command(cmd,client,arg)
694             elseif string.sub(cmd,0,1)=='@'
695             and call_object_command(string.sub(cmd,2,#cmd),client,arg) == 0 then
696                 --
697             elseif client.type == host.client_type.stdio
698             and call_libvlc_command(cmd,client,arg) == 0 then
699                 --
700             else
701                 local choices = {}
702                 if client.env.autocompletion ~= 0 then
703                     for v,_ in common.pairs_sorted(commands) do
704                         if string.sub(v,0,#cmd)==cmd then
705                             table.insert(choices, v)
706                         end
707                     end
708                 end
709                 if #choices == 1 and client.env.autoalias ~= 0 then
710                     -- client:append("Aliasing to \""..choices[1].."\".")
711                     cmd = choices[1]
712                     call_command(cmd,client,arg)
713                 else
714                     client:append("Unknown command `"..cmd.."'. Type `help' for help.")
715                     if #choices ~= 0 then
716                         client:append("Possible choices are:")
717                         local cols = math.floor(client.env.width/(client.env.colwidth+1))
718                         local fmt = "%-"..client.env.colwidth.."s"
719                         for i = 1, #choices do
720                             choices[i] = string.format(fmt,choices[i])
721                         end
722                         for i = 1, #choices, cols do
723                             local j = i + cols - 1
724                             if j > #choices then j = #choices end
725                             client:append("  "..table.concat(choices," ",i,j))
726                         end
727                     end
728                 end
729             end
730         end
731     end
732 end