by doy (jluehrs2 at uiuc dot edu)
updated by THC4k (http://www.ls-themes.org)
LSLua is a scripting interface to LiteStep using the Lua scripting language. Lua is a cross platform, open source, embeddable scripting language. This module provides an interface to the LSAPI through Lua scripts.
This document explains what LSLua as a module provides;
for convenience, each of lua code
,
litestep config code
, and
LSAPI code
are highlighted.
See history.txt for a list of bugs and changes in LSLua.
To see how LSLua may be used, with examples, see http://wiki.litestep.com.ar/Modules:LSLua
Visual C++ 2008 runtimes are required. If Litestep cannot load the module, click here.
Module sources are managed at bitbucket.
See the Lua 5.1 manual for a quick reference on using Lua. See also the book Programming in Lua. Code snippets and general information can be found at the lua-users wiki. Finally, for more specific questions not covered by those resources, feel free to ask for help in #lua on irc.freenode.net
To load LSLua use *NetLoadModule lslua-0.8
load lslua.dll
in your theme.rc. The
load lslua.dll
part is important, since,
due to restrictions in NetLoadModule, all of the modules that come with
LSLua must have a .dll extension for NetLoadModule to unpack them to the
correct place.
LSLua can be loaded threaded, but be aware that this can produce strange behavior. Litestep has no means of syncronisation between modules and you will most likely use lslua to interact with other modules. In most cases lua's coroutines will be the better choice over loading the module threaded.
*LuaFile <filename>
Executes the given file. This registers any functions that are declared,
and executes any statements not declared in a function. Any function
whose name begins with bang_
is also registered as a bang
command, named with the rest of the function name. For example, the
function bang_test
would be registered as
!test
. Bang functions take one optional string
argument consisting of the entire argument string passed to them.
*LuaBinaryModuleDir <directory>
Declares a directory to be added to the search path for binary modules
(package.cpath
). Uses an extension of .dll.
*LuaScriptModuleDir <directory>
Declares a directory to be added to the search path for Lua script
modules (package.path
). Uses an extension of .lua or
.lua.dll.
*LuaModuleDir <directory>
Equivalent to both of *LuaBinaryModuleDir
and
*LuaScriptModuleDir.
LSLuaErrorFile <file>
If set errors in lua scripts will be dumped to the <file> instead of shown via MessageBox.
!LuaExec <lua chunk>
Executes the argument as a Lua chunk.
LSLua defines the global table lslua
which contains some
important functions and variables.
lslua.message_box (text, title)
Creates a message box with text of text and optional title of title.
Quotes do work correctly, unlike !alert
.
lslua.exec (bang [,*args])
Executes bang
with the optional args
as a command, like LSXCommand or
!execute []
.
Uses LSExecute()
for calls with no args else LSExecuteEx
.
lslua.res ()
Returns a table such that lslua.res().x
is the x resolution,
lslua.res().y
is the y resolution, and
lslua.res().bpp
is the bits per pixel.
lslua.mouse ()
Returns x,y
such that x
is the x
coordinate of the mouse and y
is the y
coordinate of the mouse.
lslua.milli ()
Returns the current number of milliseconds since the last second (use
os.date
to get the rest of the time information).
lslua.version
a string containing the lslua version.
lslua.get_evar (name)
Get an evar's value in memory, not file. Returns a string, or
nil
if evar is not defined. Similar to what you expect
$name$
would give.
Uses GetRCString()
.
Note: the preferred way to use this function is through the evar module.
lslua.get_line (name)
Get an evar's raw value in memory, not file. Returns a string, or
nil
if evar is not defined. Similar to get_evar but does not
return first token.
Uses GetRCLine()
.
lslua.set_evar (name, value)
Set an evar's value in memory, not file.
Uses LSSetVariable()
.
Note: the preferred way to use this function is through the evar module.
lslua.rc_lines (prefix, file)
Get lines with given prefix from rc file. Use empty string for prefix to return all lines. If file not specified, defaults to settings already loaded in memory. Returns a table of strings.
Uses LCReadNextLine()
.
lslua.init
A user defined value that is called at the very end of module load, so that bangs are registered. It is also executed in a coroutine, so timers should work.
lslua.quit
Similar to lslua.init
,
but is called when lslua is unloaded.
LSLua comes with eight modules designed to make certain tasks easier. To
use one, include the line require "<modulename>"
at
the top of your Lua script.
The args module defines some useful argument parsing functions.
args.strjoin (table, sep)
Takes two arguments: a table of strings, and a string to use as the
separator. Returns a string containing all of the elements of the table
concatenated together, with elements separated by the separator string
passed as the second argument. For example, args.strjoin( { "this",
"is", "a", "test" }, " " ) == "this is a test"
args.strsplit (string, sep)
Takes two arguments: a string, and a delimiter (a separator). It splits
the string up into a table of substrings based on the delimiter, and
returns that table. For example, args.strsplit( "this is a test", "
" ) == { "this", "is", "a", "test" }
args.mstrsplit (string, sep)
The same as args.strsplit
,
except it returns the results as multiple return values rather
than as a table.
args.qsplit (string [, delimiters [, leftquotes [, rightquotes
]]])
Takes between 1 and 4 arguments, some of them optional. first argument is
a string, the second is a table of strings to use as delimiters, and the
third and fourth are tables of strings to use as left and right quotes,
respectively. If the fourth argument is omitted, it defaults to the same
as the third, and if they are both omitted, they default to { "\"",
"'" }
. If the second argument is omitted, it defaults to { "
" }
. If any of the second, third, or fourth arguments are strings,
they are automatically converted to tables holding those strings. This
function splits the string into substrings based on the delimiter like
args.strsplit
does,
but it takes into account quote characters as well. For example,
args.qsplit( "this is a 'string with quotes'" ) == { "this", "is", "a", "string with quotes" } args.qsplit( "%{hello},%{world}", ",", "%{", "}" ) == { "hello", "world" } args.qsplit( "[this is][a test]", "", "[", "]" ) == { "this is", "a test" }
args.mqsplit (string [, delimiters [, leftquotes [, rightquotes
]]])
The same as args.qsplit
,
except it returns the results as multiple return values rather than
as a table.
The evar module allows access to and (re)assignment of LiteStep evars.
evar.toboolean (evar)
Takes one argument, a string for an evar's name. It returns a boolean
evaluating the given evar as a boolean as how LiteStep core would in RC
'scripting'/preprocessing: It returns false
for an evar
whose value is one of these strings: "0"
,
"false"
, "off"
, or "no"
. Otherwise
it returns true
.
If for some reason you need to access the evar named "toboolean", access it with a different casing, like "ToBoolean".
The evar table itself can be indexed to access the values of evars. For
example, evar.LiteStepDir == evar[ "LiteStep" .. "Dir" ] --is
"C:\LiteStep\" (on my system)
.
The result is nil
for both undefined evars and those evars
previously set to nil
through the evar table (a magic value
is replaced with nil
).
Keys of the evar table can be assigned to; doing so will set an evar's
value. tostring()
is applied to this value. For example,
evar.nameOfEvar = 13
.
Setting an evar to nil
this way will actually set the evar
to a magic value.
Notes on evar access and assignment and why a magic
value for nil
:
There is no way (aside from !reload
or
!recycle
or restarting LS) to undefine an evar,
so the evar module has its own notion of 'undefined'; It does so using
its own magic value.
So it makes no sense to set an evar to nil
, because evars
are just key-value pairs where both key and value are strings --omitting
one or the other or both makes no sense.
The choice to handle nil
specially is because of the
addition of evar.toboolean()
;
nil
should be recognized as false
;
why not handle nil
in assignment as well?
To avoid this issue, don't associate the idea that an evar can hold
nil
.
The evarutil module provides functions to handle evars. You can use this module to save evars to a file.
evarutils.savevar(filename, varname, value [, comment])
Sets all occurances of varname
in file filename
to value
and appends the specified comment if it is provided.
evarutils.getvalue(filename, varname)
Reads file filename
and tries to find varname
. Returns the var's value or nil
evarutils.deletevar(filename,varname)
Removes all occurances of varname
from file filename
.
evarutils.config
evarutils.config
provides simple shortcuts for reading and writing config files. You can handle your Theme's settings in a very simple way with this.
The syntax for working with the config object is
evarutils.config.[<relative folders>.*]<filename>.<evarname> = <value>
The file path is constructed from evar.ThemeDir .. <relative folders> .. <filename> .. ".rc"
.
Example:
First, create a config object for the file you want to save your theme's settings to:
cfg = evarutils.config.Settings.themevars
You can get the full path by calling tostring on the object:
path = tostring( cfg ) ---> path = "C:\Litestep\Themes\Mytheme\Settings\themevars.rc"
Now, If you want to save a evar, simply index the config object:
cfg.MySettingOne = "'Hello file!'" ---> Writes "MySettingOne 'Hello file!'" to xx\Settings\themevars.rc.
To read a setting from the file use
setting = cfg:get('Setting') ---> Looks for a line like "Setting value" in the file and returns value
Delete a settings from the file:
cfg:delete('Setting') ---> Removes Setting from the file
Toggle a boolean evar in the file:
cfg:toggle('Setting') ---> interprets Setting as a boolean and inverts it.
This is designed to emulate the textedit.dll/xTextedit.dll modules in Lua. It can search through files, replace lines, insert lines, delete lines, etc. The main difference is that this module uses Lua pattern matching instead of standard regexes like textedit.dll does.
If you just want to read/write evars in a file, use the evarutils module instead!
textedit.append (filename, str)
Adds str
as a line at the end of the file
filename
.
textedit.insert (filename, pattern, str, mode)
Searches through the file filename
for lines matching
pattern
, and inserts str
there. 'There' is
determined by part of the string mode
. The mode
string can be any combination of the letters g
,
i
, b
, and G
. g
means
'global', and means that the function will be applied to every matching
line in the file. i
means case insensitive, which should be
self-explanitory. b
means before, and only applies to
insert. If b
is given, str
will be inserted
before the matching line, otherwise it will be inserted after.
G
means line global, and only applies for
textedit.replace()
.
It means that
textedit.replace()
should replace each match on a given line, instead of the first.
Returns the number of insertions made.
textedit.delete (filename, pattern, mode)
Deletes lines in filename
that match pattern
,
following the mode rules from
textedit.insert()
. Returns the
number of deletions made.
textedit.replace (filename, pattern, replace_pattern, mode)
Searches the file filename
for lines that match
pattern
and replaces pattern
with
replace_pattern
on those lines. Captures and stuff like that
from string.gsub()
are all valid here. Returns the number of
times it replaced pattern
.
textedit.find (filename, pattern, mode)
Searches filename
for pattern
, and returns
either the number of the first line that matches, or a table containing
all lines that match if the mode string contains g
.
textedit.delete_line (filename, line_num)
Deletes line number line_num
from the file
filename
. It returns true
if the line was
successfully deleted and false
otherwise.
textedit.insert_line (filename, str, line_num, before)
Inserts str
into the file filename
either
before line_num
if before
is true
,
or after line_num
if before
is
false
. Returns true
if the insertion was
successful and false
otherwise.
textedit.get_line (filename, line_num)
Returns a string containing the contents of line line_num
in
file filename
.
This module can abstracts Litestep modules as Lua objects, bringing all the greatness of OOP to scripting in litestep. It currently supports Andymon's xModules, timer-0.5 and rabidvwm, but it can be extended easily.
Basically this means that evars are mapped to class attributes and bangs are mapped to class methods, providing simple and direct access to them, without having to construct bangs manually from strings and running them with lslua.exec. The concrete wappers for modules are construced by a maze of clever metatables on access and are cached. Don't worry, there are examples below :-)
myObject = lsmodule.<module name>.<module instance name>
You always have to give a instance name, even for modules that can only exists once ( like xtray ).
traymodule= lsmodule.xtray --represents the xtray module ( which you can :unload() for example)
mytray= lsmodule.xtray.xtray --represents the xtray ( which you can :move() for example)
Now any of the wrapped module's bangs can be accessed with simple names ( "<module name><bang>" becomes "<bang>" ). If the called method has side effects (i.e. "!Move" changes the x and y position) all changed attributes are set accordingly.
myObject:<simple bang name>( <bang parameter1> , < more bangparameters>, .. )
Evars are also accessible directly through the wrapper with simple names ( "<module instance><evar>" becomes "<evar>" ).
value = myObject.<simple evar name>
"Current" Evars are used first, so myxLabel.x
tries to return $labelCurrentX$ before looking for $labelx$
Now that the modules are wrapped in lua objects it is easy ( and short!) to do pretty much anything with them.
Here are some examples:
hello_litestep.lua is a small, pointless script which demonstrates manipulating labels, using timers and callbacks.
memory.lua is a "Memory" game using xlabels and lua logic ( and it's kinda fun too ;-).
lsmodule.getname()
myNewObject = lsmodule.xlabel[lsmodule.getname()]
myNewObject.addtogroup = "some_basic_settings"
myNewObject:create()
lsmodule.callback(function, *args)
local mylabel = lsmodule.xlabel.mylabel -- this shall recieve a callback
local hellofunc = function( x ) lsmodule.message_box( "Hello from the callback\nThe event was: " .. tostring(x)" end
mylabel.onenter = lsmodule.callback( hellofunc , "onenter")
mylabel.onleave = lsmodule.callback( hellofunc , "onleave")
mylabel.onclick = lsmodule.callback( mylabel.moveby , mylabel, 10, 0) -- member functions ( normally called with a ':' ) need themself as the first argument
lsmodule.unserialize( string )
lsmodule.<module name>.<module instance name>:serialize() --> "lsmodule.module_name.module_instance" (a string)
lsmodule.unserialize("lsmodule.module_name.module_instance") --> lsmodule.<module name>.<module instance name> (a object)
lsmodule.sortby( attribute )
and lsmodule.revsortby( attribute )
myobjects = { obj1, obj2, obj2 } -- table with all object you want to sort
table.sort( myobjects, lsmodule.sortby('x') ) -- sort by x positon
table.sort( myobjects, lsmodule.revsortby('y') ) -- reverse sort by y positon
These methods only work if the litestep module "timer-0.5" or higher is loaded. "timer-1.0" is actually the first version (timer-0.1), don't use it!
lsmodule.after( timeout, function, *args)
local timer = lsmodule.after("10s", hellofunc, "a timer called!")
timer:start()
lsmodule.every( timeout, function, *args)
local timer = lsmodule.after("3s", lslua.message_box, "STOP! HAMMERTIME!")
timer:start()
Both lsmodule.every and lsmodule.after return a *new* timer object (as in lsmodule.timer.*) every time they are called. Don't forget the start the timer yourself!
<module>.version
nil
<module>:load( version )
<module>:reload()
<module>:unload()
<instance>.varfile
Changes to a instance can be automatically saved to a file if the instance has a varfile
attribute with the path to a file:
<module instance name>Varfile "X:/path/to/file"
or mylabel.varfile = evar.themedir .. "mylabelvarfile.rc"
mylabel.settext( "save me!") -- Updates 'mylabelvarfile.rc' with SomeLabeltext "Save me!"
taskbar.Alwaysontop = False -- Updates 'mylabelvarfile.rc' with xTaskbarAlwaysonTop false
<instance>:dockto(other, side, offset, steps,tim)
Instances can dock to others ( if they can move )
obj:dockto(other, side, offset, steps,tim) -- other = some object, side = "left" / "right" / top" / "bottom"
<instance>:covers(x,y)
Returns if a instance covers a specific point with
mylabel:covers(100, 200) -- true/false
If you want to use additional modules, all you have to do is create a new bangset. Imagine the iNsAnE module uses strange bangs, so you want nicer commands.
Simply create a new lua file and add something like this and even the insane module behaves just as simple as your typical xlabel. If you add a large part of a modules interface feel free to send me a patch on bitbucket or per mail ( see top of this document).
lsmodule.__bangs.INSANE = { -- lsmodule.__bangs[ module name in upper letters ]
move = function (t,x,y) t.x = x; t.y=y; lslua.exec( ("!Omgwhatastrangemovecommand %s %s %s"):format(t,x,y) )) end
}
lsmodules.insane.insane:move(10,20) -- calls `!Omgwhatastrangemovecommand insane 10 20` ( but you dont have to type that anymore )