IRC client

Read The News

Read The Docs

2.12.1 released

Posted - 01 May 2016

A new bug fix release is out, mostly minor fixes which you can see in the changelog. There is one new feature though that I am rather excited about; There are new Lua bindings for scripts thanks to @mniip. If you are a scripter I’ll go more into detail on what makes this great…


Just some quick background information: the plugin supports lua[jit] 5.1-5.3, is shipped on all platforms, and the documentation can be found here.

The first thing that really stands out when switching from Python scripts to Lua ones is the resource usage. I have around 10 scripts loaded at any given time and the python plugin alone would use around 60MB of ram, when I rewrote every script in Lua that number decreased to about 4MB. On top of that luajit is an extremely fast interpreter though scripts are rarely CPU bound.

Another useful feature is that Lua is extremely portable, this means that Windows users do not need to deal with the hassle of downloading a giant Python installer that ends up broken, requires rebooting, or is incompatable with a script. The Windows installer will include all of Lua itself which is effectively a 1MB single binary and will be installed by default so scripters can be confident users have it.

Lastly and my favorite feature is the ability to use a library called lgi. GObject-Introspection is a technology that allows information about C libraries to be used externally (usually from other languages). This means that scripts can integrate with the same libraries HexChat uses itself such as GLib and Gtk. This library is small enough that we will be bundling it on Windows which means for the first time all Windows users can have scripts that can easily modify the UI or integrate with the mainloop.

Here are some basic examples that should give you an idea of more advanced things you can do with this ability:

local lgi = require('lgi')
local GLib = lgi.require('GLib')
local Gio = lgi.require('Gio')

hexchat.register('FileTest', '1', 'Opens a file async')

--[[ A common problem in scripts is that they have to do blocking IO
     operations and in a graphical application like HexChat this means
     locking up the client. To avoid this GLib provides many asynchronous
     APIs that use threads and the mainloop to avoid blocking. This is just
     an example of opening a large file without blocking. ]]--

hexchat.hook_command('readfile', function (word, word_eol)
	if #word < 2 then
		print('Need a file name')
		return hexchat.EAT_ALL
	elseif not GLib.path_is_absolute(word[2]) then
		print('Need an absolute path')
		return hexchat.EAT_ALL

	local file = Gio.File.new_for_path(word[2])
	local original_context = hexchat.props.context

	-- This starts another thread and calls the function when it is finished
	file:load_contents_async(nil, function (file, result)
		local contents, etag, err = file:load_contents_finish(result)

		-- We need to manually reset the context as it may have changed
		if not original_context:set() then
			hexchat.find_context():set() -- front
			print('Original context lost')

		if err then
			print('Error reading file: ' .. tostring(err))
			print('File contained: ' .. #contents .. ' bytes')

	return hexchat.EAT_ALL
local lgi = require('lgi')
local Gtk = lgi.require('Gtk', '2.0')

hexchat.register('WinTile', '1', 'Tile the window left/right')

--[[ HexChat being a graphical application many times you want scripts
     to modify the UI, this is just a simple script that takes a reference
     to the main window and modifies it in a simple way (moving and resizing).
     In practice you can do any operation you would like including spawning
     new windows and modifying widgets. ]]--

hexchat.hook_command('tile', function (word, word_eol)
	if #word < 2 then
		print('Need side, left, right, or reset')
		return hexchat.EAT_ALL
	local side = word[2]:lower()
	local window = Gtk.Window(hexchat.get_info('gtkwin_ptr'))

	if side == 'reset' then
		local width, height = hexchat.prefs['gui_win_width'], hexchat.prefs['gui_win_height']
		local x, y = hexchat.prefs['gui_win_left'], hexchat.prefs['gui_win_top']

		window:resize(width, height)
		window:move(x, y)

		hexchat.command('set -quiet gui_win_save on')
	elseif side == 'left' or side == 'right' then
		local screen = window:get_screen()
		local width, height = screen:get_width(), screen:get_height()

		hexchat.command('set -quiet gui_win_save off') -- We don't want to save the temporary size

		window:resize(width / 2, height)

		if side == 'left' then
			window:move(0, 0)
			window:move(width / 2, 0)
		print('Invalid side. Need left, right, or reset')

	return hexchat.EAT_ALL

Related Posts