This documentation describes implementation of Curses User Interface. It's meant to be used by programmers who want to either extend Curses client or fix some bugs in it.
Implementation of Curses client is split into a few files. Each file contains functions of specific functionality and it may help you to orient in the code if you have basic idea of what is in each file:
contains implementation of communication with libsurprise and implementation of operations above libsurprise data structures.
contains implementation of generic environment functions - eg. windows, general components and such.
contains initialization functions.
contains main key-handling loop and implementation of all functions needed for proper work of main screen (eg. device lists, partition lists, information window).
contains functions for printing messages for user and error printing.
contains implementation of undo. Note that this is in fact never used but it is there and in future can be debugged and turned on.
contains things somewhere among envir.c
, main.c
and command.c
.
Most functions are implementing communication with user before we have enough information
to call function from command.c
.
contains generic functions which didn't fit anywhere else.
Curses client has one layer called environment built above the curses and panel libraries.
This layer implemented mainly in envir.c
provides basic components as buttons, edit
fields, list components and so on.
Basic element of this user interface is called environment window (see definition of
env_window_t
in envir.h
). Each window is implemented as a panel of libpanel.
Each window has specified handlers for function
keys, special keys (like tab
, enter
, escape
and others) and also handler
for other keys. Window also contains labels of F-keys which should be shown when window
is active. Each window contains list of components it contains and pointer to active
component.
Component (see definition of env_component_t
in envir.c
) contains some information
about its position in window, proper environment window and also pointer to component
subwindow (each component is in subwindow of parent environment window so that we don't
have to care about line wrapping and so on). Each instance of component has its id. This
id is for use of above layers and environment layer doesn't care about it. Component
can have same set of key handlers as environment window. After the generic part of component
structure is type-specific part.
For each component type there can be also specified functions which should be called
when component is deleted (array free_component
), when component is activated
(array activate_component
) or when component is deactivated (array deactivate_component
).
Currently these component types are implemented:
This component (see definition of struct comp_edit
) allows editing of
some string. You can also specify type of string it should contain. Currently
there are only three types - number, string, unknown. When type is number
component will let user leave it only when it contains number. When type is
string there are no restrictions on contents. When type is unknown user
specified checker is called to verify contents of string.
This component (struct comp_selection
) is shown as list of entries from
which user should choose one.
Choice component (struct comp_choice
) is component which is similar
to selection component. When inactive actual choice is shown. When component is
activated and user press Enter window with selection component is shown and
user can choose other entry. Actually structure of choice component contains only
pointers to window with proper selection component and to current choice. Window with
selection contains special component struct comp_bindchoice
which is not
shown and which contains only pointer back to proper struct comp_choice
.
This component (struct comp_text
) is used for displaying plain text.
This component (struct comp_button
) implements ordinary button.
This component (struct comp_progressbar
) implements progress bar.
Edit component is implemented by following functions:
called when component is activated. It only makes cursor visible.
called when component is deactivated. It calls checker to check string contents and if checker agrees with deactivation cursor is made invisible.
called when component is deleted only makes cursor invisible.
called when some unhandled key is pressed. Prints it to edited string.
moves cursor in string to given position.
moves cursor to the left.
moves cursor to the right.
moves cursor to the beginning of line.
moves cursor to the end of line.
deletes character on given position.
deletes character under cursor.
deletes character before cursor.
creates new edit component.
Selection component is implemented by these functions:
creates new selection component. Note that choices are copied to allocated memory so that after component is created caller can free array given as a parameter. Callback specified as a parameter is called whenever any change of active choice happens.
changes active choice to given one.
moves active choice up.
moves active choice down.
moves active choice one page up.
moves active choice one page down.
moves to first choice.
moves to last choice.
is called when component is activated. It draws active choice in inverse color.
is called when component is deactivated. It redraws active choice in normal color.
Choice component is implemented by these functions:
called on component activation/deactivation. Only redraws component in inverse/normal color.
is called when user chooses new choice. It hides window with choices and activates previous window (this activation will also activate current component and so new choice will be shown in our component). Then next component in window is activated.
is called when user wants to change choice. Function shows window with selection and let user choose.
is called when component should be destroyed. It closes window with selection.
creates new choice component.
Text component provides following functions:
move cursor in text component.
print character to component window (if it is control character like backspace, end of line or so it moves cursor appropriately).
print string to text component window. This function is implemented as
calling ct_addch()
at each character of the string.
move cursor to given place and print string (implemented in envir.h
).
print formatted string to text component. It first formats string through
sprintf()
and then calls ct_addstr()
.
clears text component.
creates new text component.
Button component has these functions:
It draws button on given position.
with obvious functionality.
creates new button component.
Progressbar component has these functions:
creates new component with progress bar.
updates viewed progress.
This function creates new environment window. You can specify whether window
should be centered (NEW_CENTER_FL
), have a border (NEW_BORDER_FL
), have a
nonstandard attributes set (NEW_ATTR_FL
) - first variable argument is taken as an
attribute number, have a title (NEW_TITLE_FL
) - in that case next variable argument is
taken as a title.
This function returns curses window corresponding to given environment window.
Converts key code of special key returned by curses to our key numbering.
This function destroys given environment window and all components in it.
This function closes active environment window and makes active window under it.
This function will find component with give id in given window.
This function activates given window (puts it to the beginning of window list), draws labels for F-keys and activates component active in the window.
refreshes window with given component in case component isn't hidden.
just refresh all components in the window.
This macro returns active window.
This function allocates space for component with given amount of memory reserved for type-specific part of component and initializes generic component part of structure (including creating component window).
destroys component (calls specified callback for freeing).
activates component (calls specifies callback for activating). It includes redrawing of F-key labels appropriately to component F-key handlers.
deactivates component (deletes F-key labels induced by deactivated component).
activates next component in the current window.
activates previous component in the current window.
makes component hidden (currently unused).
makes component visible (currently unused).
This macro returns type-specific part of component typed to given type.
Checks whether active component in given window has given id.
places hardware cursor the place of software cursor in active window in active component.
makes all changes buffered by curses visible, places cursor on proper place.
Key handling system of Curses client recognizes three types of keys:
These are function keys from F1 to F20 (keys with higher number can be accessed by pressing Shift and F key).
These are keys like Enter, Tab, Backspace and others
(see definition of enum env_special_keys_e
in envir.h
for complete list).
These are keys which are neither function nor special.
For each function key and each special key can be specified key handler. For other keys there is one handler for all keys. Each window and each component has its own set of handlers. When key is pressed handling function looks whether active component in active window has this key handled. If so, handler is called and handling is finished. If component doesn't have handler for pressed key handler of window is called (if present). In case neither window has key handled key is ignored and client beeps.
Curses client keeps list (curs_errlist
) of errors to report. When some function returns
any errors they are appended to the error list. In main loop if we find that there are
errors on error list we show them. Error list is handled by three functions:
This function appends given list of errors to clients error list.
This function gets first error from error list and displays it through
showerror()
function.
This function shows all errors on the list. It doesn't use general key handling function and is used generally at times user interface is not properly initialized.
Reporting does function showmessage()
which shows given text in message window
(it just creates window of proper size and text component with given string in it).
This function is also used by showerror()
function which just makes string from
struct user_error
and prints it through showmessage()
.
This section describes various utility functions used in Curses client.
This function is called through die()
macro. It will print error message
along with file name and line number and exits the client.
Error handled version of malloc()
. Exits when allocation fails.
Safe version realloc()
. Exits when reallocation fails.
Safe version (macro) of alloca
. Exits when allocation fails.
strncpy() which guarantees nullterminated result.
strncat() which guarantees nullterminated result.
Gets last component of file name.
This function gets text and maximal possible width and returns number of columns really used by printed text and number of lines used by the text.
This function gets struct user_parameter
and returns its printable form.
This function returns number of colums needed by given parameter for showing and editing.
Counts maximum of maxparamvallen()
over given list of parameters.
Returns maximal length of name of parameter.
This macro returns whether given parameter can be changed by user.
Returns number of parameters on list which can be changed.
Returns index of parameter with given id in an array of parameters.
Returns index of filesystem of given type in diskinfo
array.
Macros for rounding partition start, end and length.
Curses client has simple system for undoing operations and automatic verification of modifications. Currently this functionality is not accessible to the users and system even isn't debugged very much but it's there and it shouldn't be much work to finish it.
Undo system has a stack (undo_buf
- it is initialized in undo_init()
)
in which it stores all planned state of disks (that means all array of
struct disk_info
). On the stack operate these functions:
This function gets entry on stack and frees all struct disk_info
attached to it. So it in fact frees entry in undo buffer.
This function gets current planned state and duplicates it to undo buffer.
This function frees planned state in given struct final
and puts
state from given undo buffer on its place.
This function frees entry in undo stack if it is full and then duplicates current state to undo buffer and updates top of the stack.
This function replaces current planned state with the one from top of the stack and updates the top of the stack.
In automatic verification it is needed to restore previous state if last step was not correct.
For that case special undo buffer saved_state
is created. Before any action is done
current state is saved to this buffer then action is done. If verification succeeds state
stored in saved_state
is moved to undo stack. If verification fails stored state
is restored. For automatic verification are used these functions:
This function duplicates current state to saved_state
buffer.
This function restores state from saved_state
.
This function moves state saved in saved_state
to undo
stack (freeing oldest entry in case stack is full).
Curses client has also support of a few options. These options can be set from
command line when running client or from dialog window (choice Client settings
in menu). Values of options are stored in global variables with prefix cco_
.
Currently supported options are:
When this option is set length and partition start are automagically aligned to values supplied by libsurprise.
When this option is set verification is performed after each step and when it fails previous state is restored.
This option specifies minimal partition size.
This option specifies whether messages from libsurprise should be printed to stderr.
This option specifies whether warning message should be printed when starting client.
This option specifies whether length should be printed in cleverly chosen units in dialog boxes.
Main function of all Curses client is function cursesclient()
. It first calls init_cursclient()
for initialization and then loops in a key processing loop (see
key processing
for details about key processing).
Initialization has a few steps. At first libsurprise error system is initialized and
warning message printed. When warning is accepted screen is initialized through function
init_screen()
(it configures curses flags and so on). Then information about
disk subsystem is loaded by function curs_get_info()
(this function is implemented
in command.c
). Then undo system is initialized by undo_init()
, main window
and window with original states of disks is created including lists of devices and partitions
and information components.
Basic information about disk subsystem (struct resources
- see
resources description) is kept in global variable diskinfo
, information
about current and planned state (struct final
) in variable diskstate
. Formatted
information about partitions (and also free space between partitions) along with
pointer to corresponding array of struct disk_info
is kept in
array pinfo
(see definition of struct complete_part_info
in main.h
).
In one element of pinfo
is kept information about planned state of partitions and
in the other one is kept information about original state of partitions.
Array i_vinfo
in pinfo
is built for each disk from struct disk_info
by function create_partlist()
.
This function just copies information about partitions from given structure, sorts partition
by their beginning and inserts entries for free space if free space is large enough to be
of any interrest.
Note that most handler functions are just wrappers for functions which does the thing.
It's not because I like so much structured programming but mainly because we need
to call those functions from menu and when key is pressed. Also most functions called
from handlers does just some basic sanity checks and then pass values to functions
in usercomm.c
which let user edit parameters and confirm the action. Refreshing
of information shown on the screen is responsibily of functions in usercomm.c
and
only if no such function is called function in main.c
has to refresh the screen
(this is because we usually need to leave the key handler and ask user for more information
and so we end up in some handler in usercomm.c
).
There are three menus with functions in Curses client. When F9 is pressed menu appropriate to active component is shown. So we have menu for disk selection and menu for partition selection. There are also similar menus for window with original states of disks. Implementation of function menu consists of a few handlers:
Called whenever user closes menu. Function closes window with menu.
Called when user chooses function in menu for disk selection. Function appropriate to chosen action is called (as if F key was pressed).
Called when user chooses function in menu for partition selection. Function appropriate to chosen action is called (as if F key was pressed).
Similar to main_menu_disk_accept()
.
Similar to main_menu_part_accept()
.
These are handlers of keys in main window:
Called when menu with functions should be shown. It just creates menu appropriate to active component.
Called through main_f_quit()
when user wants to quit client. Sets
state variable curs_state
to CST_QUIT
state.
Wrapper for main_do_quit()
.
Called through main_f_gpars()
when user wants to edit global
parameters. Just calls ask_global_pars()
.
Wrapper for main_do_gpars()
.
Called through main_f_dpars()
when disk parameters should be edited.
Just calls ask_disk_exppars()
.
Wrapper for main_do_dpars()
.
Called when user wants to commit changes. Calls curs_commit()
to
commit changes, displays message appropriate to result of commit and calls recreate_all_infos()
to recreate all informations appropriately to new state of disk.
Wrapper for main_do_commit().
Called when user wants to verify planned state. Calls curs_verify()
and displays message about success if verification was ok.
Wrapper for main_do_verify().
Called on creation of new partition (in case disk selection was active).
It finds first free space on current disk asks user for confirmation through ask_new_part()
.
Wrapper for main_do_new_part().
Called when user wants to change client settings. It just calls
ask_settings()
.
Activates next component in main window. We can't use generic
activation functions from envir.c
because we only want to switch between disk and partition
selection and we want to update information window appropriately to current active component.
Activates previous component in main window (see above for sense of this function).
Activates either window with original state or with planned state (in short the one which is currently inactive).
Wrapper for main_do_other_window()
. It just deactivates active
component and calls main_do_other_window()
.
Following functions are operating on disk selection:
Creates selection component with devices from list of devices in diskinfo
.
Redraws base information about partitions shown next to partition selection.
Prints extended information about disk to information window.
Updates base information about partition, information about disk and partition selection appropriately to new disk (This function is used as callback of device selection component).
This function deletes all partition selection and disk selection components and creates new with updated data. It tries to preserve position of active entry in selections. This function is used after commit when anything may change.
Following functions are operation on partition selection:
This function shows all information about partition and filesystem on it in information window. It formats filesystem parameters that way that in one column should be parameter names and in second column parameter values.
This callback called from partition selection component updates information about partition in information window appropriately to new active partition.
This function creates selection component from struct complete_part_info
corresponding to given window.
This function creates new array i_vinfo
from struct disk_info
and recreates component with list of partitions for given disk.
This function updates (through functions show_disk_info()
or
show_part_info()
) contents of information window according to active disk or
partition.
This function calls show_base_disk_info()
on current
partition.
This function calls recreate_part_sel()
on given disk
or on all disks if -1 is passed as disk number.
This function recreates all information about disk and redisplays all information shown about it (recreate list of partitions and selection component, redisplays disk or partition information and base partition information if shown).
Now follows key handling functions for partition selection:
Called (through psel_del_part()
) when user wants to delete partition.
Calls curs_del_part()
on current partition and then recreates all information through disk_changed()
.
Wrapper for psel_do_del_part()
.
Called (through psel_new_part()
) when user wants to create partition.
Calls ask_new_part()
for user confirmation and partition creation.
Wrapper for psel_do_new_part()
.
Called (through psel_conv_part()
) on partition conversion. Real
functionality is in called ask_conv_part()
.
Wrapper for psel_do_conv_part()
.
Called (through psel_copy_part()
) when data from partition should be copied.
Just calls ask_copy_part()
.
Wrapper for psel_do_copy_part()
.
Called (through psel_moveresize_part()
) when partition should
be moved or resized. Just calls ask_moveresize_part()
.
Wrapper for psel_do_moveresize_part()
.
In the window with original state are following special key handlers:
Called (through main_orig_f_dpars()
) when imported parameters
of disk should be changed. Just calls ask_disk_imppars()
.
Wrapper for main_orig_do_dpars()
.
Shows menu with available operations.
Called (through psel_fs_ipars
) when imported parameters should be changed.
Just calls ask_import_fs_pars()
.
Wrapper for psel_do_fs_ipars()
.
For communication with user were created and implemented two compound components. They are
This component (created through newenvdialog()
) is a
window with two buttons (OK and Cancel). On creation caller specify callbacks
which should be called when dialog is accepted (either through pressing F2 or
through pushing OK button) or canceled (F10, Cancel button or Escape).
This component (created through newenvselection()
)
is a window with selection component. Caller can specify callbacks to be called
on accept (F2 or Enter) and on cancel (F10 or Escape).
There are also generic helper functions for filesystem parameter editing:
This is callback function used in edit components for checking contents of component on deactivation. It checks whether component contains number in given interval.
Callback used for checking whether string in edit component matches given regular expression.
Callback used for checking whether given string in edit component is correct length specifier (ie. it's number of sectors or size in KB, MB or GB).
Function creates on given position component used for editing given parameter (choice component for discreete numbers, strings and boolean values, edit component for interval and string). Created component has assigned id corresponding to id of parameter so that we know which component belongs to which parameter.
This function counts size of dialog needed for parameters and then
creates (using create_par_component()
) components for all changable parameters. This
function also takes argument whether given parameters are imported or exported because
changability of parameter depends on this.
This function updates given parameter appropriately to value stored in the given component.
This function gets all components with parameters in given window and creates list of parameters with appropriate values from them.
This function gets list of parameters and updates values of parameters whose components are present in given window.
First function called on partition creation is ask_new_part()
. This function
creates dialog window with partition number, partition start, partition length and
filesystem type for new partition. When user cancels the creation, callback np_cancel
is called which just closes the dialog. On accept callback np_create_accept
is called.
This function gathers data from components, stores them in global variables (I don't
like this solution much but I don't know cleaner and reasonably easy), closes
dialog and creates new dialog with filesystem parameters. When parameters are
eddited and accepted callback np_fspars_accept()
is called. It gathers the filesystem
parameters, closes dialog and calls curs_new_part()
to create new partition.
First converting function is ask_conv_part()
. This function creates selection window
in which user can choose destination filesystem type. When user chooses filesystem type
(canceling leads to calling cv_cancel()
which just closes the selection window)
cv_fstype_accept()
is called. It stores filesystem type in global variable and
closes the selection window. If destination filesystem type is same as the current one
window with current filesystem parameters is created and user can edit them. If destination
fstype differ window with filesystem parameters with default values is created (ask_fs_pars()
does the work). Function cv_fspars_mod_accept()
is called when user modified and accepted
parameters for current filesystem (fstype didn't change). The function stores
current planned state of disks (see
undo system for more
information), calls gather_fs_pars_modify()
to alter parameters, closes window
and plans conversion through curs_conv_part
. If everything succeeds, disk_changed()
is called to update viewed information. cv_fspars_new_accept()
called when user modified
parameters and accepted conversion to other fstype works almost the same way as cv_fspars_modify_accept()
.
It only uses gather_fs_pars_create()
for gathering modified parameters.
Copying of partition data starts in function ask_copy_part()
. This function creates dialog
with components for editing of destination device and partition number. When user edits values,
cp_pars_accept()
is called (on cancel is called cp_pars_cancel()
which closes the dialog).
cp_pars_accept()
extracts user specified values from dialog and closes it. Then curs_copy_part()
is called. This function checks whether destination partition exists and whether source and destination
partitions are different. If all checks succeed structure is updated to reflect copying of data.
Moving and resizing of partition starts in function ask_moveresize_part()
. This function
creates dialog with components for editing of device, partition number, partition start and length.
When user modifies values he/she wants, callback mr_pars_accept()
is called (on cancel
mr_pars_cancel()
which just closes the dialog is called). mr_pars_accept
gathers
new values from components in dialog, closes it and calls curs_moveresize_part()
to
alter proper structures. When operation succeeds viewed information for source and evetually
destination disk are updated (this has sence in case we would have more main windows).
Editing of global parameters is quite simple. In the beginning function ask_global_pars()
is called. This function creates dialog with all global parameters (function ask_fs_pars()
is used). On accept callback gp_pars_accept
is called. It saves current planned state (see
undo system for more information), alters global parameters by gather_fs_pars_modify()
,
closes dialog and calls curs_set_pars()
to set global parameters.
Setting of disk parameters starts in function ask_disk_imppars()
or ask_disk_exppars
depending on type of parameters to edit. Called function creates dialog with asked parameter set.
When user wanted to edit imported parameters callback idp_pars_accept()
is filled in
as a callback for accept otherwise edp_pars_accept()
is used. idp_pars_accept()
just stores current state to undo buffer, alter update imported parameters in
gather_fs_pars_modify()
and closes environment window. edp_pars_accept()
does the same as idp_pars_accept()
but it also calls curs_set_pars()
to
verify correctness of set parameters.
Parameter editing starts in ask_import_fs_pars()
. This function creates dialog (by calling
ask_fs_pars()
) with filesystem imported parameters. When parameters are modified and
dialog accepted ifs_pars_accept()
is called. It stores current state in undo buffer,
alter current parameters, closes the dialog and updates info panels.
Editing of Curses Client parameters starts in function ask_settings()
. This function
copies data from global variables (those with cco_
prefix) to parameter
structures used for editing and then calls ask_fs_pars()
to create dialog
with proper components. When parameters are edited and dialog accepted
set_pars_accept()
is called. This function just calls
gather_fs_pars_modify()
to copy data from components to parameter
structures then closes the dialog and copies data from parameter structures back to
global variables. In case dialog is canceled pars_cancel()
is called to close
dialog window.
When commiting changes or verifying Curses client shows progress indicator. It is handled by following functions:
This function creates window with progress bar.
This function closes window with progress bar.
This is callback called from libsurprise when progress bar should change. It redraws progress bar according to given progress.
Because it's not wise to stop libsurprise in the middle of converting when converting
special handler abort_handler()
is attached to SIGINT
. When SIGINT
is caught
global variable abort_requested
is set and function show_progress()
then
returns to libsurprise that abort was requested.
In the beginning and after commit curs_get_info()
is called to get
information about disk subsystem from libsurprise. This function first
calls get_disk_info()
which is in fact wrapper to get_resources()
of
libsurprise. Then create_api_structs()
is called to create struct
final
. create_api_structs()
calls get_final()
to create desired
struct final
. When everything is ok, add_missing_exp_pars()
is called
for each partition to add missing changable exported parameters to the
partition.
Verification and commit are quite similar operations. At first progress indicator is
created and SIGINT
handler installed. Then commit_disks()
from libsurprise
is called. After it returns SIGINT
handler is restored and progress bar hidden.
In case there were any errors they are added to error list (by add_report_errors()
).
In case we are commiting changes we free all information about disks and partitions and
then call curs_get_info()
to get new disk state from libsurprise.
All operations on struct final
at first backup current state with save_state()
then do the operation (function has usually prefix do_
). Then if verification of
every step is required curs_verify()
is called to verify the step. If verification
succeeds saved state is moved to undo buffer. If verification fails old state is restored
and error returned.
Operations on structure are following:
This does function do_curs_delete_part()
which frees data
structures associated with partition and then puts some other partition on its place
in structures and shrinks the array of partitions.
This is done by function do_curs_new_part()
. This function
first checks whether wanted partition number is used (if so error is returned). Then
aligns partition start and lenght (if option cco_align
is set) and checks
whether resulting values are reasonable (ie. positive, before end of disk and
so). If all checks are ok space for new partition is allocated and structure
filled in.
This is implemented in function curs_conv_part()
(this
is a bit exceptional function as current state is already saved when this function is
called and so this function doesn't have usual wrapper). If we are converting
to other filesystem current filesystem parameters are freed and filesystem type
is altered. Otherwise we have nothing to do (parameters were already altered).
This is implemented in function do_curs_moveresize_part()
.
Function first checks whether we are moving to other disk and in that case moves
structure for partition to proper disk. Then if partition number or disk changed it checks
whether new partition number is unused and if so it changes it in partition structure.
Then if partition start changed we align it (in case option cco_align
is set) update
structure of partition appropriately. Also if partition length changed we align the length
(if option cco_align
is set) and set length in partition structure.
When global or disk exported parameters are set curs_set_pars()
is called. It just calls curs_verify()
if verification after each step is required
and eventually restores previous state.