2.4. YaST Module Files

We have shown how really easy is to create a new YaST module, how to install it and run. Now you should learn more about source files inside the project: what they are good for, what they contain and what they handle.

2.4.1. Project Files & YaST Layers

This picture shows the disposal of project files to the particular layers of YaST. This is how the new YaST modules is supposed to be divided into files and corresponds with final version. For instance, the file sshd.scr doesn't exist in a newly-generated template.

2.4.2. Internal Workflow Explanation

This part is rather complex and not so important - you can skip it if you want.

What is done between calling the /sbin/yast2 sshd command and opening the UI up?

  1. Script /sbin/yast2 defines internal variables, such as LC_CTYPE. Then it checks whether you have Qt or ncurses available and calls the /usr/lib/YaST2/bin/y2base binary.

  2. Binary /usr/lib/YaST2/bin/y2base is called with "sshd qt" or "sshd ncurses" as two program arguments. The y2base checks whether there is any sshd.ycp client in the /usr/share/YaST2/clients/ directory and executes it.

  3. The sshd.ycp imports some binary modules such as Progress, Report or CommandLine, defines the command line options and includes the wizards.ycp file.

  4. The wizards.ycp file includes the dialogs.ycp file with defined dialogs and complex.ycp file with defined complex dialogs functions and then dialog sequences are defined.

  5. After closing the UI, the control is returned back to the sshd.ycp client which finishes the operation and exits.

2.4.3. Familiarizing with Source Files

You will find source files under the sshd/src/ path. All those files are copied into the /usr/share/YaST2/ path during the make install procedure. Let's clarify what is the purpose of these files on some simple examples. Files listed here are simplified, most of the content is replaced with a "..." mark. Important parts are emphasized. Files are sorted in a logical order.

2.4.3.1. File sshd.desktop

Application definitions for KDE, AutoYaST, YaST Control Center...

[Desktop Entry]
Type=Application
...
X-SuSE-YaST-Group=Misc
...
Icon=yast-sshd
Exec=/sbin/yast2 sshd
...
Name=Sshd
GenericName=sshd
...

These settings are used by another applications than the YaST SSHD Configuration itself. They define the module behavior and identification. See the YaST Desktop Files document for more details.

2.4.3.2. File sshd.ycp

A basic application client also with a command line interface definition. Command line support will be described in another tutorial.

Clients are stored under the /usr/share/YaST2/clients/ path

/**
 * File: clients/sshd.ycp
 ...
 */

{

textdomain "sshd";

/* The main () */
y2milestone ("----------------------------------------");
y2milestone ("Sshd module started");

import "Progress";
import "Report";
import "Summary";

import "CommandLine";
include "sshd/wizards.ycp";

map cmdline_description = $[
    ...
    "guihandler" : SshdSequence,
    ...
];

/* is this proposal or not? */
boolean propose = false;
...

/* main ui function */
any ret = nil;

if (propose) ret = SshdAutoSequence();
else ret = CommandLine::Run(cmdline_description);
y2debug("ret=%1", ret);

/* Finish */
y2milestone("Sshd module finished");
y2milestone("----------------------------------------");

return ret;

/* EOF */
}

This client is called by YaST binary, it includes the wizard.ycp file with defined sequences and calls the CommandLine::Run() wich, in our case, calls the SshdSequence() workflow from wizard.ycp.

[Note]Note

We have also other clients <module-name>_auto.ycp for the AutoYaST and <module-name>_proposal.ycp for the installation proposal but they aren't needed for this tutorial.

2.4.3.3. File wizards.ycp

Contains dialogs sequence definitions that are used by clients.

Wizards are stored under the /usr/share/YaST2/include/<module-name>/ path.

/**
 * File: include/sshd/wizards.ycp
 ...
 */

{

textdomain "sshd";

import "Sequencer";
import "Wizard";

include "sshd/complex.ycp";
include "sshd/dialogs.ycp";

/**
 * Add a configuration of sshd
 * @return sequence result
 */
any AddSequence() {
    ...
}

/**
 * Main workflow of the sshd configuration
 * @return sequence result
 */
any MainSequence() {

    map aliases = $[
	"summary"	:   ``( SummaryDialog() ),
	"overview"	:   ``( OverviewDialog() ),
	"add"		: [ ``( AddSequence() ), true ],
	"edit"		: [ ``( AddSequence() ), true ]
    ];

    map sequence = $[
	"ws_start" : "summary",
	"summary" : $[
	    `abort	: `abort,
	    `next	: `next,
	    `overview	: "overview",
	],
	"overview" : $[
	    `abort	: `abort,
	    `next	: `next,
	    `add	: "add",
	    `edit	: "edit",
	],
	"add" : $[
	    `abort	: `abort,
	    `next	: "overview",
	],
	"edit" : $[
	    `abort	: `abort,
	    `next	: "overview",
	]
    ];

    any ret = Sequencer::Run(aliases, sequence);

    return ret;
}

/**
 * Whole configuration of sshd
 * @return sequence result
 */
any SshdSequence() {

    map aliases = $[
	"read"	: [ ``( ReadDialog() ), true ],
	"main"	:   ``( MainSequence() ),
	"write"	: [ ``( WriteDialog() ), true ]
    ];

    map sequence = $[
	"ws_start" : "read",
	"read" : $[
	    `abort	: `abort,
	    `next	: "main"
	],
	"main" : $[
	    `abort	: `abort,
	    `next	: "write"
	],
	"write" : $[
	    `abort	: `abort,
	    `next	: `next
	]
    ];

    Wizard::CreateDialog();

    any ret = Sequencer::Run(aliases, sequence);

    UI::CloseDialog();
    return ret;
}

/**
 * Whole configuration of sshd but without reading and writing.
 * For use with autoinstallation.
 * @return sequence result
 */
any SshdAutoSequence() {
    ...
}

/* EOF */
}

First of all, complex.ycp and dialogs.ycp are included because they define dialogs that are used in dialog sequences.

After that, aliases to these dialogs are defined in aliases map. Then sequences pointing to these aliases are defined in a sequence map. Sequence handlers defines the relationship between event returned by a dialog function and action called by sequencer.

At the end, the SshdSequence() is called from the sshd.ycp client.

[Important]Important

Never define the `back event, dialog wizard handles it by itself.

[Note]Note

Please, take note, that sequences can call another ones: SshdSequence() calls MainSequence() and that one calls AddSequence().

Sequence called from a client should contain Wizard::CreateDialog() and UI::CloseDialog() because user should see the UI as soon as possible. Additionally Wizard::CreateDialog() creates the classic Wizard window with help text on the left side, space for dialogs on the other one and Back, Abort and Next buttons.

Screenshot of the Wizard window:

2.4.3.4. File dialogs.ycp

Dialogs definitions and their simple handling.

Dialogs are stored under the /usr/share/YaST2/include/<module-name>/ path

/**
 * File: include/sshd/dialogs.ycp
 ...
 */

{

textdomain "sshd";

import "Label";
import "Wizard";
import "Sshd";

include "sshd/helps.ycp";

/**
 * Configure1 dialog
 * @return dialog result
 */
any Configure1Dialog () {

    /* Sshd configure1 dialog caption */
    string caption = _("Sshd Configuration");

    /* Sshd configure1 dialog contents */
    term contents = `Label (_("First part of configuration of sshd"));

    Wizard::SetContentsButtons(caption, contents, HELPS["c1"]:"",
	    Label::BackButton(), Label::NextButton());

    any ret = nil;
    while (true) {

	ret = UI::UserInput();

	/* abort? */
	if(ret == `abort || ret == `cancel) {
	    if(ReallyAbort()) break;
	    else continue;
	}
        else if(ret == `next || ret == `back) {
            break;
        }
        else {
            y2error("unexpected retcode: %1", ret);
            continue;
        }
    }

    return ret;
}

/**
 * Configure2 dialog
 * @return dialog result
 */
any Configure2Dialog () {
    ...
}

/* EOF */
}

This file defines two dialog functions Configure1Dialog() and Configure2Dialog(). These functions set the behavior by calling the Wizard::SetContentsButtons() function. We can use this advanced function because we have used the Wizard::CreateDialog() in the wizards.ycp.

After the dialog is created there is a loop function that handles the user input.

Help texts are included from the helps.ycp as the HELPS map.

[Note]Note

Every string we want to mark for translation uses the _("...") notation. Every such string should have a comment for translators defined above. We use standard gettext style.

Translations insist on defined textdomain inside the file.

2.4.3.5. File complex.ycp

YCP include which contains complex functions for dialogs handling.

/**
 * File: include/sshd/complex.ycp
 ...
 */

{

textdomain "sshd";

import "Label";
...
import "Sshd";


include "sshd/helps.ycp";

...

/**
 * Read settings dialog
 * @return `abort if aborted and `next otherwise
 */
symbol ReadDialog() {
    Wizard::RestoreHelp(HELPS["read"]:"");
    // Sshd::AbortFunction = PollAbort;
    if (!Confirm::MustBeRoot()) return `abort;
    boolean ret = Sshd::Read();
    return ret ? `next : `abort;
}

/**
 * Write settings dialog
 * @return `abort if aborted and `next otherwise
 */
symbol WriteDialog() {
    ...
}

/**
 * Summary dialog
 * @return dialog result
 */
any SummaryDialog() {
    ..
}

/**
 * Overview dialog
 * @return dialog result
 */
any OverviewDialog() {

    /* Sshd overview dialog caption */
    string caption = _("Sshd Overview");

    list overview = Sshd::Overview();

    /* FIXME table header */
    term contents = Wizard_hw::ConfiguredContent(
	/* Table header */
	`header(_("Number"), _("Sshd")),
	overview, nil, nil, nil, nil );

    contents = Wizard_hw::SpacingAround(contents, 1.5, 1.5, 1.0, 1.0);

    Wizard::SetContentsButtons(caption, contents, HELPS["overview"]:"",
	    Label::BackButton(), Label::FinishButton());

    any ret = nil;
    while (true) {

	ret = UI::UserInput();

	/* abort? */
	if(ret == `abort || ret == `cancel) {
	    if(ReallyAbort()) break;
	    else continue;
	}
        /* add */
        else if(ret == `add_button) {
            ret = `add;
            break;
        }
        /* edit */
        else if(ret == `edit_button) {
            ret = `edit;
            break;
        }
        /* delete */
        else if(ret == `delete_button) {
            continue;
        }
        else if(ret == `next || ret == `back) {
            break;
        }
        else {
            y2error("unexpected retcode: %1", ret);
            continue;
        }
    }

    return ret;
}

/* EOF */
}

This file is very similar to the dialogs.ycp one but here should be more complex dialogs with handling.

The most important is the ReadDialog() that calls the Sshd::Read()—a global function from the Sshd YCP module.

Dialog functions defined in the file can be seen used in the wizards.ycp file in map of aliases.

2.4.3.6. File helps.ycp

Help texts for every dialog.

Helps are stored under the /usr/share/YaST2/include/<module-name>/ path

/**
 * File: include/sshd/helps.ycp
 ...
 */

{

textdomain "sshd";

/**
 * All helps are here
 */
map HELPS = $[

    /* Read dialog help 1/2 */
    "read" : _("<p><b><big>Initializing sshd Configuration</big></b><br>
Please wait...<br></p>
") +

    /* Read dialog help 2/2 */
    _("<p><b><big>Aborting Initialization:</big></b><br>
Safely abort the configuration utility by pressing <b>Abort</b> now.</p>
"),

...

    /* Configure1 dialog help 1/2 */
    "c1" : _("<p><b><big>Configuration Part One</big></b><br>
Press <b>Next</b> to continue.
<br></p>") +

    /* Configure1 dialog help 2/2 */
    _("<p><b><big>Selecting Something</big></b><br>
It is not possible. You must code it first. :-)
</p>"),

...

];

/* EOF */
}

This file is included by the dialogs.ycp and complex.ycp. All texts are defined in the HELPS map.

All help texts are written in HTML but they must follow the Text Style Guide.

[Note]Note

Please, take note, that help texts are divided into smaller chunks by logic parts. For translators, it's also easier to translate only a short string after you change something.

2.4.3.7. File Sshd.ycp

YCP module with global API that can be used from another modules or projects.

All modules are stored under the /usr/share/YaST2/modules/ path.

/**
 * File:        modules/Sshd.ycp
 * Package:     Configuration of sshd
 * Summary:     Sshd settings, input and output functions
 * Authors:     John The Fish <john@thesmallfish.net>
 *
 * $Id: Sshd.ycp 27914 2006-02-13 14:32:08Z jtf $
 *
 * Representation of the configuration of sshd.
 * Input and output routines.
 */

{

module "Sshd";
textdomain "sshd";

...

/**
 * Read all sshd settings
 * @return true on success
 */
global boolean Read() {
    /* Sshd read dialog caption */
    string caption = _("Initializing sshd Configuration");
    ...
    integer steps = 4;
    ...
    Progress::New( caption, " ", steps, [
	...
	], [
	...
	],
	""
    );
    ...
    Progress::NextStage();
    ...
    if(Abort()) return false;
    modified = false;
    return true;
}

/**
 * Write all sshd settings
 * @return true on success
 */
global boolean Write() {
    /* Sshd write dialog caption */
    string caption = _("Saving sshd Configuration");
    ...
    integer steps = 4;
    ...
    Progress::New( caption, " ", steps, [
	...
	], [
	...
	],
	""
    );
    ...
    Progress::NextStage();
    ...
    if(Abort()) return false;
    return true;
}

...
}

The module keyword sets the module namespace. You can acces the global variables and function with the module_name:: prefix (e.g., Sshd:: for Sshd module).

Global Read and Write functions also define the Progress bar that is changed by calling Progress::NextStage() function.

Read and Write functions should always return a boolean whether they succeeded or failed.

[Note]Note

Our project also contains the Sshd2.pm file that is a module written in a Perl language. Usage of a Perl module is is the same as usage of a YCP one but the internal syntax is different. Creating Perl modules will be explained in another tutorial.