pcb-rnd knowledge pool


HID API policy

hid_policy by Tibor 'Igor2' Palinkas on 2018-04-14

Tags: insight, HID, API, GUI

node source



Abstract: Specify the future directions of the HID API with detailed rationale.


Prior art and current state

It is safe to say that majority of the GUI applications support only one flavor of GUI - one toolkit, one widget set, one library at any given time. There are only a few applications that aims to provide multiple, parallel GUIs long term.

Historically PCB started out supporting only one; originally it was the host computer's native GUI on the Atari ST. After the first port to UNIX, it was rewritten to support another one toolkit: Xaw. This remained the choice for a decade between the mid '90s to the mid 2000s. Then the GUI had to be replaced as xaw considered outdated and obsolete 1 . A gtk port emerged.

Fortunately that time gEDA/PCB developers decided not to switch over the code from Xaw to gtk, but first decouple the GUI from the rest of the code. The new API was named the HID API (probably for Human Interface Device ), and the gtk code was written behind this abstract, more or less GUI-independent HID API. Around the same time DJ Delorie wrote the Lesstif HID, which is a lightweight alternative to the gtk HID. It also helped a lot in cross-checking the API and make sure it is not tailored to gtk (which would let the HID API become only an extra layer of bloat with no benefit). For automatic processing a CLI based batch HID is available.

(Since exporting very often include drawing the board, the exporter API is just a subset of the HID API and export modules are accessed through the same HID API. For this article, the exporter HIDs can be ignored.)

Another decade passed, and unlike what many anticipated, the HID API did not result in more GUIs appear in gEDA/PCB. In the same time, pcb-rnd started to grow a few more GUI HIDs: separate gdk and gl support for gtk2, and a generic remote (e.g. network) HID. More HIDs are planned for the upcoming years, including an SDL2 based one.

pcb-rnd's new HID policy

There are mainly three approaches possible:

PCB in the Atari and Xaw years implemented #1. This is the simplest: GUI code mixed with application code. Pros: application features can be designed and written in a way that suits the customs of the given GUI; no need to worry about how the same feature will look or work on different hosts. Cons: if the application is to be ported to a new system, either the chosen toolkit/lib needs to be ported first or the application has to be rewritten. The latter happened in the Atari ST -> Xaw transition.

Then another rewrite happened: gEDA/PCB switched to #2 with HIDs. The HID API is fat. This leaves HIDs a lot of freedom. As a consequence the Lesstif HID does not look even similar to the GTK HID. Pros: it's a compromise still letting different HIDs look and work differently, while allowing common code to be coded only once; makes it easier to port to a new platform as it is possible to write a new HID using whatever lib/toolkit that is native there. Cons: it is hard to document different looking GUIs in one user doc; this leads to having two or more parallel documentations; it is hard to code new features and make sure they work on all HIDs (see below, under the F*H section). As a consequence in gEDA/PCB the Lesstif HID offers considerably less features than the gtk HID.

In #3, the HID API is thin. HIDs are not trying to invent how to do things, they are just small slave code that faithfully display what the core tells them to and report the user input. Pros: it is much easier to implement a new HID as only a small number of GUI features need to be implemented. It is easy to implement new features (see below, under the F*H section). Cons: all GUI HIDs look basically the same - the look & feel of the application is the application's, not the toolkit's; some users don't like if gtk applications don't look and work like other gtk applications.

As of 2.0.0, pcb-rnd is heading from #2 to #3. The reasons for this decision are described below.

The F*H problem

Let F be the number of features or application functionalities (not pure GUI-based, but conceptual features) and let H be the number of HIDs. The number of complications is C; that means the number of times the programmer needs to think over something, needs to care about something.

Setup #1 is the cheapest: C=F*H, but H==1, which means C=F. The programmer needs to care about each feature once. Things can not get cheaper than this.

Setup #2 is the most expensive: C=F*H; if gEDA/PCB has two HIDs, this means any new feature needs to be tested twice. Many features, especially those that need extra GUI, need to be implemented twice - once per HID.

Setup #3 is cheapish: C=F+H. Each feature communicates with the thin API only. The API is advanced enough that all feature-specific GUI can be explained through it. The API is generic enough that any HID implementation can display any feature GUI without having to understand the feature. Thus each feature needs to be implemented/tested only once, using a randomly chosen HID, and each HID needs to be implemented/tested only once, using a random complex feature.

Good and bad HID API

Bad HID API is complex functions that implement #2. Most of these are inherited from gEDA/PCB. For example the calibrate call is bad API: it lets each HID reinvent and reimplement the printer calibration GUI. The dynamic attribute dialog (DAD) is a good API: it implements a generic mechanism to build dialog boxes in a GUI-independent way. The DAD API needs to be implemented in each HID (implemented H times) and each feature that needs a dialog box has to care about only this one GUI API, not each HIDs (so feature dialog implemented only once per feature, F). That's how DAD results in C=F+H.

The following table summarizes which HID features are good and which ones are bad. The long term plan is to remove the bad API. These are the fields of struct pcb_hid_s in src/hid.h.

calls type description
name, description, user_context, gui, printer, exporter good HID metadata so that the core knows what the HID is capable of; no major change expected
holes_after, can_mask_clear_rats, force_compositing mixed rendering engine configuration; some of these will be removed as the rendering gets simpler and more unified
do_export(), init(), uninit(), do_exit(), parse_arguments(), usage() good main entry points; no major change expected
invalidate_*(), notify_*() good redraw notifications; some might be converted into events
set_layer_group(), end_layer(), render_burst() good render: layer compositing; no major change expected
make_gc(), destroy_gc(), set_drawing_mode(), set_color(), set_line*(), set_draw*() good render: "pen" support; no major change expected
draw_*(), fill_*(), thindraw_*() good render: object drawing; no major change expected; minor optimization (reducing number of calls) expected
calibrate bad printer calibration; to be removed and reimplemented using DAD
*_is_pressed() good may be moved into core for HID API simplification
get_coords(), get_view_size() good action GUI queries; no major change expected
set_crosshair(), point_cursor() good crosshair is expensive to draw and is drawn on any mouse movement, mouse cursor is GUI-specific; thus these should be done in the HID; no major change expected
add_timer(), stop_timer(), watch_file(), unwatch_file(), add_block_hook(), stop_block_hook() good main loop is at the HID, timers and watches need to be managed there; no major change expected
log(), logv() bad logging should be implemented centrally, using DAD
confirm_dialog(), close_confirm_dialog(), report_dialog(), prompt_for(), show_item(), progress(), drc_gui, edit_attributes() bad should be reimplemented using DAD
fileselect() good file listing and selection is platform specific; shall be extended with a format specific DAD section
attribute_dialog(), attr_*() good the DAD API; no major change expected
create_menu(), remove_menu*(), update_menu_checkbox() good menu management; no major change expected
propedit_*() bad should be reimplemented as DAD in the propedit plugin

What a HID will need to do

After the transition to #3, a new HID will mainly need to:

What a HID must not do:

What a feature will need to do

After the transition to #3, a new feature will mainly need to:

What a feature must not do:

Expected results

After the transition to #3, the following benefits are expected:


1. outdated and obsolete not in technical sense but in "user experience" sense, whatever that means. I.e. old, mid '90s versions of of PCB still can be compiled in modern Linux desktops, using current versions of the Xaw toolkit; as of 2018 this works out of the box on a Debian host, without having to manually compile Xaw. In the same time, compiling a qt3 application is much more problematic, while qt3 was released around 2001. For the rest of this article, it is very important to note the difference between stable/mature kind of old (e.g. Xaw) and hard-to-use kind of old (e.g. qt3), and this is rarely determined by the age of the lib.

2. the simplicity of the Lesstif main window should not be confused with the HID implementation. The simple, stripped down version, called full screen is already available as a runtime option in the gtk HID. The Lesstif HID should make the same rich controls available and the same full screen function should switch between the simplistif Lesstif and the rich Lesstif main window.

3. part of the PCB API was actions implemented by the HIDs. It looked like a good idea: instead of inventing a new API for e.g. zoom, just let each HID implement its own Zoom() action. However, this does not make implementing a HID simpler, but induced a lot of code and doc duplication. Plus it lets HIDs diverge on user-visible API too easily. The new policy is that GUI HIDs shall not have local actions. GUI actions will be centralized in a plugin all GUI HIDs will depend on. Between the plugin and the HID there will be a small number of custom API.