1 --[==========================================================================[
2 rc
.lua
: remote control module
for VLC
3 --[==========================================================================[
4 Copyright (C
) 2007-2009 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 --]==========================================================================]
25 [============================================================================[
26 Remote control interface for VLC
28 This is a modules/control/rc.c look alike (with a bunch of new features)
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'}}"
38 -I rc and -I luarc are aliases for -I lua --lua-intf rc
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
45 The following can be set using the --lua-config option or in the interface
46 itself using the `set' command:
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,
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 ]============================================================================]
60 skip2
= function(foo
) return skip(skip(foo
)) end
61 setarg
= common
.setarg
67 --[[ Setup default environement ]]
68 env
= { prompt
= "> ";
72 welcome
= _("Remote control interface initialized. Type `help' for help.");
76 --[[ Import custom environement variables from the command line config (if possible) ]]
77 for k
,v
in pairs(env
) do
79 if type(env
[k
]) == type(config
[k
]) then
81 vlc
.msg
.dbg("set environement variable `"..k
.."' to "..tostring(env
[k
]))
83 vlc
.msg
.err("environement variable `"..k
.."' should be of type "..type(env
[k
])..". config value will be discarded.")
88 --[[ Command functions ]]
89 function set_env(name
,client
,value
)
91 local var
,val
= split_input(value
)
93 local s
= string.gsub(val
,"\"(.*)\"","%1")
94 if type(client
.env
[var
])==type(1) then
95 client
.env
[var
] = tonumber(s
)
100 client
:append( tostring(client
.env
[var
]) )
103 for e
,v
in common
.pairs_sorted(client
.env
) do
104 client
:append(e
.."="..v
)
109 function save_env(name
,client
,value
)
110 env
= common
.table_copy(client
.env
)
113 function alias(client
,value
)
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")
119 if commands
[val
] then
122 client
:append("Error: unknown primary command `"..val
.."'.")
126 for c
,v
in common
.pairs_sorted(commands
) do
127 if type(v
)==type("") then
128 client
:append(c
.."="..v
)
134 function fixme(name
,client
)
135 client
:append( "FIXME: unimplemented command `"..name
.."'." )
138 function logout(name
,client
)
139 if client
.type == host
.client_type
.net
then
140 client
:send("Bye-bye!")
143 client
:append("Error: Can't logout of stdin/stdout. Use quit or shutdown to close VLC.")
147 function shutdown(name
,client
)
148 client
:append("Bye-bye!")
149 h
:broadcast("Shutting down.")
150 vlc
.msg
.info("Requested shutdown.")
154 function quit(name
,client
)
155 if client
.type == host
.client_type
.net
then
158 shutdown(name
,client
)
162 function add(name
,client
,arg
)
163 -- TODO: parse single and double quotes properly
165 if name
== "enqueue" then
166 f
= vlc
.playlist
.enqueue
171 for o
in string.gmatch(arg
," +:([^ ]*)") do
172 table.insert(options
,o
)
174 arg
= string.gsub(arg
," +:.*$","")
175 f({{path
=arg
,options
=options
}})
178 function playlist_is_tree( client
)
179 if client
.env
.flatplaylist
== 0 then
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
)..")"
194 if item
.nb_played
> 0 then
195 str
= str
.." [played "..tostring(item
.nb_played
).." time"
196 if item
.nb_played
> 1 then
203 if item
.children
then
204 for _
, c
in ipairs(item
.children
) do
205 playlist0(c
,prefix
.." ")
210 local tree
= playlist_is_tree(client
)
211 if name
== "search" then
212 playlist
= vlc
.playlist
.search(arg
or "", tree
)
214 if tonumber(arg
) then
215 playlist
= vlc
.playlist
.get(tonumber(arg
), tree
)
217 playlist
= vlc
.playlist
.get(arg
, tree
)
219 playlist
= vlc
.playlist
.get(nil, tree
)
222 if name
== "search" then
223 client
:append("+----[ Search - "..(arg
or "`reset'").." ]")
225 client
:append("+----[ Playlist - "..playlist
.name
.." ]")
227 if playlist
.children
then
228 for _
, item
in ipairs(playlist
.children
) do
234 if name
== "search" then
235 client
:append("+----[ End of search - Use `search' to reset ]")
237 client
:append("+----[ End of playlist ]")
241 function playlist_sort(name
,client
,arg
)
243 client
:append("Valid sort keys are: id, title, artist, genre, random, duration, album.")
245 local tree
= playlist_is_tree(client
)
246 vlc
.playlist
.sort(arg
,false,tree
)
250 function services_discovery(name
,client
,arg
)
252 if vlc
.sd
.is_loaded(arg
) then
254 client
:append(arg
.." disabled.")
257 client
:append(arg
.." enabled.")
260 local sd
= vlc
.sd
.get_services_names()
261 client
:append("+----[ Services discovery ]")
262 for n
,ln
in pairs(sd
) do
264 if vlc
.sd
.is_loaded(n
) then
269 client
:append("| "..n
..": " .. ln
.. " (" .. status
.. ")")
271 client
:append("+----[ End of services discovery ]")
275 function print_text(label
,text
)
276 return function(name
,client
)
277 client
:append("+----[ "..label
.." ]")
279 for line
in string.gmatch(text
,".-\r?\n") do
280 client
:append("| "..string.gsub(line
,"\r?\n",""))
283 client
:append("+----[ End of "..string.lower(label
).." ]")
287 function help(name
,client
,arg
)
288 local width
= client
.env
.width
289 local long
= (name
== "longhelp")
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
298 local val
= commands
[cmd
]
300 for _
,a
in ipairs(val
.aliases
) do
301 str
= str
.. ", " .. a
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
312 client
:append("+----[ end of help ]")
315 function input_info(name
,client
)
316 local item
= vlc
.input
.item()
317 if(item
== nil) then return end
318 local categories
= item
:info()
319 for cat
, infos
in pairs(categories
) do
320 client
:append("+----[ "..cat
.." ]")
322 for name
, value
in pairs(infos
) do
323 client
:append("| "..name
..": "..value
)
327 client
:append("+----[ end of stream info ]")
330 function stats(name
,client
)
331 local item
= vlc
.input
.item()
332 if(item
== nil) then return end
333 local stats_tab
= item
:stats()
335 client
:append("+----[ begin of statistical info")
336 client
:append("+-[Incoming]")
337 client
:append("| input bytes read : "..string.format("%8.0f KiB",stats_tab
["read_bytes"]/1024))
338 client
:append("| input bitrate : "..string.format("%6.0f kb/s",stats_tab
["input_bitrate"]*8000))
339 client
:append("| demux bytes read : "..string.format("%8.0f KiB",stats_tab
["demux_read_bytes"]/1024))
340 client
:append("| demux bitrate : "..string.format("%6.0f kb/s",stats_tab
["demux_bitrate"]*8000))
341 client
:append("| demux corrupted : "..string.format("%5i",stats_tab
["demux_corrupted"]))
342 client
:append("| discontinuities : "..string.format("%5i",stats_tab
["demux_discontinuity"]))
344 client
:append("+-[Video Decoding]")
345 client
:append("| video decoded : "..string.format("%5i",stats_tab
["decoded_video"]))
346 client
:append("| frames displayed : "..string.format("%5i",stats_tab
["displayed_pictures"]))
347 client
:append("| frames lost : "..string.format("%5i",stats_tab
["lost_pictures"]))
349 client
:append("+-[Audio Decoding]")
350 client
:append("| audio decoded : "..string.format("%5i",stats_tab
["decoded_audio"]))
351 client
:append("| buffers played : "..string.format("%5i",stats_tab
["played_abuffers"]))
352 client
:append("| buffers lost : "..string.format("%5i",stats_tab
["lost_abuffers"]))
354 client
:append("+-[Streaming]")
355 client
:append("| packets sent : "..string.format("%5i",stats_tab
["sent_packets"]))
356 client
:append("| bytes sent : "..string.format("%8.0f KiB",stats_tab
["sent_bytes"]/1024))
357 client
:append("| sending bitrate : "..string.format("%6.0f kb/s",stats_tab
["send_bitrate"]*8000))
358 client
:append("+----[ end of statistical info ]")
361 function playlist_status(name
,client
)
362 local item
= vlc
.input
.item()
364 client
:append( "( new input: " .. vlc
.strings
.decode_uri(item
:uri()) .. " )" )
366 client
:append( "( audio volume: " .. tostring(vlc
.volume
.get()) .. " )")
367 client
:append( "( state " .. vlc
.playlist
.status() .. " )")
370 function is_playing(name
,client
)
371 if vlc
.input
.is_playing() then client
:append
"1" else client
:append
"0" end
374 function get_title(name
,client
)
375 local item
= vlc
.input
.item()
377 client
:append(item
:name())
383 function ret_print(foo
,start
,stop
)
384 local start
= start
or ""
385 local stop
= stop
or ""
386 return function(discard
,client
,...) client
:append(start
..tostring(foo(...))..stop
) end
389 function get_time(var
)
390 return function(name
,client
)
391 local input
= vlc
.object
.input()
392 client
:append(math
.floor(vlc
.var
.get( input
, var
)))
396 function titlechap(name
,client
,value
)
397 local input
= vlc
.object
.input()
398 local var
= string.gsub( name
, "_.*$", "" )
400 vlc
.var
.set( input
, var
, value
)
402 local item
= vlc
.var
.get( input
, var
)
403 -- Todo: add item name conversion
407 function titlechap_offset(client
,offset
)
408 return function(name
,value
)
409 local input
= vlc
.object
.input()
410 local var
= string.gsub( name
, "_.*$", "" )
411 vlc
.var
.set( input
, var
, vlc
.var
.get( input
, var
)+offset
)
415 function seek(name
,client
,value
)
419 function volume(name
,client
,value
)
423 client
:append(tostring(vlc
.volume
.get()))
427 function rate(name
,client
,value
)
428 local input
= vlc
.object
.input()
429 if name
== "rate" then
430 vlc
.var
.set(input
, "rate", tonumber(value
))
431 elseif name
== "normal" then
432 vlc
.var
.set(input
,"rate",1)
434 vlc
.var
.set(input
,"rate-"..name
,nil)
438 function frame(name
,client
)
439 vlc
.var
.trigger_callback(vlc
.object
.input(),"frame-next");
442 function listvalue(obj
,var
)
443 return function(client
,value
)
444 local o
= vlc
.object
.find(nil,obj
,"anywhere")
445 if not o
then return end
447 vlc
.var
.set( o
, var
, value
)
449 local c
= vlc
.var
.get( o
, var
)
450 local v
, l
= vlc
.var
.get_list( o
, var
)
451 client
:append("+----[ "..var
.." ]")
452 for i
,val
in ipairs(v
) do
453 local mark
= (val
==c
)and " *" or ""
454 client
:append("| "..tostring(val
).." - "..tostring(l
[i
])..mark
)
456 client
:append("+----[ end of "..var
.." ]")
461 function menu(name
,client
,value
)
462 local map
= { on
='show', off
='hide', up
='up', down
='down', left
='prev', right
='next', ['select']='activate' }
463 if map
[value
] and vlc
.osd
.menu
[map
[value]]
then
464 vlc
.osd
.menu
[map
[value]]
()
466 client
:append("Unknown menu command '"..tostring(value
).."'")
470 function hotkey(name
, client
, value
)
472 client
:append("Please specify a hotkey (ie key-quit or quit)")
473 elseif not common
.hotkey(value
) and not common
.hotkey("key-"..value
) then
474 client
:append("Unknown hotkey '"..value
.."'")
478 function eval(client
,val
)
479 client
:append(tostring(loadstring("return "..val
)()))
482 --[[ Declare commands, register their callback functions and provide
485 "<command name>"; { func = <function>; [ args = "<str>"; ] help = "<str>"; [ adv = <bool>; ] [ aliases = { ["<str>";]* }; ] }
488 { "add"; { func
= add
; args
= "XYZ"; help
= "add XYZ to playlist" } };
489 { "enqueue"; { func
= add
; args
= "XYZ"; help
= "queue XYZ to playlist" } };
490 { "playlist"; { func
= playlist
; help
= "show items currently in playlist" } };
491 { "search"; { func
= playlist
; args
= "[string]"; help
= "search for items in playlist (or reset search)" } };
492 { "sort"; { func
= playlist_sort
; args
= "key"; help
= "sort the playlist" } };
493 { "sd"; { func
= services_discovery
; args
= "[sd]"; help
= "show services discovery or toggle" } };
494 { "play"; { func
= skip2(vlc
.playlist
.play
); help
= "play stream" } };
495 { "stop"; { func
= skip2(vlc
.playlist
.stop
); help
= "stop stream" } };
496 { "next"; { func
= skip2(vlc
.playlist
.next); help
= "next playlist item" } };
497 { "prev"; { func
= skip2(vlc
.playlist
.prev
); help
= "previous playlist item" } };
498 { "goto"; { func
= skip2(vlc
.playlist
.goto
); help
= "goto item at index" } };
499 { "repeat"; { func
= skip2(vlc
.playlist
.repeat_
); args
= "[on|off]"; help
= "toggle playlist repeat" } };
500 { "loop"; { func
= skip2(vlc
.playlist
.loop
); args
= "[on|off]"; help
= "toggle playlist loop" } };
501 { "random"; { func
= skip2(vlc
.playlist
.random); args
= "[on|off]"; help
= "toggle playlist random" } };
502 { "clear"; { func
= skip2(vlc
.playlist
.clear
); help
= "clear the playlist" } };
503 { "status"; { func
= playlist_status
; help
= "current playlist status" } };
504 { "title"; { func
= titlechap
; args
= "[X]"; help
= "set/get title in current item" } };
505 { "title_n"; { func
= titlechap_offset(1); help
= "next title in current item" } };
506 { "title_p"; { func
= titlechap_offset(-1); help
= "previous title in current item" } };
507 { "chapter"; { func
= titlechap
; args
= "[X]"; help
= "set/get chapter in current item" } };
508 { "chapter_n"; { func
= titlechap_offset(1); help
= "next chapter in current item" } };
509 { "chapter_p"; { func
= titlechap_offset(-1); help
= "previous chapter in current item" } };
511 { "seek"; { func
= seek; args
= "X"; help
= "seek in seconds, for instance `seek 12'" } };
512 { "pause"; { func
= skip2(vlc
.playlist
.pause
); help
= "toggle pause" } };
513 { "fastforward"; { func
= setarg(common
.hotkey
,"key-jump+extrashort"); help
= "set to maximum rate" } };
514 { "rewind"; { func
= setarg(common
.hotkey
,"key-jump-extrashort"); help
= "set to minimum rate" } };
515 { "faster"; { func
= rate
; help
= "faster playing of stream" } };
516 { "slower"; { func
= rate
; help
= "slower playing of stream" } };
517 { "normal"; { func
= rate
; help
= "normal playing of stream" } };
518 { "rate"; { func
= rate
; args
= "[playback rate]"; help
= "set playback rate to value" } };
519 { "frame"; { func
= frame
; help
= "play frame by frame" } };
520 { "fullscreen"; { func
= skip2(vlc
.video
.fullscreen
); args
= "[on|off]"; help
= "toggle fullscreen"; aliases
= { "f", "F" } } };
521 { "info"; { func
= input_info
; help
= "information about the current stream" } };
522 { "stats"; { func
= stats
; help
= "show statistical information" } };
523 { "get_time"; { func
= get_time("time"); help
= "seconds elapsed since stream's beginning" } };
524 { "is_playing"; { func
= is_playing
; help
= "1 if a stream plays, 0 otherwise" } };
525 { "get_title"; { func
= get_title
; help
= "the title of the current stream" } };
526 { "get_length"; { func
= get_time("length"); help
= "the length of the current stream" } };
528 { "volume"; { func
= volume
; args
= "[X]"; help
= "set/get audio volume" } };
529 { "volup"; { func
= ret_print(vlc
.volume
.up
,"( audio volume: "," )"); args
= "[X]"; help
= "raise audio volume X steps" } };
530 { "voldown"; { func
= ret_print(vlc
.volume
.down
,"( audio volume: "," )"); args
= "[X]"; help
= "lower audio volume X steps" } };
531 { "adev"; { func
= skip(listvalue("aout","audio-device")); args
= "[X]"; help
= "set/get audio device" } };
532 { "achan"; { func
= skip(listvalue("aout","audio-channels")); args
= "[X]"; help
= "set/get audio channels" } };
533 { "atrack"; { func
= skip(listvalue("input","audio-es")); args
= "[X]"; help
= "set/get audio track" } };
534 { "vtrack"; { func
= skip(listvalue("input","video-es")); args
= "[X]"; help
= "set/get video track" } };
535 { "vratio"; { func
= skip(listvalue("vout","aspect-ratio")); args
= "[X]"; help
= "set/get video aspect ratio" } };
536 { "vcrop"; { func
= skip(listvalue("vout","crop")); args
= "[X]"; help
= "set/get video crop"; aliases
= { "crop" } } };
537 { "vzoom"; { func
= skip(listvalue("vout","zoom")); args
= "[X]"; help
= "set/get video zoom"; aliases
= { "zoom" } } };
538 { "snapshot"; { func
= common
.snapshot
; help
= "take video snapshot" } };
539 { "strack"; { func
= skip(listvalue("input","spu-es")); args
= "[X]"; help
= "set/get subtitles track" } };
540 { "hotkey"; { func
= hotkey
; args
= "[hotkey name]"; help
= "simulate hotkey press"; adv
= true; aliases
= { "key" } } };
541 { "menu"; { func
= menu
; args
= "[on|off|up|down|left|right|select]"; help
= "use menu"; adv
= true } };
543 { "set"; { func
= set_env
; args
= "[var [value]]"; help
= "set/get env var"; adv
= true } };
544 { "save_env"; { func
= save_env
; help
= "save env vars (for future clients)"; adv
= true } };
545 { "alias"; { func
= skip(alias
); args
= "[cmd]"; help
= "set/get command aliases"; adv
= true } };
546 { "description"; { func
= print_text("Description",description
); help
= "describe this module" } };
547 { "license"; { func
= print_text("License message",vlc
.misc
.license()); help
= "print VLC's license message"; adv
= true } };
548 { "help"; { func
= help
; args
= "[pattern]"; help
= "a help message"; aliases
= { "?" } } };
549 { "longhelp"; { func
= help
; args
= "[pattern]"; help
= "a longer help message" } };
550 { "logout"; { func
= logout
; help
= "exit (if in a socket connection)" } };
551 { "quit"; { func
= quit
; help
= "quit VLC (or logout if in a socket connection)" } };
552 { "shutdown"; { func
= shutdown
; help
= "shutdown VLC" } };
556 commands_ordered
[#commands_ordered
] = { "eval"; { func
= skip(eval
); help
= "eval some lua (*debug*)"; adv
=true } }
560 for i
, cmd
in ipairs( commands_ordered
) do
562 commands
[cmd
[1]]
=cmd
[2]
563 if cmd
[2].aliases
then
564 for _
,a
in ipairs(cmd
[2].aliases
) do
569 commands_ordered
[i
]=cmd
[1]
571 --[[ From now on commands_ordered is a list of the different command names
572 and commands is a associative array indexed by the command name. ]]
574 -- Compute the column width used when printing a the autocompletion list
576 for c
,_
in pairs(commands
) do
577 if #c
> env
.colwidth
then env
.colwidth
= #c
end
579 env
.coldwidth
= env
.colwidth
+ 1
581 -- Count unimplemented functions
585 for c
,v
in pairs(commands
) do
586 if v
.func
== fixme
then
595 if count
~= 0 and env
.welcome
and env
.welcome
~= "" then
596 env
.welcome
= env
.welcome
.. "\r\nWarning: "..count
.." functions are still unimplemented "..list
.."."
601 function split_input(input
)
602 local input
= strip(input
)
603 local s
= string.find(input
," ")
605 return string.sub(input
,0,s
-1), strip(string.sub(input
,s
))
611 function call_command(cmd
,client
,arg
)
612 if type(commands
[cmd
]) == type("") then
617 ok
, msg
= pcall( commands
[cmd
].func
, cmd
, client
, arg
)
619 ok
, msg
= pcall( commands
[cmd
].func
, cmd
, client
)
623 if a
~= "" then a
= " " .. a
end
624 client
:append("Error in `"..cmd
..a
.."' ".. msg
)
628 function call_libvlc_command(cmd
,client
,arg
)
629 local ok
, vlcerr
, vlcmsg
= pcall( vlc
.var
.libvlc_command
, cmd
, arg
)
632 if a
~= "" then a
= " " .. a
end
633 client
:append("Error in `"..cmd
..a
.."' ".. vlcerr
) -- when pcall fails, the 2nd arg is the error message.
638 function call_object_command(cmd
,client
,arg
)
639 local var
, val
= split_input(arg
)
640 local ok
, vlcmsg
, vlcerr
, vlcerrmsg
= pcall( vlc
.var
.command
, cmd
, var
, val
)
642 client
:append("Error in `"..cmd
.." "..var
.." "..val
.."' ".. vlcmsg
) -- when pcall fails the 2nd arg is the error message
645 client
:append(vlcmsg
)
654 h
.status_callbacks
[host
.status
.password
] = function(client
)
655 client
.env
= common
.table_copy( env
)
656 if client
.env
.welcome
~= "" then
657 client
:send( client
.env
.welcome
.. "\r\n")
659 client
:switch_status(host
.status
.read)
661 -- Print prompt when switching a client's status to `read'
662 h
.status_callbacks
[host
.status
.read] = function(client
)
663 client
:send( client
.env
.prompt
)
666 h
:listen( config
.hosts
or config
.host
or "*console" )
668 --[[ The main loop ]]
669 while not vlc
.misc
.should_die() do
670 local write, read = h
:accept_and_select()
672 for _
, client
in pairs(write) do
673 local len
= client
:send()
674 client
.buffer
= string.sub(client
.buffer
,len
+1)
675 if client
.buffer
== "" then client
:switch_status(host
.status
.read) end
678 for _
, client
in pairs(read) do
679 local input
= client
:recv(1000)
681 if string.match(input
,"\n$") then
682 client
.buffer
= string.gsub(client
.buffer
..input
,"\r?\n$","")
684 elseif client
.buffer
== ""
685 and ((client
.type == host
.client_type
.stdio
and input
== "")
686 or (client
.type == host
.client_type
.net
and input
== "\004")) then
688 client
.buffer
= "quit"
691 client
.buffer
= client
.buffer
.. input
694 local cmd
,arg
= split_input(client
.buffer
)
696 client
:switch_status(host
.status
.write)
697 if commands
[cmd
] then
698 call_command(cmd
,client
,arg
)
699 elseif string.sub(cmd
,0,1)=='@'
700 and call_object_command(string.sub(cmd
,2,#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
))