Ada Binding to "Wide" ncurses
If you're not interested in Text User Interfaces, feel free to skip this post.
Like I mentioned in a previous post, I think TUIs make a lot of sense for "admin interfaces" for cloud and on-prem systems. It's also possible their use will be forced for end-users once again if RAM prices keep going up, but we'll see. Its technical merits are arguable, but the curses library for TUIs is part of the Single UNIX Spec and I prefer to follow standards unless you have a radically better idea (similar to the use of Ada).
Previous recommendations, including whining that ncursesada wouldn't work for me
Home of the Single UNIX Spec, see the "Publications Library" link
However, something has bothered me for some time, and I've seen a few others report the same problem. The maintained Ada bindings to ncurses are only for "narrow" (i.e. 8-bit) locales. I'm a bit of a Japanophile, but a different project that exposes the "wide" API to support locales like CJK won't compile and from the defect database seems to be unmaintained. I have no intention of forking/starting a whole project, so was happy to find a lazy way to fix the problem.
Official bindings. Note the last point in "General Remarks".
Someone who dropped back to C for the UI layer
Moribund project that might have done the job, but didn't
It's possible to just add a few new bindings to the existing ncursesada library, from your own code. Specifically, for Ubuntu 26 (and similar):
- Write new code for wide bindings in a child of the official package (and link this into your executable obviously).
- Copy the system-installed gprbuild file and modify it to link against the wide variant of the C library.
FFI Bindings
This was an opportunity for me to learn how to use the Interfaces.C package from the standard library. As expected, it's complete but note that it works at the linker level so preprocessor symbols aren't available. This is only a minor limitation. For an experiment, I exposed one new curses function, `waddnwstr`, to display a wide string. There are probably only a few more required for a complete interface, e.g. display character, input string & character, wide version of the line-drawing support.
The standard Interfaces.C package
These are the packages we use:
with Interfaces.C; use Interfaces.C;
with Terminal_Interface.Curses; use Terminal_Interface.Curses;
with Terminal_Interface.Curses.Aux; use Terminal_Interface.Curses.Aux;
This is the new public interface we're implementing. Terminal_Interface.Curses is the official binding package.
package body Terminal_Interface.Curses.Ext is
procedure Add (Win : Window := Standard_Window;
Str : Wide_String;
Len : Integer := -1)
is
This is the private implementation, including an instance of what seems to be a common pattern where there is a thin wrapper around the C interface ("Waddnwstr" below) and a slightly nicer one using Ada types ("Add"). It was adapted from the narrow implementation in the official bindings and only required minor changes.
function Waddnwstr (Win : Window;
Str : wchar_array;
Len : int := -1) return int
with Import => True,
Convention => C,
External_Name => "waddnwstr";
Txt : wchar_array (0 .. Str'Length);
Length : size_t;
begin
To_C (Str, Txt, Length);
if Waddnwstr (Win, Txt, int (Len)) = Curses_Err then
raise Curses_Exception;
end if;
end Add;
end Terminal_Interface.Curses.Ext;
You just need to put this into a file terminal_interface-curses-ext.adb in your project's source directory.
The required .ads file is trivially derivable from the .adb file and left as an exercise for the reader ;-).
Gprbuild Support
This was even easier. I just copied /usr/share/gpr/ncursesada.gpr to the current dir and modified all strings like "-lncurses" to "-lncursesw" (and although I don't use them, similar for form, menu & panel).
Then you can "use" this new gpr file from your project's gpr file and everything *seems to* link correctly. At least, my trivial test worked.
Conclusion
After trying a few different ways, I think I may have found the laziest way to write a Japanese TUI in Ada. Maybe not many other people will want to scratch that itch, but it's out there for anyone who does.
Postscript
Looking back at some of the Ada libraries I recommended in the previous post mentioned at the top of this one, I think I'd change a few other things.
- Rather than GNAT.Sockets, using the server design in "Simple Components" may be better to force you to do things like fragment reassembly correctly.
- For security, we need a way to send higher-priority logs to another system. The best solution I found for this is to use the "alog" package (e.g. on Ubuntu "libalog-dev"). This can interface to the standard UNIX syslog facility.
- Instead of an ndbm-like interface I'm interested in emulating an object database, again using "Simple Components" over SQLite.
Simple Components, a library I'm currently studying
Alog library
Back to my gemlog