YaST2 Documentation

Novell Inc.

YaST2 Team

Novell Inc.

This document is not to be construed as a promise by any participating company to develop, deliver, or market a feature or a product.

Novell Inc. makes no representations or warranties with respect to the contents of this document, and specifically disclaims any express or implied warranties of merchantability or fitness or any particular purpose. Further, Novell Inc. reserves the right to revise this document and to make changes to its content, at any time, without obligation to notify any person or entity of such revisions or changes.

You are allowed to distribute unchanged copies of this document.


Table of Contents

Preface
1. Intro
2. What's Inside
3. Style Conventions
4. Introducing YaST
YaST - The Big Picture (overview)
1. Overview
1.1. Access To The System
1.2. Reasonable Suggestions
1.3. Workflows
1.4. Modules And The YCP-Language
1.5. User Interface
1.6. Summary
2. YaST Architecture
2.1. The SCR (System Configuration Repository)
2.2. The UI (User Interface)
2.3. YaST Core Engine
2.4. External Programs
The YaST Programming Language - YCP
1. The First YCP Program
1.1. YCP Source
1.2. The YCP compiler
1.3. Running YCP
2. YCP Data Types
2.1. Data Type void (nil)
2.2. Data Type symbol
2.3. Data Type boolean
2.4. Data Type integer
2.5. Data Type float
2.6. Data Type string
2.7. Data Type byteblock
2.8. Data Type list
2.9. Data Type map
2.10. Data Type path
2.11. Data Type term
2.12. Data Type any
3. More YCP types
3.1. Data Type block
4. YCP Type System
4.1. Data Type any And Type Checking
5. YCP Expression Evaluation
5.1. Evaluation Of Blocks
5.2. Evaluation Of Basic Data Types
6. YCP Operators
6.1. Comparison Operators
6.2. Boolean Operators
6.3. Bit Operators
6.4. Math Operators
6.5. Triple Operator
6.6. Operators Precedence
6.7. The bracket operator
6.7.1. Introduction
6.7.2. Access variant
6.7.2.1. Accessing lists
6.7.2.2. Accessing maps
6.7.2.3. Mixed map/list access
6.7.3. Assign variant
7. Data Type locale
8. YCP Program Structure
8.1. Comments
8.2. Variable Declaration
8.3. Variable Assignment
8.4. Conditional Branch
8.5. while() Loop
8.6. do..while() Loop
8.7. repeat..until() Loop
8.8. break Statement
8.9. continue Statement
8.10. return Statement
8.11. Function definition
8.12. Function declaration
8.13. include Statement
8.14. import Statement
8.15. Variable Scopes and blocks
8.16. Applying Expressions To Lists And Maps
8.16.1. foreach() Statement
8.16.2. listmap() Statement
8.16.3. maplist() Statement
8.16.4. mapmap() Statement
9. Controlling The User Interface
10. The YaST Wizard
11. Running y2base Stand-Alone
Access to the System (SCR in General)
1. SCR Agents
2. SCR Tree
3. Accessing SCR
4. Using SCR From Within YCP
5. Using SCR From The Command Line
6. Useful SCR Agents
YCP Modules in General
1. YCP Modules Overview
2. True YCP Modules
2.1. Included Modules
2.2. True Modules (Imported Modules)
2.3. True Modules And Constructors
3. Some Rules
3.1. Usability
4. Module Layout
4.1. Module Skeleton
4.2. Module Example
Source Code Documentation HowTo
1. YCP Module and Client Documentation
1.1. Documentation-Generation Tool
1.1.1. HTML Output
1.1.2. XML Output
1.2. YCP File Header
1.2.1. Authors AKA Author Attribute
1.2.2. Depends Attribute
1.2.3. File Attribute
1.2.4. Flags Attribute
1.2.5. Package AKA Module Attribute
1.2.6. Summary Attribute
1.3. YCP Functions
1.3.1. Description tag @deprecated
1.3.2. Description tag @descr
1.3.3. Description tag @example
1.3.4. Description tag @param
1.3.5. Description tag @ref
1.3.6. Description tag @return
1.3.7. Description tag @short
1.3.8. Description tag @since
1.3.9. Description tag @stable
1.3.10. Description tag @struct
1.3.11. Description tag @unstable
1.4. YCP Variables
2. Perl Module Documentation
3. SCR Agent Documentation
Common Modules and Libraries
1. Modules and Libraries
1.1. Packages Manipulation
1.1.1. Modules
1.1.2. Functions
1.1.2.1. Package Installation (GUI)
1.1.2.2. Package Installation (GUI, custom message)
1.1.2.3. Conditional Package Installation
1.1.2.4. Packages Installation (No GUI)
1.1.2.5. Testing
1.1.2.6. Other
1.2. Using the package manager
1.2.1. Introduction
1.2.2. Implementation
1.2.3. Querying the package manager
1.2.3.1. Checking an installed package
1.2.3.2. Checking the installation sources
1.2.3.3. Deleting a package
1.2.3.4. Installing a package
1.2.3.5. Solving dependencies
1.2.3.6. Committing the changes
1.2.4. Example code
1.3. Common Popup Dialogs
1.3.1. Simple and Expert Version
1.3.2. Headlines, Yes or No?
1.3.3. Predefined Messages
1.3.4. When to use which Popup
1.3.4.1. Decision Popups - two buttons, return true or false
1.3.4.2. Info Popups - just an "OK" button
1.4. Advanced YaST2 command line parsing
1.4.1. Important features
1.4.2. Basic usage of module CommandLine
1.4.3. Internally handled commands
1.4.4. Specification of the supported commands in YaST module
1.4.5. Advanced API
1.4.6. Example usage in YaST module
1.5. Runlevel Editor Library
1.5.1. Enabling/Disabling Service
1.5.2. Specifying Runlevels for Service
1.5.3. Init Script Actions
1.5.4. Service Info
1.5.5. Is Service Enabled?
1.5.6. Example of Usage
1.5.7. What Should You Know?
UI Layout and Events
1. YaST UI
1.1. YaST2 Layout
1.1.1. Summary: What's This All About?
1.1.2. Basics and Terms
1.1.2.1. The UI
1.1.2.2. Widgets
1.1.2.3. UI Independence and the libyui
1.1.2.4. The Nice Size
1.1.2.5. Initial Dialog Sizes
1.1.2.6. Full Screen Dialogs: `opt(`defaultsize)
1.1.3. Layout Building Blocks
1.1.3.1. Layout Boxes: HBox and VBox
1.1.3.2. Specifying Proportions: HWeight and VWeight
1.1.3.3. Rubber Bands: HStretch and VStretch
1.1.3.4. Making Common Widgets Stretchable: `opt(`hstretch) and `opt(`vstretch)
1.1.3.5. Spacings: HSpacing and VSpacing
1.1.3.6. Alignments: Left, Right, HCenter, Top, Bottom, VCenter, HVCenter
1.1.3.7. Compressing Excess Space: HSquash, VSquash, HVSquash
1.1.3.8. Optical Grouping: Frame
1.1.3.9. Grouping RadioButtons: RadioButtonGroup
1.1.3.10. The Esoterics: ReplacePoint
1.1.3.11. Obsolete: Split
1.1.4. Common Layout Techniques
1.1.4.1. Creating Widgets of Equal Size
1.1.4.2. Creating Widgets of Equal Size that don't Grow
1.1.4.3. Creating Widgets of Equal Size that don't Grow - with Spacings in between
1.1.4.4. Specifying the Size of Scrollable Widgets
1.1.5. Hints and Tips
1.1.5.1. Debugging Aids: The Log File
1.1.5.2. Keep it Simple - Do not Overcrowd Dialogs!
1.1.5.3. Always Keep Other UIs in Mind - What does it Look Like with NCurses?
1.1.5.4. Do not Neglect Mouseless Users - Always Provide Keyboard Shortcuts!
1.1.6. The Layout Algorithm - How the Layout Engine Works Internally
1.1.6.1. Primary and Secondary Dimensions
1.1.6.2. Calculating the Nice Size
1.1.6.2.1. Secondary Nice Size
1.1.6.2.2. Primary Nice Size
1.1.6.3. Setting the Size of a Layout - SetSize()
1.1.6.3.1. Running out of Space - the Pathological Cases
1.1.6.3.2. Centering in the Secondary Dimension
1.2. UI Events
1.2.1. Introduction
1.2.1.1. The YaST2 Event Model
1.2.1.1.1. Classic GUI Event Loops
1.2.1.1.2. The YaST2 Approach
1.2.1.1.3. Simplicity vs. Features
1.2.1.1.4. The notify Option
1.2.1.1.5. Downsides and Discussions
1.2.1.1.6. Design Alternatives
1.2.1.2. Event Delivery
1.2.1.2.1. Event Queues vs. One Single Pending Event
1.2.1.2.2. Event Reliability
1.2.1.2.3. Defensive Programming
1.2.2. Event-related UI Builtin Functions
1.2.3. Event Reference
1.2.3.1. Event Maps in General
1.2.3.2. Event Types
1.2.3.2.1. WidgetEvent
1.2.3.2.2. Activated WidgetEvent
1.2.3.2.3. ValueChanged WidgetEvent
1.2.3.2.4. SelectionChanged WidgetEvent
1.2.3.2.5. MenuEvent
1.2.3.2.6. TimeoutEvent
1.2.3.2.7. CancelEvent
1.2.3.2.8. KeyEvent
1.2.3.2.9. DebugEvent
Installation — Frameworks and Installation Process
1. Installation in General
1.1. Product Installation Control
1.1.1. Functionality
1.1.2. Implementation
1.1.3. Configuration
1.1.3.1. workflows
1.1.3.2. Proposals
1.1.3.3. Installation and Product Variables
1.1.3.4. Special Installation and Product Variables
1.1.3.5. Installation helpers
1.1.3.6. Software
1.1.3.7. Partitioning
1.1.3.7.1. Algorithm for space allocation
1.1.3.7.2. Configuration Options
1.1.3.8. Hooks
1.2. Firstboot Configuration
1.2.1. Enabling Firstboot
1.2.2. Customizing YaST Firstboot
1.2.2.1. Customizing Messages
1.2.2.2. License Action
1.2.2.3. Release Notes
1.2.2.4. Customizing Workflow Components
1.2.3. Scripting
2. Installation Proposal
2.1. API for YaST2 installation proposal
2.1.1. Motivation
2.1.2. Overview
2.1.3. The Dispatcher Interface
2.1.4. API functions
2.1.5. Dummy Proposal
2.2. Proposal API Reference
2.2.1. MakeProposal
2.2.1.1. Parameters
2.2.1.2. Return Values
2.2.2. AskUser
2.2.2.1. Parameters
2.2.2.2. Description
2.2.2.3. Return Values
2.2.3. Description
2.2.3.1. Return Values
2.2.4. Write
2.2.4.1. Description
2.2.4.2. Return Values
YaST Development And Tools
1. Development And Tools
1.1. YaST2 Development Tools
1.1.1. Quick Start
1.1.2. What is it?
1.1.3. Migration
1.1.4. Translation (po) Modules)
1.1.5. create-spec: Automatic creation of the .spec file
1.1.6. Overview of Paths
1.1.7. Toplevel make Targets in Detail
1.1.7.1. make package-local
1.1.7.2. make package
1.1.7.3. make check-tagversion
1.1.7.4. make check-up-to-date
1.1.7.5. make checkin-stable
1.1.7.6. make stable
1.2. YaST2 Logging
1.2.1. Introduction
1.2.2. Quick start
1.2.3. Logging levels
1.2.4. Logging functions
1.2.5. Additional functions
1.2.5.1. Setting the logfile name
1.2.5.2. Universal logging functions:
1.2.6. Components
1.2.7. Logfiles
1.2.8. Log entries
1.2.9. Logging control
1.2.10. Environment control
1.3. Check YCP Syntax
1.3.1. Quick Start
1.3.2. Why this Document?
1.3.3. Header Comment Checks
1.3.4. Filename Check
1.3.5. Author / Maintainer Entry Check
1.3.6. CVS Id: Marker Check
1.3.7. Translatable Messages Checks
1.3.7.1. textdomain Check
1.3.8. RichText / HTML Sanity Check
1.3.8.1. Completeness of <p> / </p> Paragraph Tags
1.3.8.2. Text Before, After, Between Paragraphs
1.3.8.3. No More Than One Paragraph per Message
1.3.8.4. Excess Forced Line Breaks <br> after Paragraphs
1.3.9. Widget / UI Function Parameter Checks
1.3.9.1. Keyboard Shortcut Check
1.3.9.2. Translatable Messages Check
1.3.10. Standardized Lib Function Checks
1.3.10.1. Duplicate Definitions of Wizard Lib Functions
1.3.10.2. Definitions and Usage of Obsolete Functions
1.3.10.3. Usage of Predefined Messages
1.3.11. Alternative Variable Declarations
1.3.12. Checking YCP Examples
1.3.13. check_ycp and Emacs
1.3.14. Extending check_ycp
1.3.14.1. Adding new Widgets / UI Functions
1.3.14.2. Other Extensions
1.4. The YaST2 Macro Recorder
1.4.1. Introduction
1.4.2. Quick Start
1.4.3. Purpose
1.4.4. What it is not
1.4.5. Quirks and Limitations
1.4.6. Anatomy of a Macro
1.5. YaST Desktop Files
1.5.1. Desktop File Rules
1.5.2. Desktop File Entries
1.5.2.1. Mandatory Desktop File Entries
1.5.2.2. YaST-Specific Desktop File Entries
1.5.2.3. AutoYaST-Specific Desktop File Entries
1.5.3. Desktop File Example
Reference — YCP Language
I. WFM Builtins
Args — Returns the arguments with which the module was called.
Execute — Special interface to the system agent. Not for general use.
GetEncoding — Returns the current encoding code
GetEnvironmentEncoding — Returns the encoding code of the environment where YaST is started
GetLanguage — Returns the current language code (without modifiers !)
Read — Special interface to the system agent. Not for general use.
SCRClose — Closes a scr instance.
SCRGetDefault — Gets the default scr instance.
SCRGetName — Get the name of a scr instance.
SCROpen — Create a new scr instance.
SCRSetDefault — Sets the default scr instance.
SetLanguage — Selects the language for translate()
Write — Special interface to the system agent. Not for general use.
call — Executes a YCP client or a Y2 client component.
II. YCP Byteblock Builtins
size — Returns a size of a byteblock in bytes.
tobyteblock — Converts a value to a byteblock.
III. YCP Float Builtins
pow — power function
tofloat — Converts a value to a floating point number.
tostring — Converts a floating point number to a string
trunc — round to integer, towards zero
IV. YCP Integer Builtins
tointeger — Converts a value to an integer.
V. YCP List Builtins
add — Create a new list with a new element
change — Changes a list. Deprecated, use LIST[size(LIST)] = value.
contains — Checks if a list contains an element
filter — Filters a List
find — Searches for the first occurence of a certain element in a list
flatten — Flattens List
foreach — Processes the content of a list
listmap — Maps an operation onto all elements of a list and thus creates a map.
lsort — Sort A List respecting locale
maplist — Maps an operation onto all elements of a list and thus creates a new list.
merge — Merges two lists into one
prepend — Prepends a list with a new element
remove — Removes element from a list
select — Selects a list element (deprecated, use LIST[INDEX]:DEFAULT)
setcontains — Checks if a sorted list contains an element
size — Returns size of list
sort — Sorts a List according to the YCP builtin predicate
sort — Sort list using an expression
splitstring — Split a string by delimiter
sublist — Extracts a sublist
sublist — Extracts a sublist
tolist — Converts a value to a list (deprecated, use (list)VAR).
toset — Sorts list and removes duplicates
union — Unions of lists
VI. Map Builtins
add — Add a key/value pair to a map
change — Change element pair in a map. Deprecated, use MAP[KEY] = VALUE.
filter — Filter a Map
foreach — Process the content of a map
haskey — Check if map has a certain key
lookup — Select a map element (deprecated, use MAP[KEY]:DEFAULT)
maplist — Maps an operation onto all elements key/value and create a list
mapmap — Maps an operation onto all key/value pairs of a map
remove — Remove key/value pair from a map
size — Size of a map
tomap — Converts a value to a map.
union — Union of 2 maps
VII. Miscellaneous YCP Builtins
eval — Evaluate a YCP value.
is — Checks whether a value is of a certain type
random — Random number generator.
setenv — Change or add an environment variable
setenv — Change or add an environment variable
sformat — Format a String
sleep — Sleeps a number of milliseconds.
srandom — Initialize random number generator
srandom — Initialize random number generator.
time — Return the number of seconds since 1.1.1970.
y2debug — Log a message to the y2log.
y2error — Log an error to the y2log.
y2internal — Log an internal message to the y2log.
y2milestone — Log a milestone to the y2log.
y2security — Log a security message to the y2log.
y2warning — Log a warning to the y2log.
VIII. YCP Path Builtins
add — Add a path element to existing path
size — Returns the number of path elements
topath — Converts a value to a path.
IX. YCP String Builtins
crypt — Encrypts a string
cryptbigcrypt — Encrypts a string using bigcrypt
cryptblowfish — Encrypts a string with blowfish
cryptmd5 — Encrypts a string using md5
deletechars — Removes all characters from a string
dgettext — Translates the text using the given text domain
dngettext — Translates the text using a locale-aware plural form handling
dpgettext — Translates the text using the given text domain and path
filterchars — Filters characters out of a String
find — Returns position of a substring
findfirstnotof — Searches string for the first non matching chars
findfirstof — Finds position of the first matching characters in string
findlastnotof — Searches the last element of string that doesn't match
findlastof — Searches string for the last match
issubstring — searches for a specific string within another string
mergestring — Joins list elements with a string
regexpmatch — Searches a string for a POSIX Extended Regular Expression match.
regexppos — Returns a pair with position and length of the first match.
regexpsub — Regex Substitution
regexptokenize — Regex tokenize
search — Returns position of a substring
size — Returns the number of characters of the string s
substring — Returns part of a string
substring — Extracts a substring
timestring — Returns time string
toascii — Returns characters below 0x7F included in STRING
tohexstring — Converts an integer to a hexadecimal string.
tolower — Makes a string lowercase
tostring — Converts a value to a string.
toupper — Makes a string uppercase
X. YCP Term Builtins
add — Add value to term
argsof — Returns the arguments of a term.
remove — Remove item from term
select — Select item from term
size — Returns the number of arguments of the term TERM.
symbolof — Returns the symbol of the term TERM.
toterm — Converts a value to a term.
toterm — Constructs a term from a symbol and a list.
Reference — User Interface
I. Event-related UI Builtin Functions
UI::UserInput — Waits for user input and returns a widget ID.
UI::PollInput — Checks for pending user input. Does not wait. Returns a widget ID or nil if no input is available.
UI::TimeoutUserInput — Waits for user input and returns a widget ID. Returns ID `timeout if no input is available for timeout milliseconds.
UI::WaitForEvent — Waits for user input and returns an event map. Returns ID `timeout if no input is available for timeout milliseconds.
II.
AskForExistingDirectory — Ask user for existing directory
AskForExistingFile — Ask user for existing file
AskForSaveFileName — Ask user for a file to save data to.
Beep — Beeps the system bell
BusyCursor — Sets the mouse cursor to the busy cursor
ChangeWidget — Changes widget contents
CheckShortcuts — Performs an explicit shortcut check after postponing shortcut checks.
CloseDialog() — Closes an open dialog
DumpWidgetTree — Debugging function
FakeUserInput — Fakes User Input
GetDisplayInfo — Gets Display Info
GetLanguage — Gets Language
GetProductName — Gets Product Name
Glyph — Returns a special character (a 'glyph')
HasSpecialWidget — Checks for support of a special widget type.
MakeScreenShot — Makes Screen Shot
NormalCursor — Sets the mouse cursor to the normal cursor
OpenDialog — Opens a Dialog with options
PlayMacro — Plays a recorded macro
PollInput — Poll Input
PostponeShortcutCheck — Postpones Shortcut Check
QueryWidget — Queries Widget contents
RecalcLayout — Recalculates Layout
Recode — Recodes encoding of string from or to "UTF-8" encoding.
RecordMacro — Records Macro into a file
RedrawScreen — Redraws the screen
ReplaceWidget
RunInTerminal — runs external program in the same terminal
RunPkgSelection — Initializes and run the PackageSelector widget
SetConsoleFont — Sets Console Font
SetFocus — Sets Focus to the specified widget
SetFunctionKeys — Sets the (default) function keys for a number of buttons.
SetKeyboard — Sets Keyboard
SetLanguage — Sets the language of the UI
SetProductName — Sets Product Name
StopRecordingMacro — Stops recording macro
TextMode — Check if the UI is running in text (NCurses) mode.
TimeoutUserInput — User Input with Timeout
UserInput — User Input
WFM/SCR — callback
WaitForEvent — Waits for Event
WidgetExists — Checks whether or not a widget with the given ID currently exists
WizardCommand — Runs a wizard command
III.
AAA_All-Widgets — Generic options for all widgets
BarGraph — Horizontal bar graph (optional widget)
BusyIndicator — Graphical busy indicator
CheckBox — Clickable on/off toggle button
CheckBoxFrame — Frame with clickable on/off toggle button
ComboBox — drop-down list selection (optionally editable)
DateField — Date input field
DownloadProgress — Self-polling file growth progress indicator (optional widget)
DumbTab — Simplistic tab widget that behaves like push buttons
Empty — Placeholder widget
Frame — Frame with label
HBox — Generic layout: Arrange widgets horizontally or vertically
HSpacing — Fixed size empty space for layout
HSquash — Layout aid: Minimize widget to its preferred size
HWeight — Control relative size of layouts
Image — Pixmap image
InputField — Input field
IntField — Numeric limited range input field
Label — Simple static text
Left — Layout alignment
LogView — scrollable log lines like "tail -f"
MarginBox — Margins around one child widget
MenuButton — Button with popup menu
MinWidth — Layout minimum size
MultiLineEdit — multiple line text edit field
MultiSelectionBox — Selection box that allows selecton of multiple items
PackageSelector — Complete software package selection
PartitionSplitter — Hard disk partition splitter tool (optional widget)
PatternSelector — High-level widget to select software patterns (selections)
PkgSpecial — Package selection special - DON'T USE IT
ProgressBar — Graphical progress indicator
PushButton — Perform action on click
RadioButton — Clickable on/off toggle button for radio boxes
RadioButtonGroup — Radio box - select one of many radio buttons
ReplacePoint — Pseudo widget to replace parts of a dialog
RichText — Static text with HTML-like formatting
SelectionBox — Scrollable list selection
SimplePatchSelector — Simplified approach to patch selection
Slider — Numeric limited range input (optional widget)
Table — Multicolumn table widget
TimeField — Time input field
TimezoneSelector — Timezone selector map
Tree — Scrollable tree selection
VMultiProgressMeter — Progress bar with multiple segments (optional widget)
Wizard — Wizard frame - not for general use, use the Wizard:: module instead!
A. UI Richtext

Preface


Chapter 1. Intro

The most important thing reads: [DON'T PANIC!]

Administering a Linux system at the lowest level is sometimes not an easy task. If it should be done manually, it requires a very experienced and knowledgeable person, willing to browse and edit very many configuration files. As a result there have been many efforts to create intelligent tools that provide rather automatic means to accomplish this challenging and (at the same time) tedious task.

One of these tools is YaST, the SUSE Linux™ installer. Being the result of a rather long period of development, it is by now a very large and capable system, well suited to install and administer a SUSE Linux™ system. While the internal functionality of YaST is quite multifaceted and therefore not exactly easy to understand, it should not be kept as a secret. Rather the world shall be encouraged to make use of the mechanisms YaST can provide.

This goal can be achieved because YaST is not a closed monolithic system but has a high degree of modularity. In fact it consists largely of modules that could as well be created by people not related to YaST development. For example hardware vendors could provide a YaST module for customizing specific system settings related to their particular piece of hardware. From the user's point of view this would be much more comfortable than editing configuration files by hand.

Of course this can't be done without some knowledge of the YaST internal functionality. So this document tries to lighten things up by advancing from the unsubtle connections in the beginning to more and more detailed explanations towards the end. However, describing the particularities of this matter in full detail would easily fill several heavy books which in turn would contradict the goal of introductory simplification. Furthermore some of these “details” are subject to moderate change service, which would render “static” documentation like this one outdated rather quickly.

To alleviate these problems, this text very often refers to the “officialYaST developers documentation that can be found in /usr/share/doc/packages/yast2* (especially towards the end). Aside from the references to be found in the following text, this location provides very valuable information regarding the whole YaST environment. To have access to these files the following packages must be installed:

  • yast2-devel

  • yast2-core-devel

  • liby2util-devel

  • yast2-packagemanager-devel

Chapter 2. What's Inside

This document is subdivided into the following chapters:

Introduction.  A brief explanation of the intention and nature of this document.

YaST - The Big Picture.  This is a short depiction of the YaST installer and the YaST environment as such. The architectural peculiarities of YaST are explained as far as it is necessary to understand the elucidations that follow thereafter.

The YaST Language - YCP.  This chapter is dedicated to the YaST language that constitutes most of the high-level functionality of YaST. Sections covering the basic language elements are accompanied by others that deal with user interface creation and program structure.

SCR Details.  In this chapter the YaST System Configuration Repository (SCR) is explained in some concise detail. It shows how to access configuration data and hardware data from within YaST modules in a consistent way.

YaST Modules.  Some explanations regarding the different types of YaST modules as well as some rules for writing them.

Appendix A. References.  Throughout this document there are numerous references to the YaST developers documentation. To ease access to these links this appendix is mostly a dense index of those references.

Chapter 3. Style Conventions

Throughout this document some conventions regarding the typeface of printed text are used:

  • Emphasized text is used to denote important parts of the text.

  • Product names, file names and paths are printed using literal typeface. Furthermore cut-outs from programs that are embedded in the normal text flow are printed this way.

  • Commands and command lines are printed using the command typeface.

  • Keyboard keys are denoted as in CTRL-C.

  • The description of programming language elements is displayed as shown below.

    Synopsis: while (condition) loop_body

    The parts of the language construct are printed like commands while any arguments are printed emphasized.

Chapter 4. Introducing YaST

YaST is the installation program used by SUSE Linux™ to install Linux on a system and to administer this installed system thereafter. The notation “program” is a bit misleading here because in fact YaST consists of many components and layers. Therefore one may as well regard YaST as an installation and administering environment.

Among the most important components in this environment are the YaST-modules which are usually written in a YaST-specific language called YaST Control Language (YCP). With exception of some rare cases the whole high-level functionality of YaST is formulated in YCP. When YaST is running, the YCP-modules are interpreted by the low-level YaST-components and the YCP-code makes use of the infrastructure provided by them. The possibility to add such modules at any time realizes the concept of extensibility that is inherent in YaST.

This concept of extensibility by means of modules has been designed into YaST from the very beginning. In fact YaST-modules are the layer of YaST the user comes in contact with. Nearly every dialog on screen during the installation is realized as a YaST-module and there are also modules that act behind the scenes to care of specific pieces of hardware, e.g. the keyboard.

The YaST-modules mentioned so far come ready-made with the distribution and provide the core functionality to install and administer a SUSE Linux™-system, but this need not and should not be the end. The extension facility is intended to be also used by “third party people”, e.g. hardware vendors, who want to contribute YaST-functionality in some way.

To pave the way for this intention to come alive this document will provide some insight into the inner mechanics of YaST. The primary goal of the following elucidations is to make available the information that is needed to write YaST-modules that conform to the programming paradigm imposed by YaST.

What This Document Is.  This document explains in some detail how to extend the functionality of YaST by means of modules. Of course every module of more than trivial functionality will have to make use of the features that are provided by the YaST-core-components and other YaST-modules. While the official YaST developers documentation is the primary knowledge base for all YaST related information, it is a bit overwhelming for everyone who tries to get into the matter for the first time. Therefore this document tries to provide a gentle introduction by explaining things rather explicitly in the beginning and getting more and more concise towards the end. By providing very many references to the developers documentation this document can be thought of as a “guided index” to this voluminous material.

What This Document Is Not.  This document will not explain the “binary” particularities related to YaST, i.e. there will be no implementation notes on how the low-level machinery of YaST is realized. Because YaST implies the module concept for extensibility, only this approach is promoted here. The “engine” that executes these modules and how it is assembled is subject to the following explanations only in so far as it is necessary to understand the interaction of the various components.

The Audience Of This Document.  So this document is intended for all people who want to make use of the YaST-functionality by providing modules for a specific task. Additionally the information presented herein might be interesting for all those who want to adept something about the whys and wherefores related to YaST as such. Furthermore, to get most out of this reading, the reader is supposed to have some programming experience in a structured programming language, ideally C/C++. Some expertise in functional programming could also be helpful but is not really necessary.

YaST - The Big Picture (overview)


List of Figures

2.1. The YaST Architecture

To be able to create a YaST-module it is necessary to have understood how the extensive YaST-world is structured, which components there are, what they do and how they do it. Therefore prior to going into closer detail we'll step back from the blackboard and have a look at the big picture first. By doing so you will have the opportunity to get an overview of the ample terrain YaST is living on. While you don't have to understand each and every byte YaST consists of, having seen the whole issue will ease your understanding of the details we will come across.

Chapter 1. Overview

YaST has been invented to have an extensible and fairly standardized means to install and manage SUSE Linux™ on a system. Basically YaST serves three main purposes:

  • Installation of SUSE Linux™ on a system

  • Configuration of the installed system

  • Administration of the installed system

To provide a solution to the resulting demands that has a lifetime extensible well into the future this solution had to be flexible and maintainable. Consequently some key concepts determined the design of YaST. In particular it was the strict separation of:

  • The user interface

  • The functional code doing the job

  • The data representing the current state of the system

Furthermore YaST has some very specific attributes that make it unique for the user as well as for those people who are developing it or contributing to it. The following sections outline some of the features of the YaST installer that should be seen as a guiding line for module development.

1.1. Access To The System

Managing a SUSE Linux™ system requires direct low-level access to the system which generally means reading and writing configuration data. Of course this could be done manually by a knowledgeable person using a conventional editor. A more comfortable and in most cases safer way is to use YaST. Consequently YaST must be able to handle this configuration data on the system level. By handling the original data YaST activities take into account manual editing that might also occur. Thus nobody is forced to use YaST exclusively for configuration tasks.

In YaST the access to system configuration data is realized by means of a special component (or layer if you prefer), the System Configuration Repository (SCR) (see below and Access to the System (SCR in General)). The SCR component basically consists of a number of so-called agents that have been created to accomplish a specific kind of access. For example there is an agent to run shell-commands and there is another one that reads and writes ASCII-files of a specific format. Additionally there are agents that provide access to the system hardware e.g. by taking hold on the proc-file-system.

All these agents are gathered together under a common hood, the SCR-API that can be used from within the YaST-modules in a consistent way. In summary the SCR provides kind of a view on all kinds of data, either YaST2 internal data, original system configuration files or hardware data.

1.2. Reasonable Suggestions

YaST implies lots of artificial intelligence to provide reasonable suggestions for the various tasks. During installation the target system is thoroughly analyzed with respect to its hardware components and in most cases YaST succeeds in suggesting a proper configuration for them.

These suggestions are presented in an overview dialog that shows the main characteristics of the system to be installed and how YaST would handle them. If you are satisfied with these automatically generated settings you can simply accept them. If not, each of the system configuration categories can be “activated” to be changed manually. This is where the workflows come into their own.

1.3. Workflows

If you decide to change a specific configuration category this is usually being done in a workflow. Workflows are used to lead you through the steps necessary to accomplish a specific task. The steps are generally small to avoid an information “overflow”. At the end of the sequence the task has been accomplished and the changes are made permanent in the system.

As was stated above, you are not forced to do it this way. You could as well edit configuration files by hand but YaST can offer as much help as possible for this. Sometimes a workflow has multiple branches for “novice” and “expert” modes. The novice mode fills in the default values and tries to determine as much as possible automatically. The expert mode offers full control and allows to enter even unreasonable values.

By providing pre-configured workflows and configuration data, it is possible to automate almost arbitrary configuration tasks with YaST. From adding a user, to installing a completely configured SUSE Linux™ on specific hardware, nearly everything is possible.

1.4. Modules And The YCP-Language

Every workflow is assembled from rather small steps, implemented by means of YaST modules written in a YaST-specific scripting language, the YaST Control Language (YCP). These YaST-modules are then called in a predefined sequence to complete a specific task.

In fact it is possible to even write modules in bash and Perl as long as the module need not have a user interface, i.e. it is not interactive. Such non-interactive modules typically handle specific problems like controlling a particular piece of hardware and can be called from within YCP-modules. This building block approach makes constructing complex workflows easy and maintainable.

1.5. User Interface

The YCP language is also used to control the user interface (UI) presented on screen. The UI displays the information already known by the system and retrieves the information entered by the user.

There are two modes of operation:

  • Text mode for console-based service

    In text mode the user interface is presented in the NCurses environment that provides windowing capabilities and entry forms on a text-based console. Mouse support is neither possible nor necessary here because all dialogs can be operated using only the keyboard.

  • Graphics mode for X11-based service

    In graphics mode the well-known Qt-system is used to present the dialogs in a graphical way using a running X11-server. Operating these dialogs follows the common habits of graphical user interfaces.

It is important to notice here that both methods principally use the same YaST-specific YCP-API to build the dialogs. While there are some (rare) cases where the YCP-code has to distinguish these modes, the dialogs are usually programed for both worlds in in one single source with the same code.

1.6. Summary

In summary YaST provides the following features, some of them having already been mentioned above:

  • System access

    YaST provides thorough probing of the system hardware and presents the information gathered thereby via the SCR-API. The SCR is also the means for reading and writing configuration files.

  • Reasonable Suggestions

    Based on the system analysis and predefined configuration data, YaST is able to provide reasonable suggestions for almost any configuration task.

  • Workflows

    Management of particular configuration categories is usually realized in form of workflows that split up the whole task into small steps.

  • Modules and YCP

    The steps constituting a workflow are usually realized as YaST-modules that are written in the YaST Control Language (YCP)

  • User interface

    The user interface of YaST is realized by means of a specific API from within the YCP-modules. This API supports a text-based console-mode as well as a graphical X11-mode.

  • Internationalization

    YaST provides support for various languages.

  • Multi-platform support

    YaST provides support for various platforms like Intel™ (x86), Apple™, IBM™ (s390) etc.

Chapter 2. YaST Architecture

YaST2 is a modular system for Linux installation and system administration. The design goals include:

  • Flexibility

  • Extensibility

  • Maintainability

  • Network transparency

    support administration of remote hosts or virtual machines on mainframes, machines without CD/DVD drives, rack-mounted machines

  • User interface independence

    must run in graphical and text-only environments and serial consoles

  • Cover the whole range from novice users to expert system administrators

To achieve the above design goals, YaST2 is split up into a number of components for each individual task:

Figure 2.1. The YaST Architecture

The YaST Architecture

There is the core engine and to run scripts written in YCP (YaST2's own scripting language), Perl or (in future releases) other scripting languages.

The engine and scripts together form a YaST2 Module for the user.

2.1. The SCR (System Configuration Repository)

Even though in most scenarios there is only one single machine, it is important to distinguish between the installation source machine and the installation target machine:

  • The installation source machine is the machine that holds the installation media - usually CDs or DVDs - and a mini-Linux called "inst-sys" that is copied from one of those installation media to that machine's RAM disk to have a basic operating system to work with on a "bare metal" machine (a machine that doesn't have an operating system installed yet). Most of that inst-sys is read-only, there is only limited disk space for temporary files, and since everything runs from a RAM disk the writable part of it is very volatile.

  • The installation target on the other hand is the machine that is to be installed or administered. That may be the same machine as the installation source machine (in fact, this is very common for PC installation or administration tasks), but it might as well be two distinct machines - a virtual machine on a mainframe computer or a remote rack-mounted machine without any display adapter or CD/DVD drives.

All communication with the installation target is handled via the System Configuration Repository (SCR) to guarantee the network abstraction design goal. This is much easier said than done, however: YaST2 module developers always have to keep in mind that it is strictly forbidden to access system files (or any other system resources, for that matter) directly, even if there may be very convenient CPAN Perl modules to do that. Rather, SCR is to be used instead - always. Otherwise everything might run fine if installation source and target are the same machine, but break horribly if they are not.

SCR in itself is also modularized: All calls are handled by "agents" that each know how to handle a particular configuration "path" like "/etc/fstab" or "/etc/passwd". That may be a simple file, but it may also be a directory hierarchy like "probe" - this particular agent handles all kinds of hardware probing, from mouse and display adapters to storage device controllers (like SCSI or IDE controllers), disks attached to each individual controller or partitions on those disks. Paths are denoted like ".etc.fstab" for SCR. YCP even has a special data type "path" for just this case (a special kind of string with some special operations).

SCR agents handle no more than three calls:

  • SCR::Read()

  • SCR::Write()

  • SCR::Execute()

The first argument is always the path to handle, but there may be any number of additional parameters, depending on the agent.

While Read() and Write() are obvious, Execute() may not be: This is intended for some kinds of agents that actually run a program on the installation target. In particular, the ".target.bash" agent does that - it runs a "bash" shell on the target machine and accepts a shell command as an argument. This is the tool of choice for tasks such as creating backup copies of configuration files or running any special command on the target machine - and again, the distinction between installation source and installation target machine becomes very important: You want run these commands on the (possibly remote) target machine, not on the machine that happens to hold the installation media.

SCR agents can easily added when needed. There are frameworks available to write SCR agents in C++, in Perl, or as Bash shell scripts as well as several ready-made parsers for different file formats like the ".ini" file parser that can handle files with "key = value" pairs or the "anyagent" that generalizes that concept even more using regular expressions. Those parsers return YCP lists and maps ready for further processing.

Typically, a YaST2 module for a specific installation or administration task includes a set of YCP or Perl scripts as well as some SCR agents to handle its particular configuration files.

2.2. The UI (User Interface)

Given the wide variety of machines that can possibly be handled with YaST2, it is important to keep the user interface (UI) abstraction in mind - very much like the SCR, the UI does not necessarily run on the installation target machine. It doesn't even need to run on the same machine as the WFM.

The UI provides dialogs with "widgets" - user interface elements such as input fields, selection lists or buttons. It is transparent to the calling application if those widgets are part of a graphical toolkit such as Qt, or text based (using the NCurses library) or something completely else. An input field for example only guarantees that the user can enter and edit some value with it. A button only provides means to notify the application when the user activated it - by mouse click (if the UI supports using pointing devices such as a mouse), by key press or however else.

The UI has a small number of built-in functions - for example:

  • UI::OpenDialog() accepts a widget hierarchy as an argument and opens a dialog with those widgets

  • UI::CloseDialog() closes a dialog

  • UI::QueryWidget() returns a widget's property such as the current value of an input field or selection box

  • UI::ChangeWidget() changes a widget's property

  • UI::UserInput() waits until the user has taken some action such as activate a button - after which the application can call UI::QueryWidget() for each widget in the dialog to get the current values the user entered. The application does not have to handle every key press in each input field directly - the widgets are self-sufficient to a large degree.

There is virtually no low-level control for the widgets - nor is it necessary or even desired to have that. You don't specify a button's width or height - you specify its label to be "Continue", for example, and it will adapt its dimensions accordingly. If desired, more specific layout constraints can be specified: For example, buttons can be arranged in a row with equal width each. The UI will resize them as needed, giving them additional margins if necessary.

The existing UIs provide another layer of network abstraction: The graphical UI uses the Qt toolkit which is based on the X Window System's Xlib which in turn uses the X protocol (usually) running on top of TCP/IP. X Terminals can be used as well as a Linux console (that may be the installation source machine or the installation target machine or another machine connected via the network) running the X Window System or even X servers running on top of other operating systems.

The NCurses (text based) UI requires no more than a shell session - on a text terminal (serial console or other), on a Linux console, in an XTerm session, via ssh or whatever.

Currently, there is no web UI, but YaST2's concepts would easily allow for that if it proves useful or necessary.

2.3. YaST Core Engine

The component broker is the central piece of YaST. It acts as a dispatcher for all other components: When a (YCP, Perl or whatever) script calls a function, the broker determines what component handles that function call based on the respective namespace identifier. It is transparent to the caller what programming language a function is written in; the component broker handles that kind of dispatching. The caller only needs to know the function name, its namespace and (or course) the required parameters.

For example, calls like UI::OpenDialog() go to the UI (the user interface), SCR::Read() to the SCR (the system configuration repository). Even scripts can provide namespaces via modules in YCP or Perl.

All communication between the different parts of YaST core is done via a predefined set of YCP data types - simple data types like string, integer, boolean etc., but also compound data types like maps (key / value pairs, also known as "hashes" in other programming languages) or lists (like arrays or vectors in other programming languages). For complex data structures, maps, lists and simple data types can be nested to any degree.

2.4. External Programs

The core-engine of YaST consists of some binary components (modules) that are interconnected via YaST-specific protocols. There are clients as well as servers that are responsible for specific tasks that may have to be accomplished during a YaST-session. According to the well-known client-server-paradigm often used in software technology, YaST-servers are program modules that passively await connections from certain clients to process their requests. Clients on the other hand are active components that send requests to the servers thereby initiating certain actions.

For example the SCR and the UI act as server components that process client-requests on demand. An example for a client module is the stdio-component that can be used to connect the YaST-internal communication with a terminal.

Because this architectural specialty is meant to be used only by the YaST core developers to establish and maintain the low-level machinery we will not go into more detail here. Instead we will focus on the advocated method of extending YaST at the “open end” by creating YCP-modules.

The YaST Programming Language - YCP


The YaST-language YCP is a scripting language to be interpreted by the YCP-engine (YCP interpreter) specially designed for manipulation with a system configuration. Its syntax is very similar to C programming language. Because YCP can make use of the whole infrastructure that YaST provides, the actions that can be accomplished with YCP are very powerful.

YCP has the usual features of procedural languages and some more, partially originating from the functional programming paradigm:

  • Control structures like if/then/else, foreach-loops.

  • Compound data types like strings, lists and maps.

  • Function definition (procedures)

  • Variable scopes

  • Name spaces

  • Include files

  • UNIX command execution (via the YaST infrastructure)

On the following pages we will explore the YCP language definition and find out how to use YCP to write “programs” that can be executed by YaST.

Chapter 1. The First YCP Program

Probably the best way to get into the matter is by means of a simple example.

1.1. YCP Source

The following little program opens a window that displays the string “Hello, World!” and provides a push button for termination.

Example 1.1. “Hello World” in YCP

{
    string message = "Hello, World!";
    
    UI::OpenDialog(
               `VBox( 
                     `Label( message ),
                     `PushButton("&OK")
                    )
              );

    UI::UserInput();

    UI::CloseDialog();
}
          

In the following this code will be explained shortly in a line-by-line manner thereby touching some topics we will examine in detail later on.

  • {

    The opening curly opens a so-called block in YCP. Blocks are used to “glue” several YCP-statements together to form an entity that can be handled just like a single statement.

  • string message = "Hello, World!";

    In this line we define a variable named “message” that is of type string. In YCP any variable definition must imply a value assignment to avoid all errors that might occur due to uninitialized variables. Here we assign the constant string “Hello, World!”. Furthermore the terminating semicolon is mandatory in YCP to indicate the end of a statement (just like C).

  • UI::OpenDialog(

    This command opens a dialog on screen. Because we want to display something, the code describing our dialog has to be sent to the UI. This is being done by the leading name space identifier UI::. The (single) parameter that is supplied here determines the content of the dialog.

  • `VBox(

    This is a UI-statement related to the geometry of the dialog to be defined. As the name indicates it opens a (virtual) vertical box that displays all content in a column-wise manner. (Geometry management is described in more detail in Chapter 9, Controlling The User Interface).

    The leading back-quote introduces a YCP-feature that stems from the functional programming paradigm. In YCP-speak the `VBox() is a term. In YCP, terms are used as a structured constants and are typically passed to functions provided by YaST infrastructure as parameters as is done here with OpenDialog().

  • `Label( message ),

    Displaying strings in YCP is done by means of Labels. This statement gets one parameter, the string variable we defined in the beginning. Because it is the first of two parameters passed to `VBox() this line is not terminated with a semicolon but with a comma. As in most programming languages commas are used to separate parameters in YCP.

  • `PushButton("&OK")

    This statement displays a labeled push button. Since it is the next element in the enclosing `VBox(), it is displayed immediately below the preceding label. The & in the label string is a YaST feature declaring the subsequent character to be a key-shortcut. As a result the button can not only be clicked with the mouse but also be activated by typing ALT-O.

  • ) and );

    The next two lines first close the open `VBox() and then the open OpenDialog(). Because `VBox() is passed as a parameter to OpenDialog() there is no need to terminate the statement with a semicolon. OpenDialog() on the other hand is a statement in the UI and hence must be terminated with a semicolon.

  • UI::UserInput();

    Here we hand over control to the UI which then awaits some sort of user input. In this case it simply waits for the push button to be pressed by the user. Consequently our program blocks at this point until the user really does it.

  • UI::CloseDialog();

    After all the UI-related action has finished, i.e. when UI::UserInput() returns, we want to remove the dialog we just created. This is done here.

  • }

    Indicating the end of the block, the closing curly bracket ends our little YCP-program.

1.2. The YCP compiler

Section not written yet...

1.3. Running YCP

Now we can start the program using YaST. For this, we will use a script /sbin/yast2. It is an envelope for easier setup of a running YaST environment.

So if you are reading this document with a browser, you could copy-and-paste the program listed above into a file hello.ycp, and then run /sbin/yast2 hello.ycp which should render the following “spectacular” result.

Figure 1.1. Output of the “Hello, World!”-program

Output of the Hello, World!-program

Starting off with this simple example we will now explore the more subtle details of YCP. Since all programming is about handling of data there must be a way to hold it in variables of different types. In the next section you will get to know the various data types that YCP knows about.

Chapter 2. YCP Data Types

Just like any other high-level programming language YCP has typed variables to hold data of different kinds:

2.1. Data Type void (nil)

This is the most simple data type. It has only one possible value: nil. Declaring variables of this type doesn't make much sense but it is very useful to declare functions that need not return any useful value. nil is often also returned as an error flag if functions fail in doing their job somehow.

2.2. Data Type symbol

A symbol is a literal constant. It is denoted by a single backquote and a letter or underscore optionally followed by further letters, underscores or digits.

Example 2.1. Symbol constants

`literal
`next

2.3. Data Type boolean

In contrast to C/C++, a YCP boolean is a real data type with the dedicated values true and false. Comparison operations like < or == evaluate to a boolean value. The if (...) statement expects a boolean value as the result of the decision clause.

2.4. Data Type integer

This is a machine independent signed integer value that is represented internally by a 64 bit value. The valid range is from -2^63 through 2^63-1. Integer constants are written just as you would expect. You can write them either decimal or hexadecimal by prefixing them with 0x, or octal by prefixing them with 0 (just like in C/C++).

Example 2.2. Integer constants

1
-17049349
0x9fa0
0xDEADBEEF
0233

2.5. Data Type float

Floating point numbers. Because they are represented via the C datatype double the valid range is machine dependent. Constants are written just as you would expect. The decimal point is mandatory only if no exponent follows. Then there must be at least one digit leading the decimal point. The exponent symbol may be e or E.

Example 2.3. Float constants

1.0
-0.0035
1e30
-0.128e-17

2.6. Data Type string

Represents a character string of almost arbitrary length (limited only by memory restrictions). String constants consist of UNICODE characters encoded in UTF8. They are enclosed in double quotes.

The backslash in string can be used to mark special characters:

RepresentationMeaning
\nNewline (ASCII 10)
\tTabulator
\rCarriage Return (ASCII 13)
\bBackspace
\fForm Feed
\abc ASCII character represented by the octal value abc. Note that unlike in C, there must be exactly 3 octal digits!
\XThe character X itself.

A backslash followed by a newline makes both the backslash and the newline being ignored. Thus you can split a string constant over multiple lines in the YCP code.

Example 2.4. String constants

This string ends with a newline character.\n”
“This is also a newline: \012

2.7. Data Type byteblock

A byteblock simply is a sequence of zero or more bytes. The ASCII syntax for a byteblock is #[hexstring]. The hexstring is a sequence of hexadecimal values, lower and upper case letters are both allowed. A byte block consisting of the three bytes 1, 17 and 254 can thus be written as #[0111fE].

In most cases, however, you will not write a byteblock constant directly into the YCP code. You can use the SCR to read and write byteblocks.

Example 2.5. Byteblock constants

#[ ]
#[42]
#[0111fE]
#[03A6f298B5]

2.8. Data Type list

A list is a finite sequence of values. These values need not necessarily have the same data type. List constants are denoted by square brackets. In contrast to C it is possible to use complex expressions as list members when defining a list constant. The empty list is denoted by [].

Example 2.6. List constants

[ ]
[ 1, 2, true ]
[ variable, 17 + 38, some_function(x, y) ]
[ "list", "of", "strings" ]

Accessing the list elements is done by means of the index operator as in my_list[1]:"error". The list elements are numbered starting with 0, so index 1 returns the second element. After the index operator there must be a colon denoting a following default value that is returned if the list access fails somehow. The default value should have the type that is expected for the current list access, in this case the string “error”.

Note 1: A list preserves order of its elements when iterating over them.

Note 2: There is also another method for accessing lists, originating from the early days of YaST. The command select(my_list, 1, "error") also returns the second element of my_list. While this still works, it is deprecated by now and may be dropped in the future.

2.9. Data Type map

A YCP-map is an associative array. It is a list of key-value-pairs with the keys being non-ambiguous, i.e. there are no two keys being exactly equal. While you can use values of any type for keys and values, you should restrict the keys to be of type string because from experience other types tend to complicate the code. Values of arbitrary type on the other hand make the map a very flexible data container. Maps are denoted with $[ key_0:value_0, key_1:value_1, ...]. The empty map is denoted by $[].

Note: A map does not reserve order of its elements when iterating over them.

Example 2.7. Map constants

$[ ]
$[ "/usr": 560, "/home" : 3200 ]
$[ "first": true, "2": [ true, false ], "number" : 8+9 ]

Accessing the map elements is done by means of the index operator as in my_map["os_type"]:"linux". This returns the value associated with the key "os_type". As with lists, a default value must be appended (after a colon) that is returned if the given key does not exist. Again it should have the type that is expected for the current access, in this case the string “linux”.

You may have noticed that the syntax for accessing maps kind of resembles that of accessing lists. This is due to the fact that lists are realized as maps internally with constant keys 0, 1, 2, and so on.

Note: There is also another method for accessing maps, originating from the early days of YaST. The command lookup(my_map, "os_type", "linux") also returns the value associated with the given key. While this still works, it is deprecated by now and may be dropped in the future.

2.10. Data Type path

A path is something special to YCP and similar to paths in TCL. It is a sequence of path elements separated by dots. A path element can contain any characters except \x00. If it contains something else than a-zA-Z0-9_- it must be enclosed in double quotes. The root path, i.e. the root of the tree is denoted by a single dot. Paths can be used for multiple purposes. One of their main tasks is the selection of data from complex data structures like the SCR-tree (see Chapter 2, SCR Tree).

The backslash in paths can be used to mark a special characters:

RepresentationMeaning
\nNewline (ASCII 10)
\tTabulator
\rCarriage Return (ASCII 13)
\bBackspace
\fForm Feed
\xXX ASCII character represented by the hexadecimal value XX.
\XThe character X itself.

Example 2.8. Path constants

.
.17
.etc.fstab
."\nHello !\n".World

."\xff" == ."\xFF"
."\x41" == ."A"
."" != .

2.11. Data Type term

A term is something you won't find in C, Perl, Pascal or Lisp but you will find it in functional programming languages like Prolog for example. It is a list plus a symbol, with the list written between normal brackets. The term `alpha(17, true) denotes a symbol `alpha and the list [ 17, true ] as parameters for that symbol. This looks pretty much like a function call.

You can also use the term as a parameter in another function call, for example to specify a user dialog.

Example 2.9. Term constants

`like_function_call(17, true)
`HBox(`Pushbutton(`Id(`ok), "OK"), `TextEntry(`Id(`name), "Name"))

2.12. Data Type any

In the previous sections you have seen the data types YCP knows about. In most cases you will (and should) assign a certain data type to every variable you declare. However, there might be cases when the type of a variable is not really clear at coding time, e.g. (in some rare cases) if you access the SCR to get some hardware data. While you should try very hard to avoid this situation, there might be cases where you can't.

To solve this problem, you may assign the type any to your variable which makes it accept assignments of any other valid type. However, because variables of type any are highly deprecated in YaST by now, this “feature” will eventually be dropped in the near future.

Chapter 3. More YCP types

Table of Contents

3.1. Data Type block

Section not written yet...

3.1. Data Type block

Basically a block is a sequence of YCP statements enclosed in curly brackets. It can be a whole YCP program as was the case with the outermost block in hello.ycp from Section 1.1, “YCP Source”. What is special about blocks in YCP is that they represent a value and therefore can be assigned to a variable. It is sometimes really useful to have those blocks as YCP values because this makes it possible to use them as parameters to function calls. Of course the syntactical structure of blocks can become rather complex which leads to a description of the whole language itself. Therefore we put this into a section of its own: Chapter 8, YCP Program Structure.

For now the following examples should suffice.

Example 3.1. Block constants

{ return 17; }
{ integer a = 5; return a + 8; }

Chapter 4. YCP Type System

Section not written yet...

4.1. Data Type any And Type Checking

In Data type any you have seen the data type any. Because the value of type any can not be assigned to a variable of any other type. FIXME. So it is important to check its type with is(...) and then re-assigning it to a variable of the correct type. The following example shows how this should be done.

Example 4.1. Type checking and data type any

//
// Hypothetical example:
// ---------------------
// We don't know whether the SCR will return integers or floats...
//
any     any_var   = 0;
integer int_var   = 0;
float   float_var = 0.0;
boolean int_case  = false;

any_var = SCR::Read(...);

if ( is( any_var, integer ) )
{
   int_var  = any_var;
   int_case = true;
}
else if ( is( any_var, float ) )
{
   float_var = any_var;
   int_case  = false;
}
else
{
   // Error...
}

if ( int_case )
{
   // Use int_var...
}
else
{
   // Use float_var...
}
          

As this is very cumbersome, you should try to avoid this oddity in any case. If it is ineluctable, do it as shown above to stay compatible with future YaST behavior.

Chapter 5. YCP Expression Evaluation

From the interpreters point of view any YCP value is an expression and thus can be evaluated. How the evaluation is done in a particular case depends on the data type of the expression.

Because the block data type is somewhat special with respect to evaluation it will be explained first. The other basic data types will follow thereafter.

5.1. Evaluation Of Blocks

A YCP block is a sequence of statements enclosed in curly brackets. Upon evaluation (execution), all the statements in the block are evaluated one by one. Because blocks are also a valid data type in YCP, they can have a value (see Data type block). If a block contains the special statement return(...), then the returned value replaces the block upon evaluation.

The following code sample shows a block with some statements.

{
   integer n = 1;

   while (n <= 10)
   {
      y2milestone("Number: %1", n);
      n = n + 1;
   }

   y2milestone("Returned number: %1", n);

   return n;
}
        

It calculates the numbers 1 through 10 and prints these numbers into the log file. The statement y2milestone(...) used for this is explained in YaST2 Logging along with YCP-logging as such. For now we are interested in the output that is written to the log file. As can be seen below the loop is executed 10 times and the counter has the value 11 after the loop. Finally the last statement return(...) determines the value of the whole block, in this case 11.

...ycp/block_01.ycp:6 Number: 1
...ycp/block_01.ycp:6 Number: 2
...ycp/block_01.ycp:6 Number: 3
...ycp/block_01.ycp:6 Number: 4
...ycp/block_01.ycp:6 Number: 5
...ycp/block_01.ycp:6 Number: 6
...ycp/block_01.ycp:6 Number: 7
...ycp/block_01.ycp:6 Number: 8
...ycp/block_01.ycp:6 Number: 9
...ycp/block_01.ycp:6 Number: 10
...ycp/block_01.ycp:10 Returned number : 11
        

5.2. Evaluation Of Basic Data Types

The basic YCP data types we got to know in Chapter 2, YCP Data Types are evaluated in a rather straight forward way as will be shown in the following list.

Evaluation of basic data types

  • Simple data types

    Most of the YCP data types can't be “evaluated” at all, as they simply evaluate to themselves. This holds for the simple types void, boolean, integer, float, string, symbol and path.

  • list

    When evaluating a list, the interpreter evaluates all the list elements thereby forming a new list.

    {
       list list_var = [ 1 + 1, true || false, "foo" + "bar" ];
    
       y2milestone("list_var: %1", list_var );
    }
                

    yields the log file entry

    ...ycp/list_eval.ycp:4 list_var: [2, true, "foobar"]
                
  • map

    A map is handled similar to a list. The values (but not the keys) are evaluated to form a new map.

    {
       map map_var = $[ "one" : `one, `two : "one" + "one" ];
       
       y2milestone("map_var: %1", map_var );
    }
                

    yields the log file entry

    ...ycp/map_eval.ycp:4 map_var: $["one":`one, `two:"oneone"]
                
  • term

    Upon evaluation, term parameters are evaluated to form a new term.

    {
       term term_var = `val ( 1 + 1, true || false, "foo" + "bar" );
    
       y2milestone("term_var: %1", term_var );
    }
                

    yields the log file entry

    ...ycp/term_eval.ycp:4 term_var: `val(2, true, "foobar")
                

All the evaluations we have seen above are closely related to operators that may be used within an expression to act on the variables. The next section will give an overview of the operators that can be used in YCP.

Chapter 6. YCP Operators

As any other programming language YCP knows a lot of operators that can be used to act on data.

6.1. Comparison Operators

These are binary operators for comparison of two values. The result is always boolean.

OperatorDatatypeDescription
==almost allTrue if operands are equal, otherwise false.
<almost allTrue if left operand is smaller than the right one, otherwise false.
>almost allTrue if left operand is greater than the right one, otherwise false.
<=almost allTrue if left operand is smaller or equal to the right one, otherwise false.
>=almost allTrue if left operand is greater or equal to the right one, otherwise false.
!=almost allTrue if operands are not equal, otherwise false.

6.2. Boolean Operators

These are logical operators, that works with boolean datatype, two are binary one is unary. The result is always boolean.

OperatorDatatypeDescription
&&booleanTrue if both operands are true, otherwise false (logical and).
||booleanTrue if at least one of the operands is true, otherwise false (logical or).
!booleanTrue if the operand if false, otherwise false (logical not).

6.3. Bit Operators

These are bit operators that works with integer, two are binary one is unary. The result is always integer.

OperatorDatatypeDescription
&integerBits of the result number are product of the bits of the operands (bit and).
|integerBits of the result number are count of the bits of the operands (bit or).
~integerBits of the result number are reverted bits of operand (bit not).
<<integerBits of the result number are left shifted bits of the operands (bit shift left).
>>integerBits of the result number are right shifted bits of the operands (bit shift right).

6.4. Math Operators

There math operators works with numeric data types (integer and float) and also with string. All are binary (except unary minus).

OperatorDatatypeDescription
+integer, float, stringThe result is sum of the numbers or concatenation of the strings.
-integer, floatThe result is difference of the numbers.
*integer, floatThe result is product of the numbers.
/integer, floatThe result is quotient of the numbers (number class is preserved, thus quotient of integers produce integer, etc).
%integerThe result is modulo.
unary -integer, floatThe result is negative number.

6.5. Triple Operator

This is the operator known from C language ( condition ? expression : expression). The first operand is expression that can evaluate to boolean, types of second and third operands are code dependent. The result of the triple operator expression is the second operand in the case the first operand (condition) evaluates to true, the third one otherwise.

CodeResultComment
(3 > 2) ? true : falsetrueThe expression (3 > 2) evaluates to true, the result is true
contains ([1, 2, 3], 5) ? "yes" : "no""no"The expression contains ([1, 2, 3], 5) evaluates to false, the result is "no"
(size ([]) > 0) ? 1 : -1-1The expression size ([]) > 0 evaluates to false, the result is -1
[Note]Note

Using brackets makes code cleaner, but is not necessary (according to operators precedence).

[Note]Note

With the introduction of the index operator ( a = mapvar["key"]:default ), the sequence "]:" became a lexical token usable only for indexing, so watch out when using the triple operator with lists and maps. Use parentheses or white space.

6.6. Operators Precedence

The table of operators precedence (from lowest to highest).

 Direction  Operators 
right=
left?:
left||
left&&
left== !=
left< <= > >=
left+ -
left* / %
left<< >>
left|
left&
prefix! ~ -

6.7. The bracket operator

6.7.1. Introduction

The bracket operator is the use of '[' and ']' like accessing arrays in C.

In YCP, this operator is used to ease handling with (possibly nested) lists and maps.

The bracket operator can be applied to any list or map variable and should be used in favour of (deeply) nested lookup() and select() cascades.

6.7.2. Access variant

The access variant of the bracket operator is used for accessing elements of a list or a map. It effectively replaces select for lists and lookup for maps.

6.7.2.1. Accessing lists

General syntax:

for simple lists:

<list-var>[<index>]:<default-value>

for nested lists:

<list-var>[<index>,<index> <, ...>]:<default-value>

index must be an integer and counts from 0 up to the number of elements-1.

It will return the default-value if you try to access an out-of-bounds element.

[Important]Important

Note that there must be no space between the closing bracket and the colon.

Examples:

{
    list list_of_numbers = [1, 2, 3];

    // value of element with index 2 is 3
    integer three = list_of_numbers[2]:0;

    // evaluates to the default value 0,
    // because list element with index 42 doesn't exist
    integer zero = list_of_numbers[42]:0;

    // evaluates to 4
    integer number = [3, 4, 5][1]:8;

    list list_of_lists = [[1,2], [3,4], [5,6]];

    // list_of_lists[1] -> [3,4]
    // [3,4][0] -> 3
    //
    // returns true because the left side evaluates to 3
    // just as the right side does (three == 3)
    return (list_of_lists[1,0]:0 == three);
}

6.7.2.2. Accessing maps

General syntax:

for simple maps:

<map-var>[<key>]:<default-value>

Examples:

{
    // map with string as a key, integer as a value
    map simple_map = $["a":1, "b":2, "c":3];

    // evaluates to 3
    integer three = simple_map["c"]:0;

    // evaluates to 0
    integer zero = simple_map["notthere"]:0;
}
{
    // map with string as a key, integer as a value
    // this example also defines the data-types
    map <string, integer> simple_map = $["a":1, "b":2, "c":3];

    // evaluates to 3
    integer three = simple_map["c"]:0;

    // evaluates to 0
    integer zero = simple_map["notthere"]:0;
}

for nested lists:

<map-var>[<key>,<key> <, ...>]:<default-value>

key must have an allowed type for maps, integer, string, or symbol.

It will return default-value if you try to access an non existing key.

[Important]Important

Note that there must be no space between the closing bracket and the colon.

Examples:

{
    // map with string as a key and another map as a value
    map nested_map = $[
	"a":$[1:2],
	"b":$[3:4],
	"c":$[5:6]
    ];

    // nested_map["b"] -> $[3:4] ("b" is a key of the map)
    // $[3:4][3] -> 4            ( 3  is a key of the map)
    //
    // returns true
    return (nested_map["b",3]:0 == 4);
}
{
    // map with string as a key and another map as a value
    // this map has a defined data-type
    map <string, map <integer, integer> > nested_map = $[
	"a":$[1:2],
	"b":$[3:4],
	"c":$[5:6]
    ];

    // returns true
    return (nested_map["b", 3]:0 == 4);
}

6.7.2.3. Mixed map/list access

Since the bracket operator applies to list and maps, you can use it to access nested lists and maps. But be careful not to mix up the index/key types.

Examples:

{
    map <string, list <integer> > map_of_lists = $[
	"a":[1, 2, 3, 4],
	"b":[5, 6],
	"c":[7, 8, 9]
    ];
    // evaluates to 3
    integer three = map_of_lists["a", 2]:0;

    list <map <integer, integer> > list_of_maps = [
	$[1:2],
	$[3:4],
	$[5:6]
    ];
    // returns true
    return (list_of_maps[1,0]:0 == three);
}

6.7.3. Assign variant

The bracket operator can also be used on the left side of an assignment (lvalue). This changes the list or map element in place (!!) and must be used with care.

If the map or list element does not exist, it will be created. The bracket operator can therefore replace add and change.

Creating a new list element will extend the size of the list. Holes will be filled with nil. See the examples below.

If used as an lvalue, the default value is not allowed.

Examples:

{
    list numbers = [1, 2, 3];

    // changes the second element
    // numbers == [1, 25, 3] now
    numbers[1] = 25;

    // extends the list to 7 elements (0-6)
    // numbers == [1, 25, 3, nil, nil, nil, 6] now
    numbers[6] = 6;

    // remove an element item with index 2
    // numbers == [1, 25, nil, nil, nil, 6] now
    numbers = remove (numbers, 2);
}
{
    map <string, integer> new_map = $["a":1, "b":2, "c":3];
    // changes the "c" element
    // new_map == $["a":1, "b":2, "c":42] now
    new_map["c"] = 42;

    // add a new element to m
    new_map["zz"] = 13;

    // remove an elment "a"
    // new_map == $["b":2, "c":42, "zz":13] now
    new_map = remove (new_map, "a");

    // returns $["b":2, "c":42, "zz":13]
    return (new_map);
}

Chapter 7. Data Type locale

A string that is localized by YaST2 via the gettext mechanism (see gettext and ngettext in libc for more informations). Basically a string to be translated via gettext must be enclosed in _(...) which causes gettext to look up the translated string. It is even possible to distinguish singular and plural verbalizations depending on a parameter that denotes the actual number of something.

For a simple number-independent string you write _(some_string_constant) which causes its translation.

If the string to be translated needs to be different depending on the multiplicity of something then you write _(singular_string_constant1, plural_string_constant2, actual_number). If actual_number equals 1 then the translation of the first string is used. Otherwise the translation of the second string is used.

Note 1: There are languages that distinguish more than the two cases singular and plural. Principally gettext can handle even those cases as it allows more than two strings for selection, but that is beyond the scope of this document (see the gettext documentation).

Note 2: It is not possible to put something other than string constants between the brackets.

Example 7.1. Locale constants

_("Everybody likes Linux!")
_("An error has occurred.", "Some errors have occurred", error_count)

Chapter 8. YCP Program Structure

Now that we have learned how data can be stored and evaluated in YCP, we will take a look at the surrounding code structure that can be realized. Code structure is created by means of blocks and statements.

8.1. Comments

Despite not being “reallystatements, comments do (and should) belong to the overall structure of a YCP program. There are two kinds of comments:

  • Single-line comments

    Single-line comments may start at any position on the line and reach up to the end of this line. They are introduced with “//”.

  • Multi-line comments

    Multi-line comments may also start at any position on the line but they may end on another line below the starting line. Consequently there must be a start tag (“/*”) and an end tag (“*/”) as is shown below.

Example 8.1. Comments

{
    // A single-line comment ends at the end of the line.

    /*
      Multi-line comments
      may span several lines.
    */

    y2milestone("This program runs without error");
}

8.2. Variable Declaration

Synopsis: data_type variable_name = initial_value;

Variable declarations in YCP are similar to C. Before you can use a variable, you must declare it. With the declaration you appoint the new variable to be of a certain data_type which means you can assign only values of that specific type. To avoid any errors caused by uninitialized variables, a declaration must imply a suitable value assignment.

Note: A variable declaration may occur at several points in the code which determines its validity (accessibility) in certain program regions (see Section 8.15, “Variable Scopes and blocks”).

Example 8.2. Variable Declaration

{
    integer int_num     = 42;
    float   float_num   = 42.0;
    string  TipOfTheDay = "Linux is the best!";
    integer sum         = 4 * (int_num + 8);
}

8.3. Variable Assignment

Synopsis: variable_name = value;

An assignment statement is almost the same as a declaration statement. Just leave out the declaration. It is an error to assign a value to a variable that has not already been declared or to a variable of different data type.

Example 8.3. Variable Assignment

{
    integer number = 0;

    number = number + 1;
    number = 2 * number;
    number = "Don't assign me to integers!";     // This will cause an error!!!
}

8.4. Conditional Branch

Synopsis: if (condition) then_part [ else else_part ]

Depending on condition only one of the code branches then_part and else_part is executed. The else_part is optional and may be omitted. Both then_part and else_part may either be single statements or a sequence of statements enclosed in curly brackets, i.e. a block. The then_part is executed if and only if condition evaluates to true, the else_part otherwise. It is an error if condition evaluates to something other than true or false.

Example 8.4. Conditional branch

{
    integer a = 10;

    if ( a > 10 )
	y2milestone("a is greater than 10");
    else
    {
	// Multiple statements require a block...
	
	y2milestone("a is less than or equal to 10");
	a = a * 10;
    }
}

Example 8.5. Conditional branch with "else if"

{
    list &string& new_list = ["a", "new", "string"];

    if (contains(new_list, "test"))
	y2milestone("this is a test");

    else if (size(new_list) > 100)
	y2error("Too big list!");

    else if (contains(new_list, "string"))
	y2milestone("this is a new list");

    else
	y2error("Undefined behavior for list %1", new_list);
}

8.5. while() Loop

Synopsis: while (condition) loop_body

The while() loop executes the attached loop_body again and again as long as condition evaluates to true. The loop_body may be either a single statement or a block of statements.

Because condition is checked at the top of loop_body, it may not be executed at all if condition is false right from start.

Example 8.6. while() Loop

{
    integer a = 0;

    while (a < 10) a = a + 1;
 
    while (a >= 0)
    {
	y2milestone("Current a: %1", a);
	a = a - 1;
    }
}

8.6. do..while() Loop

Synopsis: do loop_body while (condition);

The do...while() loop executes the attached loop_body again and again as long as condition evaluates to true. The loop_body may be either a single statement or a block of statements.

Because condition is checked at the bottom of loop_body, it is executed at least once, even if condition is false right from start.

Example 8.7. do...while() Loop

{
    integer a = 0;

    do
    {
	y2milestone("Current a: %1", a);
	a = a + 1;
    } while (a <= 10);
}

8.7. repeat..until() Loop

Synopsis: repeat loop_body until (condition);

The repeat...until() loop executes the attached loop_body again and again as long as condition evaluates to false. The loop_body may be either a single statement or a block of statements.

Because condition is checked at the bottom of loop_body, it is executed at least once, even if condition is true right from start.

repeat...until() is similar to do...while() except that condition is logically inverted. The example below has been converted from the do...while() example.

Example 8.8. repeat...until() Loop

{
    integer a = 0;

    repeat 
    {
	y2milestone("Current a: %1", a);
	a = a + 1;
    } until (a > 10);
}
	  

8.8. break Statement

Synopsis: break;

The break statement is used within loops to exit immediately. The execution is continued at the first statement after the loop.

Example 8.9. break statement

{
    integer a = 0;

    repeat 
    {
	y2milestone("Current a: %1", a);
	a = a + 1;
	if (a == 7) break;		// Exit the loop here, if a equals 7.
    } until (a > 10);		// Value 10 will never be reached.

    y2milestone("Final a: %1", a);	// This prints 7.
}

Example 8.10.  break statement in foreach

{
    foreach (string text, ["a", "new", "string", "break"], {
	// finishes the foreach loop
	if (text == "string") break;
	
	// "string" and "break" will never get here
	y2milestone("Current text is '%1'", text);
    });
}

Example 8.11.  Nice break statement in foreach

{
    // list of found prime-number
    list <integer> found = [];
    // start with number
    integer number = 2;
    // finish with number
    integer max_number = 20000;
    
    while (number < max_number) {
	boolean not_found = true;
	
	// try all already found numbers
	foreach (integer try, found, {
	    if (number % try == 0) {
		not_found = false;
		break;
	    }
	});
	
	if (not_found)
	    found = add (found, number);

	number = number + 1;
    }
    
    y2milestone("Sum:   %1", size(found));
    y2milestone("Found: %1", found);
}

Example 8.12.  break statement in listmap

{
    integer counter = 0;

    // goes through the list and returns a map
    // made of this list
    map new_map = listmap (string text,
        ["a", "new", "string", "break"], 
    {
        // finishes the listmap loop
        if (text == "string") break;

        counter = counter + 1;
	// returns one "key : value" pair of the new map
        return $[text : counter];
    });
    
    y2milestone("Returned map: %1", new_map);
}

[Note]Note

The break statement can be used for all loop statments and also statments: listmap, mapmap, list-based maplist, map-based maplist, list-based foreach, map-based foreach, list-based filter, map-based filter.

8.9. continue Statement

Synopsis: continue;

The continue statement is used within loops to abandon the current loop cycle immediately. In contrast to break it doesnt exit the loop but jumps to the conditional clause that controls the loop. So for a while() loop, it jumps to the beginning of the loop and checks the condition. For a do...while() loop or repeat...until() loop, it jumps to the end of the loop end checks the condition.

Example 8.13.  continue statement in while

{
    integer a = 0;

    while (a < 10)
    {
	a = a + 1;
	if (a % 2 == 1) continue;     // % is the modulo operator.
	y2milestone("This is an even number: %1", a);
    }
}

Example 8.14.  continue statement in foreach

{
    // lists all files in /tmp directory
    map cmd = (map) SCR::Execute(.target.bash_output, "ls -1a /tmp");
    
    list <string> files = splitstring (
	(string) cmd["stdout"]:"", "\n"
    );
    
    // clearing memory
    cmd = nil;
    
    foreach (string filename, files, {
	if (regexpmatch(filename, "x")) {
	    y2warning("Filename '%1' contains 'x'", filename);
	}
	
	if (size(filename) > 20) {
	    y2error("Filename '%1' is longer than 20 chars", filename);
	    // we don't work with files with longer names
	    continue;
	}
	
	if (regexpmatch(filename, "^\.")) {
	    y2milestone("Filename '%1' starts with a dot", filename);
	} else {
	    y2milestone("Filename '%1' is what we are looking for", filename);
	}
    });
}

Example 8.15.  continue statement in listmap

{
    list <integer> random_integers = [];
    
    // filling up with random numbers
    while (size(random_integers) < 10) {
	random_integers = add (random_integers, random(32768));
    }
    random_integers = toset(random_integers);
    
    map <integer, integer> new_map = listmap (integer number, random_integers, {
	// do not proccess huge numbers
	// actually, this finished the listmap and returns nil
	if (number > 32000) {
	    y2error("A huge number has been found");
	    continue;
	}
	
	if (number % 3 == 0) {
	    return $[number : number / 3];
	} else {
	    return $[number : number * 3];
	}
    });
    
    y2milestone("New map: %1", new_map);
}

[Note]Note
The usage is similar to the break's one.

8.10. return Statement

Synopsis: return [ return_value ];

The return statement immediately leaves the current function or a current top level block (that contains it) and optionally assigns a return_value to this block. If blocks are nested, i.e. if the current block is contained in another block, the return statement leaves all nested blocks and defines the value of the outermost block.

However, if a block is used in an expression other than a block, and that expression is contained in an outer block, the return statement of the inner block won't leave the outer block but define the value of the inner block. This behavior is a as one would expect. For example in the iteration builtins in Section 8.16, “Applying Expressions To Lists And Maps”,

Example 8.16. return statement 1

{
    // This block evaluates to 42.
    return 42;

    y2milestone("This command will never be executed");
}

Example 8.17. return statement 2

{
    // This block evaluates to 18
    while (true)
    {
	return 18;
    }
}

Example 8.18. return statement 3

{
    // This program evaluates to 3:
    integer a = 1 + { return 2; };

    return a;
}

8.11. Function definition

Synopsis: data_type function_name ( [ typed_parameters ] ) function_body

A function definition creates a new function in the current namespace named function_name with a parameter list typed_parameters that has function_body attached for evaluation. The function_body must return a value of type data_type and the arguments passed upon function call must match the type definitions in typed_parameters.

Example 8.19. Function definition

{
    void nothing()
    {
	y2milestone("doing nothing, returning nothing");
    }

    integer half( integer value )
    {
	return value / 2;
    }

    map <string, integer> get_some_map () {
	return listmap (string key, ["a", "b", "c"], {
	    return $[key : random(999)];
	});
    }

    // This renders: ...nothing: nil  -  half: 21...
    y2milestone("nothing: %1  -  half: %2", nothing(), half(42) );
    
    // This renders something like: new half-random map: $["a":839, "b":393, "c":782]
    y2milestone("new half-random map: %1", get_some_map());
}

8.12. Function declaration

Synopsis: data_type function_name ( [ typed_parameters ] );

A function declaration allows you to declare only a header of a function without its body. It's main purpose is for indirect recursion etc. You have to provide a function definition with exactly the same arguments later in the same file. A new function will be declared in the current namespace named function_name with a parameter list typed_parameters.

Example 8.20. Function declaration

{
    // declares the function - it is defined later
    void nothing();

    integer half( integer value )
    {
	return value / 2;
    }

    // This renders: ...nothing: nil  -  half: 21...
    // uses the function nothing
    y2milestone("nothing: %1  -  half: %2", nothing(), half(42) );

    // defines the function
    void nothing()
    {
	y2milestone("doing nothing, returning nothing");
    }
}

8.13. include Statement

Synopsis: include " included file";

The include statement allows you to insert contents of a file at the given place in the current file. If the current file is a module, the contents of the included file will become a part of the module.

This is useful for dividing a large file into number of pieces. However, if a file is included more than once in a single block, the 2nd, 3rd etc. include statements are ignored.

The included file can be a relative or an absolute file name. Relative names are looked up with /usr/share/YaST2/include as a base.

Example 8.21. Include a file

// this will include /usr/share/YaST2/include/program/definitions.ycp

include "program/definitions.ycp";

8.14. import Statement

Synopsis: import "name_space";

The import statement allows you to import another namespace (module) into the one you are just running in. Then you can access the global functions and variables of that namespace using the double-colon.

Namespaces, to be imported, are looked up in the /usr/share/YaST2/modules/ directory.

{
    import "Hostname";
    import "IP";
    
    // writes: Check FQDN: true
    y2milestone("Check FQDN: %1", Hostname::Check("www.example.com"));

    // writes: Check IP4: false
    y2milestone("Check IP: %1", IP::Check4("192.168.0.356"));
    
}

This is often used for clients using some global API of modules but also for modules using global API of another ones. Please, be careful on creating cross-dependencies when creating an RPM package from sources.

8.15. Variable Scopes and blocks

In contrast to many other programming languages, YCP variables can be defined at (almost) any point in the code, namely between other statements. Given that, there must be some rules regarding the creation, destruction and validity of variables. Generally variables are valid (accessible) within the block they are declared in. This also covers nested blocks that may exist in this current block. The valid program region for a variable is called a scope.

Example 8.22. Variable scopes and blocks

{
    // Declared in the outer block
    integer outer = 42;

    {
	// Declared in the inner block
	integer inner = 84;

	// This is OK.
	// Log: ...IN: inner: 84 - outer: 42
	y2milestone("IN: inner: %1 - outer: %2", inner, outer);
    }

    // This yields an error because "inner" is not defined any more.
    y2milestone("OUT: inner: %1 - outer: %2", inner, outer);
}

8.16. Applying Expressions To Lists And Maps

Additionally to the structural language elements described so far, there are special commands that apply to lists and maps. What is special about these commands is that they apply an expression to the single elements of a list or map. This is done in a functional manner, i.e. the expression to be applied is passed as a parameter. Generally this executes faster than a procedural loop because the internal functionality is realized in a very effective way.

Furthermore some of these commands create lists from maps, maps from maps, maps from lists etc., so that they can be used to avoid the cumbersome assembling of these compound data types in a procedural loop.

8.16.1. foreach() Statement

Synopsis (list): any foreach ( type variable, list<type>, { expression } );

Synopsis (map): any foreach( type_key variable_key, type_value variable_value, map<type_key, type_value>, { expression } );

This statement is a means to process the content of list or map in a sequential manner. It establishes an implicit loop over all entries of the list or map thereby executing the given expression with the respective entries. With lists the variable is a placeholder for the current entry. With maps, variable_key and variable_value are substituted for the respective key-value-pair.

[Note]Note

The return value of the last execution of expression determines the value of the whole foreach() statement.

Example 8.23. foreach() Loop

{
    // Exemplary foreach for list
    // This yields 3
    foreach(integer value, [1, 2, 3], { return value; });

    // Exemplary foreach for map
    // This yields 9
    foreach(integer key, integer value, $[1:1, 2:4, 3:9], {
	    y2milestone("value: %1", value);
	    return value;
    });
}

Example 8.24. Sophisticated foreach() Loop

{
    list <string> codes = ["X1", "D", "vT", "o", "T5h8"];
    
    list <string> one_letter_codes = [];
    list <string> other_codes = [];
    
    foreach (string code, codes, {
	// all one-letter codes
	if (size(code) == 1) {
	    one_letter_codes = add (one_letter_codes, code);
	// other ones
	} else {
	    other_codes = add (other_codes, code);
	}
    });
    
    // Results in: ["D", "o"]
    y2milestone("One-letter codes: %1", one_letter_codes);
    
    // Results in: ["X1", "vT", "T5h8"]
    y2milestone("Other codes: %1", other_codes);
}

8.16.2. listmap() Statement

Synopsis: map<type1, type2> listmap( type3 variable, list<type3>, { expression returning map<type1, type2> } );

This statement is a means to process the content of list in a sequential manner. It establishes an implicit loop over all entries in list thereby executing the given expression with the respective entry. During execution variable is a placeholder for the current entry. For each element in list the expression is evaluated in a new context. The result of each evaluation MUST be a map with a single pair of key-value. All the returned key-value-pairs are assembled to form the new map that is returned.

Example 8.25. listmap() statement

{
    // This results in $[1:"xy", 2:"xy", 3:"xy"]
    map <integer, string> m1 = listmap (integer s, [1, 2, 3], {
	return $[s: "xy"]
    });

    // This results in $[11:2, 12:4, 13:6]
    map <integer, integer> m2 = listmap (integer s, [1, 2, 3], {
	integer a = s+10;
        integer b = s*2;
	list ret = [a, b];
        return ret;
    });

    y2milestone("map 1: %1  - map 2: %2", m1, m2);
}

8.16.3. maplist() Statement

Synopsis (map): list<type1> maplist( type2 key, type3 value, map<type2, type3>, { block returning type1 } );

Synopsis (list): list<type1> maplist( type2 variable, list<type2>, { block returning type1 } );

This statement is a means to process the content of map or list in a sequential manner. It establishes an implicit loop over all entries in map or list thereby executing the given expression with the respective entries. With lists the variable is a placeholder for the current entry. With maps, key and value are substituted for the respective key-value-pair. For each element the expression is evaluated in a new context. All return values are assembled to form the new list that is returned.

[Note]Note

To exit the loop before it ends, use the break. See the usage in the break statement description.

To skip to the next loop step, use the continue. See the usage in the continue statement description.

Example 8.26. maplist() statement

{
    // This results in [2, 4, 6]
    list<integer> l1 = maplist (integer s, [1, 2, 3], {
	reuturn s*2;
    });

    // This results in [2, 6, 12]
    list<integer> l2 = maplist (integer k, integer v, $[1:2, 2:3, 3:4], {
	return k*v;
    });

    y2milestone("list 1: %1  - list 2: %2", l1, l2);
}

8.16.4. mapmap() Statement

Synopsis: map<type1, type2> mapmap( type3 key, type4 value, map<type3, type4>, { expression returning map<type1, type2> } );

This statement is a means to process the content of map in a sequential manner. It establishes an implicit loop over all entries in map thereby executing the given expression with key and value substituted for the respective key-value-pair. For each map element the expression is evaluated in a new context. The result of each evaluation MUST be a map with a single pair of key-value. All the returned key-value-pairs are assembled to form the new map that is returned.

Example 8.27. mapmap() statement

{
    // This results in $[11:"ax", 12:"bx"]
    map<integer,string> m = mapmap (integer k, string v, $[1:"a", 2:"b"], {
	return [k+10, v+"x"];
    });

    y2milestone("map: %1", m);
}
            

Chapter 9. Controlling The User Interface

In the previous sections we have already seen some YCP code that dealt with the creation and handling of on-screen dialogs. These examples were rather simple to show the basic strategy of creating dialogs. Of course designing “real” dialogs that do useful things is is bit more complicated and requires a rather good knowledge of the instruments provided by the UI.

Because the UI has been designed to be most flexible, the possibilities for creating and managing dialogs are quite versatile. Consequently the instruments for doing this are rather diverse. In fact the UI extends the basic YCP language to a large extent, thereby providing the means to create and manage on-screen dialogs.

For more information about the User Interface, handling events see the Layout HOWTO.

Chapter 10. The YaST Wizard

In the previous section the basic mechanisms suitable for managing on-screen dialogs were presented. However, creating dialogs for each and every application from scratch would be cumbersome and moreover is unnecessary. For example the well-known layout that is presented with nearly every YaST dialog during installation was not programmed anew for each dialog. Rather it is kind of imported from a special YCP module, the Wizard that provides all the functionality necessary to create uniform dialogs.

Furthermore, as time went by, during the development of the YaST installer, the developers encountered situations where the same (or similar) tasks ofttimes had to be accomplished at different locations in the overall program flow. For example opening a popup to ask the user a question with the predefined buttons “Yes” and “No” is a procedure used very often.

This led to the development of predefined dialog elements that can (and should) be included in the current YCP source. Displaying the Yes-No-popup from the example above is then reduced to calling a function with respective parameters. Aside from avoiding the need to redevelop such things again and again, another benefit is the ever same visual appearance that adds up to the well-known YaST look-and-feel. Meanwhile there are also many functions that are not UI-related but nonetheless very useful.

The omnium gatherum of all these elements has been collected to form the so-called “YaST Wizard”. In short the YaST Wizard consists of one YCP module that provides the layout framework used in the installation dialogs and some additional YCP modules that provide access to several common dialog elements needed rather often. Many “generic” functions are at hand as well.

The following two sections cover these topics mostly by means of references to the YaST developers documentation.

Chapter 11. Running y2base Stand-Alone

In the previous section you got to know how to do a YaST program. Well, normally YCP-scripts are executed involving the whole YaST-machinery, e.g. during installation, which requires correct embedding of the script into the surrounding YCP environment. Fortunately there is a way to let run YCP-scripts isolated, i.e. stand-alone.

To do so we make use of the architectural separation of components featured by YaST. The “command line version” of YaST is called y2base and can usually be found in /usr/lib/YaST2/bin. You could set the PATH to include this location to avoid typing in the full path every time.

$> y2base -h

Usage: y2base [LogOpts] Client [ClientOpts] Server [Generic ServerOpts] [Specific ServerOpts]
LogOptions are:
    -l | --logfile LogFile    : Set logfile
ClientOptions are:
    -s                        : Get options as one YCPList from stdin
    -f FileName               : Get YCPValue(s) from file
    '(any YCPValue)'          : Parameter _IS_ a YCPValue
Generic ServerOptions are:
    -p FileName               : Evaluate YCPValue(s) from file (preload)
    '(any YCPValue)'          : Parameter _IS_ a YCPValue to be evaluated
Specific ServerOptions are any options passed on unevaluated.

Examples:
y2base installation qt
    Start binary y2base with intallation.ycp as client and qt as server
y2base installation '("test")' qt
    Provide YCPValue '"test"' as parameter for client installation
y2base installation qt -geometry 800x600
    Provide geometry information as specific server options

This help page, showing the possible options in a call of y2base, is rather self-explaining. For the moment the interesting parameters are Client and Server. In Section 2.4, “External Programs” we learned that YaST consists of several modules, some of them being client-components and some others being server-components. By invoking YaST in the way displayed above we can connect any client-component with any server-component.

Because a YCP-program (also called YCP-module) can act as a client-component, it is possible to connect it with a server-component suitable of executing it. Since our “Hello, World!”-program displays something on screen, we need to use the UI as server-component in this case. As already said the UI is able to use a text-based console environment as well as a graphical X11 environment which leads to the following two methods of running a YCP-script.

  • y2base file.ycp qt

    This will execute file.ycp in the graphical Qt-UI.

  • y2base file.ycp ncurses

    This will execute file.ycp in the text-based NCurses-UI.

Access to the System (SCR in General)


List of Figures

2.1. SCR Hierarchy Tree

List of Tables

2.1. SCR Node Types
3.1. The SCR-commands

In the introductory chapter we have already heard something about accessing the system with SCR. Because manipulating the system at the lowest layer is all YaST is about, we now want to take a closer look at this topic.

Basically the SCR creates a consistent view of the system hardware and its configuration files. There are many dependencies between the different entities among those data and these dependencies have to be taken into consideration when manipulating them. For this being possible in a convenient way for the higher-level modules there must be an easy and consistent accessing method. This method is provided by the SCR as it presents kind of an abstraction of the various types of data to be handled.

Now the data “landscape” that must be covered here is rather heterogeneous. Hardware data and configuration data, both of most multifaceted type can hardly be handled by one single monolithic program. Therefore the SCR consists of a “head” that is accompanied by various helper programs, the so-called agents, each of them being specialized on a specific task.

Chapter 1. SCR Agents

For each category of system data there is a corresponding SCR-agent. Their job is to map the real system data to YCP-data structures so that YCP-modules can access them in a convenient way. In fact the SCR-agents provide the YCP data structures. They come into existence with the presence of an SCR-agent that provides them. Otherwise they wouldn't be there.

For example there is an agent that reads and writes the /etc/sysconfig files. The YCP-representation of a sysconfig-variable is a single YCP-string. When reading, the agent reads the variable in the corresponding file and creates a YCP-string from it. When writing, the agent gets the new value as YCP-string and changes the variable in the corresponding file accordingly.

It must be said here, that the set of agents may change over time. New agents may be created in the future and other ones might be abandoned if their functionality is obsolete or taken over by another agent. Generally this is no problem because for module development it is not (and should not be) necessary to know exactly which agent does what. As already said, the SCR provides an abstraction of the data to be handled and this abstraction comes into being in form of a tree, the SCR-tree.

Chapter 2. SCR Tree

As a computer's hardware and software configuration is quite complex, the SCR organizes all data in form of a tree. It resembles very much a file system with its folders, sub-folders and files whereby the tree structure reflects the thematic separation of the various configuration categories.

The SCR-tree consists of two different kinds of nodes:

Table 2.1. SCR Node Types

Data nodes Data nodes represent single pieces of data, for example a sysconfig-entry or a mountpoint of a file system in /etc/fstab. They are the leaves of the tree and stand for actual data to be handled.
Map nodes Map nodes allow for navigation to the leaves just like the path components in the directory structure of a file system. This way map nodes are used to structure the data in a suitable manner.

The names of the nodes in the SCR-tree can be concatenated resulting in the creation of an SCR-path. An SCR-path is a description were to find a node in the SCR-tree. It is a sequence of path components each of them being a string. As we have seen in the section Data type path YCP-paths are prepended by dots (.) which act as separators in compound paths. So .foo.bar is a valid YCP-path. If bar is an SCR data node, then SCR::Read(.foo.bar) would render some data. If bar is a map node, then SCR::Dir(.foo.bar) would reveal the immediate sub-nodes in the SCR-tree, e.g. ["big", "brown", "fox"]. The single dot (.) is also valid and denotes the root of the whole SCR-tree. Consequently SCR::Dir(.) will return a list of all the top-nodes in the SCR-tree.

In the figure below we see a (very small) cut-out of the SCR-tree that is related to hardware-specific information.

Figure 2.1. SCR Hierarchy Tree

SCR Hierarchy Tree

The light gray nodes are SCR-map-nodes denoting the path to the data. They can be used with SCR::Dir(...) to find out what is below. So in the figure above SCR::Dir(.probe) would return a list as [..."has_smp", "boot_arch", "has_apm"...].

The dark grey nodes are SCR-data-nodes that stand for the actual data. What can be done with them depends on the actual node (reading, writing, executing), but usually SCR::Read(...) is possible. As is shown above SCR::Read(.probe.boot_arch) would return "grub".

Chapter 3. Accessing SCR

Now that we know how the SCR-landscape can be navigated we will take a look at how the data that is dug in there can be accessed. The accessing methods already implied above shall now be defined more precisely.

There are four accessing methods.

  • Reading

    We speak of reading, when the agent reads some configuration file or scans the system hardware and produces a YCP data structure representing this information.

  • Writing

    We speak of writing, when the agent gets some YCP data structure and creates or modifies some system configuration file according to these data.

  • Executing

    We speak of executing, when the agent gets some YCP data that can be interpreted as instruction and executes it. Usually this is being done by means of another program, e.g. the bash.

  • Dir

    Compared to the other accessing methods listed above, the Dir-command is somewhat special. It takes as argument an SCR-path that points to a specific node in the SCR-tree. It returns a list of all the sub-paths that are immediately below this node. This way it works just like a dir-command in a file system. For example if you apply this to the root of the SCR-tree (.), the answer would be a list with all the top nodes known by the SCR , e.g. ["audio", ... , "yast2"]. [1]

As a rule all SCR-agents implement some of the four accessing methods listed above. However depending on the task the agent was made for, not all of them may be provided.

For convenient use from YCP the accessing methods are realized by means of an API, i.e. a defined set of YCP-functions that are understood. You can call these functions from YCP if you prepend the commands with the name space identifier SCR:: which causes redirection to the SCR.

Table 3.1. The SCR-commands

FunctionWhat it does
Read(path p) -> any Reads the data represented by the node at path p. The value returned can be any YCP data type but it is always one single value.
Write(path p, any v) -> boolean Writes the value v to the node at path p. The boolean return value is true on success. On error the return value is false and a log entry is generated in the log file. Reasons for errors can be a mistyped value v or some problem with the periphery that lies behind the data-node.
Execute(path p) -> boolean This command is mostly used with the system-agent (see Chapter 6, Useful SCR Agents). Usually the return value indicates success or failure of the executed command.
Dir(path p) -> list(string) Returns a list of all subtree nodes immediately below the node p. For each such node the list contains a string denoting its name. If p does not point to a map node, i.e. the last path component is a leaf, the command will return an empty list or nil.



[1] Unfortunately not all SCR-agents do support this command properly. There may be agents that wrongly return an empty list or even nil when queried this way.

Chapter 4. Using SCR From Within YCP

FIXME: To be done... (Examples?)

Chapter 5. Using SCR From The Command Line

In the last section we saw some examples of how the SCR can be used from YCP. However if you only want to test or explore different SCR-paths, writing a YCP-script for every access can be cumbersome. Fortunately the SCR-component can be run individually on the command line of a terminal using a method very similar to the one we saw in Section 1.4, “Advanced YaST2 command line parsing”.

In contrast to the method demonstrated there, this time we don't feed a YCP-script into YaST. Instead we make another use of the architectural separation of components featured by YaST in that we connect the so-called stdio-component with the SCR-component. By doing so we can feed everything we type on the command line into the SCR.

However, because of the “raw” nature of the YaST-internal communication paths, this method is not very comfortable. You can't correct typos with Backspace or Del here (the SCR is not meant to be operated in this way). By doing so we kind of “simulateYaST-internal communication which normally forecloses any misspelling.

Furthermore, if you play around with the SCR in this manner you will be able to initiate privileged actions only if you are running the commands under the root-account.

[Caution]Caution

If you run “manual” SCR-commands under the root-account, the SCR will “gracefully” fulfill all your wishes. So be careful with Write and Execute!!!

Now operating the SCR this way can be shown best with some examples.

Example 5.1. Operating the SCR from the command line

$> /usr/lib/YaST2/bin/y2base stdio scr
([])
`Read(.probe.boot_arch)
("grub")
`Read(.probe.version)
("Oct  7 2002, 15:05:08")
`Read(.probe.has_smp)
(false)
`Read(.probe.has_apm)
(true)
`Read(.probe.boot_arch)
(nil)
	

As is shown above, the command y2base stdio scr starts YaST in a specific way. It connects the YaST client-component stdio with the server-component scr. After that the SCR is running and awaits any input on stdio which in this case is the console. To explore the content of the SCR-tree you can now enter any SCR-commands just as you would do in YCP. The only difference is the absence of the SCR:: name space identifier which must not be given here as can be seen in the last line.

Chapter 6. Useful SCR Agents

The SCR-world knows many agents for all sorts of tasks. Unfortunately this matter is subject to a rather high change service and not (yet) well documented. Therefore it is not easily possible to explain the details in a manner of “Which agent provides which paths for what reason?”. As a result only the most helpful agents are mentioned here along with references to the respective developers documentation.

Please note that even the developers documentation might be outdated to some extent. Consequently the most reliable source of information are the “real” files below /usr/share/YaST2/.

YCP Modules in General


Creating modules for YaST means extending its functionality. For this being possible it is necessary to follow the infrastructural and functional particularities of YaST as well as some guidelines regarding the interaction of the module with the user and the rest of the system. In the following we'll have a closer look at these topics .

Chapter 1. YCP Modules Overview

Throughout this document the term “YCP module” was mentioned repeatedly without providing a sharp definition. In fact the term “module” is used quite loosely in the YaST world, because there are several kinds of modules involved in different contexts. The following text shall lighten this topic.

Different kinds of YCP modules

  • Generic YCP modules

    In principle every YCP file that provides a distinct functionality can be seen as a module. Typical representatives of this kind of module are the inst_xxx.ycp files that are part of the YaST installer. Modules of this kind mostly represent rather self-contained functionality, e.g like inst_keyboard.ycp that provides the user dialog for selecting a keyboard during installation. These modules are usually called via CallFunction().

  • Library modules

    This kind of module can be seen as what is called a library in other programming languages. Usually these modules are a collection of functions that must be included to be used. As with other programming languages, including in YCP means merely text insertion that takes place each and every time an include is stated. This is often adverse with respect to speed and memory consumption.

  • True YCP modules

    This kind of module is the most interesting one. True modules represent an “object oriented” approach to module design. Because the mechanisms associated with them deserve some special mention, the next section will cover this topic in more detail.

Chapter 2. True YCP Modules

True modules are rather new in the YaST world and it is planned that they will replace the old method of including modules completely (with exception of some rare cases perhaps). The following sections will outline the differences between these concepts.

2.1. Included Modules

YCP, originally planned as a functional language, always did dynamic (i.e. runtime) binding of variables. Although useful in many cases, it's quite puzzling for someone used to “imperative” languages. So you could well program the following block and get an unexpected result.

{
   integer x = 42;

   define f() ``{ return x; }

   ... // lots of lines

   x = 55;

   return f();  // will return 55 because of runtime binding of x!
}
	

Another widely misused feature is to include global definitions. While there was no alternative as long as include was the only referencing instrument, this is certainly not a good programming practice in view of speed and memory considerations.

2.2. True Modules (Imported Modules)

In contrast to included modules, true modules have some distinct properties that are shown in the list below.

  • Definition-time bindings

    Definitions are evaluated in the sequence of the program flow.

  • One-time inclusion

    In contrast to include the import statement includes the module only once even if there are more than one import statement in the program flow. Later imports are silently ignored.

  • Proprietary global namespace

    The module definition implies a module declaration that determines the namespace of the module's global variable scope.

  • Local environment

    Aside from the data located in the module's global namespace all other data defined in the module is purely local, i.e. is invisible from the outside.

  • Module constructor function

    Each true module may have a constructor function that is automatically executed upon first import.

The following listing is a brief sample of a true module.

{
   // This is a module called "Sample".
   // Therefore the file name MUST be Sample.ycp
   // The "module" statement makes the module accessible for 'import'.
   //
   module "Sample";

   // This is a local declaration.
   // It can only be 'seen' inside the module.
   //
   integer local_var = 42;

   // This is a global declaration.
   // It can be accessed from outside with the name space identifier 'Sample::'.
   //
   global integer global_var = 27;

   // This is a global function.
   // It has access to global_var *and* local_var.
   //
   global define sample_f () ``{ return local_var + global_var; }
}
	

The module above can be used with the import statement. The syntax for file inclusion with import is similar to include. The interpreter automatically appends “.ycp” to the filename and searches below /usr/lib/YaST2/modules. If the filename starts with “./”, the file is loaded from the local directory. The global declarations of the module can then be accessed with the name space identifier Sample::.

[Note]Note

The file name must match the module declaration! Inside modules, only variable or function declarations are allowed. Stand-alone blocks or any kind of evaluation statements are forbidden.

{
    // This imports the 'Sample'-module.
    //
    import "Sample";

    // The global function is called with the respective name space identifier.
    //
    integer i = Sample::sample_f();     // == 69

    // No access to local module variables.
    //
    i = Sample::local_var;              // ERROR, no access possible !

    // No problem with global variables.
    //
    i = Sample::global_var;             // == 27

    Sample::global_var = 0;             // This variable is writable !!

    return Sample::sample_f();          // == 42, since global_var is 0
}
	
[Note]Note

The first encounter of the statement import "Sample"; triggers the loading of “Sample.ycp”. Subsequent import statements are ignored, because “Sample” is already defined. Consequently you can't replace a module during runtime !

2.3. True Modules And Constructors

If a global function with the same name as the module is defined, it is treated as a constructor. The constructor is called after the module has been loaded and evaluated for the first time. Because of this the constructor could (and should) be defined at the beginning of the module. Despite being located “on top” it can make use of the functions declared later in the file.

Module constructors are used mostly for initialization purposes, e.g. for setting local variables to proper values. However, the actions within a constructor can be arbitrarily complex.

[Note]Note

Constructors can't have any arguments. The result of calling a constructor from the outside is ignored.

{
   // This is a module called "Class" with a constructor function.
   //
   module "Class";

   // A globally accessible variable.
   //
   global integer class_var = 42;

   // This is the constructor (same name as the module).
   //
   global define Class() ``{ class_var = 12345; }
}


{
    // The usage of the "Class"-module.
    //
    import "Class";

    return Class::class_var;            // will be 12345 !
}
	

Chapter 3. Some Rules

Table of Contents

3.1. Usability

Most often when a YaST module shall be created, this module will have some interaction with the user. This usually implies the creation of dialogs to be displayed on screen. As you might have noticed the dialogs that come ready-made with YaST follow a distinct “look and feel” which is due to the fact that the YaST developers follow some rules regarding the visual appearance as well as the functional behavior of a dialog. The keywords here are usability and GUI-consistency.

3.1. Usability

When it comes to user-interaction one concept that is stressed very often is usability or - more speaking - user-friendliness. If you have ever heard s.th. about ergonomics you may also know the term Human Computer Interaction (HCI). For us regular folks usability is probably the best notation because it best summarizes what's it all about. It means that the program in question is good “usable” by the user. In general that means that operating a screen dialog should enjoin as low a burden as possible on the user.

In order to have a good usability a system should satisfy the following criteria:

  • Users must be able to accomplish their goal with minimal effort and maximum results.

  • The system must not treat the user in a hostile fashion or treat the user as if they do not matter.

  • The system can not crash or produce any unexpected results at any point in the process.

  • There must be constraints on the users actions.

  • Users should not suffer from information overload.

  • The system must be consistent at every point in the process.

  • The system must always provide feedback to the user so that they know and understand what is happening at every point in the process.

[Important]Important

If you want to create an interactive YaST module you should try to heed those rules to ease the users life and to assure your module fits smoothly into the surrounding YaST environment which (hopefully) follows them too.

All that said above in essence is an outline from a very good article by Todd Burgess. If you are interested in a more elaborate discussion of usability you may have a look at http://www.osOpinion.com/Opinions/ToddBurgess/ToddBurgess1.html

Chapter 4. Module Layout

FIXME: To be done...

4.1. Module Skeleton

FIXME: To be done...

4.2. Module Example

FIXME: To be done...

Source Code Documentation HowTo

Auto-Generated Documentation


A lot of YaST documentation is generated from the source code. That's the reason why we should follow some rules when documenting the source code (especially functions and variables) to have the possibility of auto-generated documentation.

Why should we have it? It's not needed to create your own modules, functions or agents when such things alread exist. You can just use something that already exist. But the most important information is that it exists, where it exists and how to use it. All these pieces of information can be in the auto-generated documentation.

Additionally, if you develop some universal module with, e.g., functionality offering abstraction layer for reading and writing records from and into the OpenLDAP server, you should document the functionality itself to offer it to others.

Chapter 1. YCP Module and Client Documentation

Most of the modules and clients are written in the YCP programming language.

Although YCP itself offers more possibilities howto write comments, there are some quite strict rules that needs to be followed. Don't be afraid, they are easy to remember.

1.1. Documentation-Generation Tool

Every YaST package, you have installed on your system, has its auto-generated HTML-based documentation in the /usr/share/doc/packages/_package_name_/.../autodocs/ directory. This documentation is generated by the ycpdoc script — part of the yast2-devtools package.

[Important]Important

To recognize the comment that should be processed for the autodocs, ycpdoc needs this syntax:

/**
 *
 * slash and two asterisks identify the comment
 * for file headers, functions and variables
 *
 */

/***
 *
 * slash and three asterisks identify the initial comment
 * of file (intro)
 *
 */

If you don't use two (or three) asterisks, the comment doesn't get processed at all.

Here you can see all available options and arguments of the ycpdoc script. This is the /usr/lib/YaST2/bin/ycpdoc --help command output:

Usage:
      ycpdoc -h|--help|--man
      ycpdoc [-d <dir>] [-s <dir>] [-f html|xml] [-i] [-] [-o] [-wr] files.ycp...

Options and Arguments:
    -h  Show this help screen

    -d dir, --outputdir=dir
        Output files are placed to directory dir

    -s dir, --strip=dir
        Strip only dir when generating output files (default all). Remaining
        slashes are converted to double underscores.

    -f format, --format=format
        Produce output in given format, html or xml. The option may be
        repeated. HTML is the default. XML produces ycpdoc.xml, the DTD is
        not stable yet.

    -O file, --xml-output=file
        For xml output, write to file. The default is "ycpdoc.xml", not
        respecting outputdir.

    -i, --noindex
        Do not generate index.html (intro.html, files.html)

    -   Write output to stdout. Do not generate indexes. If there are more
        input files, generate only one output html file

    -o, --oldindex
        Old style of index: creates only index.html

    -wr Do not warn about undeclared return types

    --state
        For debugging, prints the parsing state for each line.

1.1.1. HTML Output

This is the example of generated HTML-based documentation. Run the command /usr/lib/YaST2/bin/ycpdoc /usr/share/YaST2/modules/FileUtils.ycp and open the just generated HTML files in some HTML browser. First of all, see the one that contains FileUtils.html in its name.

You can see all global functions and variables documented there. Functions also with their parameters, return types, examples, etc.

1.1.2. XML Output

This example of generated XML-based documentation. Run the command /usr/lib/YaST2/bin/ycpdoc --format=xml /usr/share/YaST2/modules/FileUtils.ycp and see the just generated ycpdoc.xml XML file.

[Note]Note

XML is not for humans, it's optimized for computers. That's also why the XML output of ycpdoc doesn't so look nice but it's very valuable for text-processing tools.

Example 1.1. Examplary XML output reformated and shortened by hand

<?xml version="1.0" encoding="UTF-8"?>
<ycpdoc>
  <files>
    <file_item key="usr/share/YaST2/modules/FileUtils.ycp">
      <header>
        <authors>
          <ITEM>author</ITEM>
        </authors>
        <file>modules/FileUtils.ycp</file>
        <module>YaST2</module>
        <summary>summary</summary>
      </header>

      <provides>
        <provides_item>
	  <descr></descr>
	  <example_file></example_file>
	  <file>usr/share/YaST2/modules/FileUtils.ycp</file>
	  <global>1</global>
	  <kind>function</kind>
	  <name>Exists</name>
	  <parameters>
	    <parameters_item>
	      <name>target</name>
	      <type>string</type>
	    </parameters_item>
	  </parameters>
	  <return>true if exists</return>
	  <scruple></scruple>
	  <see></see>
	  <short>short description</short>
	  <type>boolean</type>
	</provides_item>
	...	
      </provides>

      <requires>
        <requires_item>
          <kind>import</kind>
	  <name>SCR</name>
        </requires_item>
        ...
      </requires>
    </file_item>
  </files>
</ycpdoc>

1.2. YCP File Header

The file header should show general information about the file name and location, author, and a general purpose.

Example 1.2. Exemplary file header

{
/**
 * File:	modules/CRONConfig.ycp
 * Package:	General YaST Modules
 * Authors:	John The Fish <john@thesmallfish.net>
 * Flags:	Stable
 *
 * $Id: $
 *
 * This module offers to read, change and write the current
 * CRON setup for all system users.
 */
 
    module "CRONConfig";
    ...
}

File header format:

Attribute: value

Possible Attributes are:

  • Authors

  • Author

  • Depends

  • File

  • Flags

  • Module

  • Package

  • Summary

1.2.1. Authors AKA Author Attribute

Defines the author(s) of the file.

Example 1.3. Single-line Authors tag

Authors: Wendy Graceful <grace@example.com>

Example 1.4. Multi-line Authors tag

Authors: Wendy Graceful <grace@example.com>
         Lars Kralewski <lars@example.com>

1.2.2. Depends Attribute

What does this file / module depend on.

Example 1.5. Depends tag

Depends: Language

1.2.3. File Attribute

Relative path where is this file located under the base YaST /usr/share/YaST2/ path.

Example 1.6. File tag

File: modules/HTTP.ycp

1.2.4. Flags Attribute

Defines the default value for the module stability (read: stability of module API).

Possible values are:

  • Stable

  • Unstable

Example 1.7. Flags tag

Flags: Stable

1.2.5. Package AKA Module Attribute

Textual description of the package (YaST module) that this file belongs to. This tag is obsolete.

Example 1.8. Package tag

Package: SSHD Configuration Module

1.2.6. Summary Attribute

Defines the purpose of this file, such as what does do, how it does that, what it uses for that, etc.

Example 1.9. Multi-line Summary tag

Summary: This is a multiline summary for the exemplary YaST module
         that does nothing and has no valuable content at all.

Summary tag is something special —. You don't need to define the tag itself, just write a multiline text after at the bottom of the comment after the $Id: $ entry.

Example 1.10. Other Multi-line Summary tag

/**
 * File: modules/DoNothingAtAll.ycp
 *
 * ...
 *
 * $Id: $
 *
 * This is a multiline summary for the exemplary YaST module
 * that does nothing and has no valuable content at all.
 */

1.3. YCP Functions

The function header should describe all needed pieces of information about the function. Such as parameters, return value, function's textual description, examples, etc.

Example 1.11. Simple Example of Function Description

/**
 * Returns number of just connected users to the server.
 * You can filter these users by defining the filtering parameter.
 *
 * @param part_of_ip ("192.168.") that users are connected from
 * @return integer number of connected users
 *
 * @see AnotherFunction()
 */
global integer GetCountOfConnectedUsers (string part_of_ip) {
    ...
}

Function description tags:

  • @deprecated

  • @descr

  • @example

  • @param

  • @ref

  • @return

  • @short

  • @since

  • @stable

  • @struct

  • @unstable

1.3.1. Description tag @deprecated

Defines that the function has been deprecated. Additionaly defines another function as the replacement.

Example 1.12. @deprecated tag

@deprecated AnotherFunction()

1.3.2. Description tag @descr

Complete description of the function. It might describe the environment or detailed behavior.

Example 1.13. Standard description

@descr This is a standard multi-line description
       of a function.

Example 1.14. Automatic description

/**
* This first line is identified as @short
*
* This multi-line @descr (after a newline) is automatically
* taken as the function description.
*
* Additionally, this multi-line text is automatically taken as another paragraph
* of the function @descr (the number of paragraphs is unlimited).
*/

1.3.3. Description tag @example

Block of examples of usage, they are as exported as they are written (like <pre> in HTML).

Example 1.15. Multiline @example block

@example
list <string> servers = GetListOfNSServers("example.com.");
boolean success = AddNewNSServer("ns4.example.com.", "example.com.");

1.3.4. Description tag @param

Describes the parameter of the function. The order of @param tags is significant.

Example 1.16. @param tag

/**
 * Adds a new NS Server into list of domain NS servers.
 *
 * @param new_ns_server FQDN
 * @param domain
 * @return boolean successfull
 */
global boolean AddNewNSServer (string new_ns_server, string domain) {
   ...
   return success;
}

1.3.5. Description tag @ref

Single-line version of @see tag.

Example 1.17. @ref tag

@ref AddNewNSServer()

1.3.6. Description tag @return

Description of the return value.

Example 1.18. @return tag

/**
 * Removes the NS Server from list of domain NS servers.
 *
 * @param ns_server FQDN
 * @param domain
 * @return boolean successfull
 */
global boolean RemoveNSServer (string ns_server, string domain) {
   ...
   boolean success = true;

   return success;
}

1.3.7. Description tag @short

Short single-line description of the function.

Example 1.19. Standard description

@short This function does nothing valuable

Example 1.20. Automatic description

/**
* This first line is identified as @short
*
* This multi-line @descr (after a newline) is automatically
* taken as the function description.
*/

See the @descr tag.

1.3.8. Description tag @since

Since which version thix function exists. This is not a commonly used tag.

Example 1.21. @since tag

@since: 2.12.65

1.3.9. Description tag @stable

Defines that the function has a stable API and we don't plan to change it for years.

See tag @unstable.

This tag might changes the default value of the file's stability of the API (just for the current function).

Example 1.22. @stable tag

@stable

1.3.10. Description tag @struct

This tag is used to represent the used data-structure closer. It's as represented as it's written (such as the <pre> HTML tag).

Example 1.23. @struct tag

@struct returns $[
    // example.com.
    "domain"     : "domain name",

    // list of NS servers assigned to the domain
    "ns_servers" : [ "ns1", "ns2", ... ],

    // map of MX servers $[ "server_name" : priority ]
    "mx_servers" : $[ "mx1" : 10, "mx2" : 5 ],
]

1.3.11. Description tag @unstable

Defines that the function has unknown of unstable API and we plan (or might) to change it in the future.

See the @stable tag.

Example 1.24. @unstable tag

@unstable

1.4. YCP Variables

The YCP variable description can contain almost all pieces of information as the YCP function description except @param and @return tags that don't make sense here. You can, for instance, describe the variable's textual description, examples, structures, etc. However, it's usual that only the description is given.

Example 1.25. Simple Example of Variable Description

/**
 * Defines the number of connected users for each IP.
 * There might be more connected users from one IP.
 *
 * @struct $[
 *	"192.168.0.1"  : 5,
 *	"192.168.0.12" : 1,
 *	"192.168.0.35" : 3,
 * ]
 *
 * @see GetNumberOfConnectedUsers()
 */
global map <string, integer>  number_of_connected_users = nil;

Example 1.26. The Most Usual Usage

/**
 * Stores the currently selected domain
 */
global string current_domain = "";

Function description tags:

  • @deprecated

  • @descr

  • @example

  • @ref

  • @short

  • @since

  • @stable

  • @struct

  • @unstable

See the YCP function description for usage and parameters of tags mentioned above.

Chapter 2. Perl Module Documentation

...

Chapter 3. SCR Agent Documentation

The file header should show what is the SCR Agent good for, supported acces types (read/write/execute) and examples how to use it.

Example 3.1. Exemplary SCR Agent Definition

/**
 * File:
 *   sshd.scr
 * Summary:
 *   SCR Agent for reading/writing /etc/ssh/sshd_config
 *   using the ini-agent
 * Access:
 *   read/write
 * Authors:
 *   John The Fish <john@thesmallfish.net>
 * Example:
 *   Dir(.sshd)
 *   (["Port", "X11Forwarding", "Compression", "MaxAuthTries", "PermitRootLogin"])
 *
 *   Read(.sshd.Compression)
 *   (["yes"])
 *
 *   Write(.sshd.Compression, "no")
 *   (true)
 *
 * $Id: sshd.scr 11113 2005-10-20 14:15:16Z jtf $
 *
 * Fore more information about possible keys and values
 * consult with the sshd_config man pages `man sshd_config`.
 */

.sshd

`ag_ini(
    ...
)

Common Modules and Libraries


Chapter 1. Modules and Libraries

1.1. Packages Manipulation

This is a set of basic routines for manipulating packages.

1.1.1. Modules

The module Package is the module for handling package installation , it works on the target system and correctly differentiates between normal and autoinstallaton mode.

PackageSystem is for situations when you need immediate action (for example to start the client).

  • Package

  • PackageSystem

1.1.2. Functions

The function names should be self-descriptive, so there are no comments here. Feel free to ask if you are in doubts. (FIXME)

1.1.2.1. Package Installation (GUI)

  • boolean Package::Install(<string> package);

  • boolean Package::InstallAll(list<string> packages);

  • boolean Package::InstallAny(list<string> packages);

  • boolean Package::Remove(<string> package);

  • boolean Package::RemoveAll(list<string> packages);

1.1.2.2. Package Installation (GUI, custom message)

  • boolean Package::InstallMsg(<string> package, <string> message);

  • boolean Package::InstallAllMsg(list<string> packages, <string> message);

  • boolean Package::InstallAnyMsg(list<string> packages, <string> message);

  • boolean Package::RemoveMsg(<string> package, <string> message);

  • boolean Package::RemoveAllMsg(list<string> packages, <string> message);

1.1.2.3. Conditional Package Installation

[Note]Note

GUI based, do not install if Mode::config is defined, only in PackageSystem)

  • boolean Package::CheckAndInstallPackages (list<string> packages);

  • boolean Package::CheckAndInstallPackagesInteractive (list<string> packages); // with error handling

1.1.2.4. Packages Installation (No GUI)

  • boolean Package::DoInstall(list<string> packages);

  • boolean Package::DoRemove(list<string> packages);

  • boolean Package::DoInstallAndRemove(list<string> toinstall, list<string> toremove);

1.1.2.5. Testing

  • boolean Package::Available(<string> package);

  • boolean Package::AvailableAll(list<string> packages);

  • boolean Package::AvailableAny(list<string> packages);

  • boolean Package::Installed(<string> package);

  • boolean Package::InstalledAll(list<string> packages);

  • boolean Package::InstalledAny(list<string> packages);

1.1.2.6. Other

  • void RunSUSEconfig();

  • boolean Package::LastOperationCanceled();

  • boolean Package::InstallKernel(list<lt;string> kernel_modules);

1.2. Using the package manager

1.2.1. Introduction

This is a short introduction into using the package manager interface from within YCP code.

It describes the required Pkg calls and how to use them in actual code.

1.2.2. Implementation

The new package manager does all package related handling inside C++ code and uses callbacks for user interaction.

This reduces the amount of ycp code needed for package installation (or removal) to a few lines.

The package manager keeps a list of installed (on the system) and available (on the CD or other installation media) packages and their status (install, delete, no change). YCP code can query the package manager for installed or available packages and set their status. The actual package installation or deletion takes place with a final commit call.

This call goes through the package list and deletes all packages marked as such. Then it orderes the packages marked for installation according to their "PreRequires" rpm tag and the media number (so you usually install CDs in ascending order).

All user interaction is done via callbacks from inside the package manager. If no callback is defined, the user doesn't see anything and also isn't requested to change CDs. Especially the latter case makes it important to set up appropriate callbacks.

1.2.3. Querying the package manager

1.2.3.1. Checking an installed package

Pkg::IsProvided checks if a rpm tag (a package name or any other symbolic name provided by a package) is installed.

It does a 'rpm -q' and if this fails a 'rpm -q --whatprovides' for the given tag.

It does not start the complete package manager.

1.2.3.2. Checking the installation sources

Pkg::IsAvailable checks if a rpm package is available for installation. (It currently does not check for other tags provided, this will be implemented in a future version.)

It starts the package manager if it isn't running already. Starting the package manager may take some time since it must read all it's caches about the installation sources and set up the internal data structures.

1.2.3.3. Deleting a package

Pkg::PkgDelete marks an installed package for deletion. It does not immediately run 'rpm -e' but just sets the internal status of the package in the package manager.

The actual deletion must be triggered with Pkg::PkgCommit().

1.2.3.4. Installing a package

Pkg::PkgInstall marks an available package for installation. It does not immediately run 'rpm -Uhv' but just sets the internal status of the package in the package manager.

The actual installation must be triggered with Pkg::PkgCommit().

1.2.3.5. Solving dependencies

Pkg::PkgSolve() tries to solve open package dependencies and marks other (dependant) packages for installation/deletion.

(It currently does an automatic solving without user interaction. Callbacks for user interaction will be added later.)

1.2.3.6. Committing the changes

Pkg::PkgCommit does the 'rpm' calls needed to install or delete packages.

It sorts the packages according to their pre-requires and their location on the media (CD order), handles mouting/unmounting of the media, does the package download (if the media is an ftp or http server), and calls rpm.

All these actions might trigger callbacks for user information or user actions (i.e. media change).

The module PackageCallbacks from yast2-packager.rpm defined default callbacks for all these actions and should be imported by any code using Pkg::PkgCommit(0).

The integer parameter given to Pkg::PkgCommit(0) must be zero for the normal usage. Non-zero values are only allowed during system installation when no media change is possible.

1.2.4. Example code

{
    import "PackageCallbacks";

    // installing netscape and cups
    Pkg::PkgInstall ("netscape");
    Pkg::PkgInstall ("cups");

    // deleting lprng if it is installed
    if (Pkg::IsProvided ("lprng"))
    Pkg::PkgDelete ("lprng");

    // fill open dependencies
    Pkg::PkgSolve();

    // do the rpm calls
    Pkg::PkgCommit();

}

1.3. Common Popup Dialogs

The module Popup.ycp contains commonly used popup dialogs for general usage. Use those dialogs rather than creating your own custom dialogs whenever possible.

If you have your own definitions of equivalent popups (which is very likely), please remove them as soon as possible and use the popups from Popup.ycp. The new popups usually require fewer parameters, e.g., you no longer need to explicitly pass standard button labels like Yes or No etc. as parameters (we had to do that because of technical limitations with the translator module, but we found a workaround for that).

1.3.1. Simple and Expert Version

There are several versions for each type of popup, a simple version with a minimum number of parameters and one or more "expert" versions where you can pass lots of parameters to fine-tune many details.

Use the simplest version one whenever you can. It's normally the version that makes most sense for most cases.

If you use an expert version, try to stick to the default behaviour (i.e. the behaviour of the simple version) as closely as possible. Change only parameters you really need to change. In particular, only change the default button for very good reasons.

If there is a specialized simple version, use it whenever you can. For example, use Popup::Warning rather than Popup::AnyMessage with the same message if you want to display a warning. This way we can make sure all warnings look the same and make them easily recognizable as warnings.

1.3.2. Headlines, Yes or No?

Sense or nonsense of providing headlines for each popup is a cause for endless discussion - we've been through that. Sometimes headlines make sense, sometimes they don't.

As a general rule of thumb, don't provide a headline that is more or less the same as the message itself, i.e. don't

// Example how NOT to use popups:
//
// - The headline is redundant
//
// - The text is too verbose
//
// - The text contains a reference to "YaST2"
//   (the user shouldn't need to know what that is)
{
    import "Popup";

    boolean answer = Popup::YesNoHeadline( "Create Backup?", // superfluous headline
					   "Should YaST2 create a backup of the configuration files?" );
}

this is plainly redundant. This could be done much better like this:

// Improved version of ask_create_backup_bad.ycp:
//
// Note there is no superfluous headline any more,
// the text is concise, and there is no more reference
// to "YaST2" (a user shouldn't need to know what that is)
{
    import "Popup";

    boolean answer = Popup::YesNo( "Create a backup of the configuration files?" );
}

i.e. concise question, no lyrics around it, clear, plain and simple.

If you need to provide more background information to the users so they can understand what tragedy could befall their machine should they chose either alternative, a headline makes perfect sense for the more experienced user who gets to this kind of question quite frequently:

// Example when to use a headline:
//
// There is lengthy text that advanced users might have read several times
// before, so we give him a concise headline that identifies that dialog so he
// can keep on working without having to read everything again.
{
    import "Popup";

    string long_text =
	"Resizing the windows partition works well in most cases,\n"
	+ "but there are pathological cases where this might fail.\n"
	+ "\n"
	+ "You might lose all data on that disk. So please make sure\n"
	+ "you have an up-to-date backup of all relevant data\n"
	+ "for disaster recovery.\n"
	+ "\n"
	+ "If you are unsure, it might be a good idea to abort the installation\n"
	+ "right now and make a backup."
	;

    boolean answer = Popup::YesNoHeadline( "Resize Windows Partition?", long_text );
}
[Note]Note

This example might be a good candidate for ContinueCancel() - see below

If you use headlines, please use them to either

  • classify the type of popup (Error, Warning, ...)

  • summarize the question.

Please DON'T use headlines like Question etc. - that doesn't have any informative value, yet it forces the user to read this useless headline.

For all those reasons, most popups come in a generic version where you can pass a headline ("Headline" is included in the names of those) and a simple version for general usage.

1.3.3. Predefined Messages

There are predefined messages for commonly used texts for the dialogs. Use them when you use the expert version of a dialog - don't pass your own messages if you can avoid it! Not only makes this life easier for our translators (they need to translate stuff like Cancel over and over again), it also gives us a chance to provide consistent keyboard shortcuts throughout the entire program.

Example 1.1. 

Use

`OpenDialog(
            ...
            `HBox(
:-)               `PushButton( `id(`ok ), `opt(`default), Label::OKButton() ),
                  `PushButton( `id(`retry ), Label::RetryButton() ),
                  `PushButton( `id(`cancel), Label::CancelButton() )
                  )
           )

Do not use

`OpenDialog(
            ...
            `HBox(
:-(               `PushButton( `id(`ok ), `opt(`default), _("&OK") ),
                  `PushButton( `id(`retry ), "&Retry"  ),
                  `PushButton( `id(`cancel), "&Cancel" )
                  )
           )

- even whenever you create your own dialogs.


The first part of the name always is the message itself literally Retry, the suffix indicates the type Button to indicate whether or not it has a keyboard shortcut. Currently we have Label::xxxButton (with keyboard shortcut) and Label::xxxMsg (without shortcut).

1.3.4. When to use which Popup

1.3.4.1. Decision Popups - two buttons, return true or false

For confirmation of possibly dangerous things, use Popup::ContinueCancel.

Only when there are two really distinct paths of decision use Popup::YesNo. And no, cancelling the entire process doesn't count here - this is no equivalent decision.

The positive case (Yes or Continue) is the default. If you don't like that, use the generic Popup::AnyQuestion directly and pass `focus_no for the focus parameter.

[Important]Important

Remember: Only do that for very good reasons - i.e. when it's a really dangerous decision that typically results in loss of data that can't easily be restored.

  • If you need to pass other button labels, think twice.

  • If you really need this, use Popup::AnyQuestion.

But NEVER use it so simply change the order of default buttons - i.e. NEVER create dialogs like this one:

// Bad Popup design: Default button order exchanged
//
// DON'T DO THIS!
{
    import "Popup";
    import "Label";

    boolean dont_do_it =
	Popup::AnyQuestion( Popup::NoHeadline(),
			    "Format hard disk?",
			    Label::CancelButton(),
			    Label::ContinueButton(),
			    `yes ); // initial focus - "Cancel" in this case
}
// Bad Popup design: Default button order exchanged
//
// DON'T DO THIS!
{
    import "Popup";
    import "Label";

    boolean dont_do_it =
	Popup::AnyQuestion( Popup::NoHeadline(),
			    "Show installation log?",
			    Label::NoButton(),
			    Label::YesButton(),
			    `no ); // button role reversed - "Yes" 
}

1.3.4.2. Info Popups - just an "OK" button

If you can classify a simple message accordingly, use one of

  • Popup::Error

  • Popup::Warning

  • Popup::Notify

they all have a headline that indicates the type (Error, Warning or Notification).

If you can't classify your message, use the Popup::Message.

Use Popup::LongText to display large amounts of text that might need scrolling.

1.4. Advanced YaST2 command line parsing

1.4.1. Important features

  • simple specification in the YaST module

  • automatic help

  • automatic checking of arguments (types, format)

  • interactive session without UI

1.4.2. Basic usage of module CommandLine

The aim of the module is to provide as automatic interface as possible for controlling the module. To support interactive sessions, the YaST module needs to provide command-handling loop similar to concept of event-handling in GUIs.

If the module does not need to do any special handling of the actions, it can use the "commandline" include (a wrapper for CommandLine). The include defines a single function "CommandLineRun()", which implements a standard event-loop and returns true on success. The module just needs to specify handlers for actions, user-interface, initialization and finishing.

Example 1.2. Simple CommandLine definition

{

define void deleteHandler( map options ) ``{
    string dev = options["device"]:"";
    CommandLine::Print("Deleting: "+dev);

    if(Lan::Delete(dev) && Lan::Commit())
	CommandLine::Print("Success");
    else
	CommandLine::Print("Error");
}

...

map cmdline = $[
    "help"	: "Configuration of network cards",
    "id"	: "lan",
    "guihandler": ``(LanSequence()),
    "initialize": ``(Lan::Read()),
    "finish"	: ``(Lan::Finish()),

    "actions"	: $[
	"list" : $[
	    "help"	: "display configuration summary",
	    "example"	: "lan list configured",
	    "handler"	: ``(listHandler())
	],
	"add" : $[
	    "help"	: "add a network card",
	    "handler"	: ``(addHandler())
	],
	"delete" : $[
	    "help"	: "delete a network card",
	    "handler"	: ``(deleteHandler())
	]
    ],
    ...
];

import "Lan";
include "commandline/commandline.ycp";

CommandLineRun(cmdline);

/* EOF */
}
    

The UI handler is specified in the "guihandler" key of the command line description map. It must take no arguments and return boolean, true on success.

The initialize resp. finish handler is specified by the "initialize" resp. "finish" key of the description map. They do not take any arguments and must return boolean, true on success. Notice that "initialize" and "finish" handlers are not used if a user asked for GUI (guihandler is used instead and therefore it must do initializing and finishing on its own). The handler for an action is specified in the "handler" key of the action description map. Each handler must take a single argument containing the options entered by the user and return a boolean, true on success. If the handler returns "false", the command line will abort for non-interactive handling. This is useful for handling error states. However, a handler must use CommandLine::Abort() to stop event-handling in the interactive mode.

The CommandLine module is stateful, i.e., it contains a current state of command line parsing/interactive console control of a YaST module. Therefore, the CommandLineRun() handles the commands as follows:

  1. standard UI start of a module - CommandLine::StartGUI will return true in this case

  2. command given as an argument - the inner while loop will be done only once

  3. interactive controling of a module - the while loop will be done as long as the user does not enter "exit" or "quit"

1.4.3. Internally handled commands

help

shows the help text for the command

interactive

starts interactive session without UI

<command> help

shows the command-specific help

<command> quiet

option to supress the progress messages

These are available in interactive mode only:

quit

quits interactive session, sets CommandLine::Aborted() flag to true

exit

exits interactive session, sets CommandLine::Aborted() flag to false

1.4.4. Specification of the supported commands in YaST module

[Note]Note

If the map does not follow the following rules, an error will be emitted into log.

The map describing the command line interface of a module must contain "id" entry containing the name of the module. The map can contain global help text, a list of actions, a list of options and a list for mapping of options to actions (which optionscan be used for which actions). Each action and option must have its help text.

Actions is a map with the action name as a key. For each action, it isnecessary to provide the help text. Optionally, action can have defined an example of usage.

A list of flags can be specified to change the default behavior of the parameter checker. It is a list with key "options". Currently known flags:

non_strict

for unknown parameters, do not check their validity can be used for passing unknown options into the handler

Example 1.3. Actions map definition

	"actions"	: $[
			"list"	: $[
				"help": "display configuration summary",
				"example": "lan list configured",
				"options": [ "non_strict" ]
			],
			"add"	: $[
				"help": "add a network card" ],
			...
                        ],
        

Options is a map with the option name as a key. For each option, it is necessary to provide a description text in "help" key, and a "type" key (for options with arguments only). Optionally, an option can contain an example.

There are two kinds of options: flags and options, which require an argument.For flags ommit type key, or specify type as "". A type is a string describing the type.basic types supported are string, boolean, integer.

Special types:

regex

In this case, you need to specify "typespec" key containing the regular expression the argument should be matched against.

enum

In this case, the typespec key must contain a list of possible values as strings

Example 1.4. Options map definition

    	"options"	: $[
			"propose" : $[
				"help": "propose a configuration",
				"example": "lan add propose",
				"type": ""
			],
			"device": $[
				"help": "device ID",
				"type": "string",
				"example": "lan add device=eth0"
			],
			"blem"	: $[
				"help": "other argument (without spaces)",
				"type": "regex",
				"typespec": "^[^ ]+$"
			],
			"atboot": $[
				"help": "should be brought up at boot?",
				"type": "enum",
				"typespec": ["yes","no"]
                                }

                            

The actions and options are grouped together using mappings. Currently, you can mapan action to a set of options. You can't specify required/optional options, all ofthem are optional.

Example 1.5. Mappings between actions and their options

	"mappings"	$[
			"list"	: [ "configured", "unconfigured" ],
			"add"	: [ "device", "ip", "netmask", "blem" ],
			"delete": [ "device" ]
                        ]

        

1.4.5. Advanced API

If you need to write your own event loop, this is a part of the CommandLine API useful for this:

boolean CommandLine::StartGUI()

returns true, if the user asked to start up the module GUI

list CommandLine::Scan()

reads a new command line in interactive mode, splits the arguments into a list

map CommandLine::Command()

parse (and scan if needed) next command and return its map

map CommandLine::Parse( list commandline )

lower-level function to parse the command line, check the validity

boolean CommandLine::Done()

returns true, if the last command was already returned

boolean CommandLine::Aborted()

returns true, if the user asked to cancel the changes

void CommandLine::Error(string)

prints the string and then a message how to obtain the help

void CommandLine::Print(string)

prints the string

void CommandLine::PrintVerbose(string)

same as CommandLine::Print, but the string is printed only in verbose mode

Example 1.6. Example of an event-loop


	import "CommandLine";

	if( ! CommandLine::Init( description_of_commands, Args() ) ) return;

	if( CommandLine::StartGUI() ) {

		<do standard GUI module>

		return;
	}
	
	<initialize call>

	while( ! CommandLine::Done() )
	{
		map command = CommandLine::Command();

		<handle commands here>
	}
	
	<finish call>
    

The CommandLine::Init() returns boolean whether the module has something to do. If the user only requested help or the arguments passed were not correct, the module should stop processing.

The CommandLine::Command() will do the user interaction if necessary. Also, it will handle all supported"system" commands, like "help", "exit" and "quit".

[Note]Note

In interactive mode, the communication uses /dev/tty. In non-interactive commandline mode it prints everything to standard error output.

1.4.6. Example usage in YaST module

For an example without using the event-loop provided by the CommandLine module, see lan-simple.ycp.

/**
 * File:	clients/lan.ycp
 * Package:	Network configuration
 * Summary:	Network cards main file
 * Authors:	Michal Svec <msvec@suse.cz>
 *
 * $Id: lan-simple.ycp 10158 2003-06-23 12:48:40Z visnov $
 *
 * Main file for network card configuration.
 * Uses all other files.
 */

{

/***
 * <h3>Network configuration</h3>
 */

import "CommandLine";
include "network/lan/wizards.ycp";

/**
 * Command line definition
 */
map cmdline = $[
    "help"	: "Configuration of network cards",
    "id"	: "lan",
    "actions"	: $[
	"list" : $[
	    "help"	: "display configuration summary",
	    "example"	: "lan list configured"
	],
	"add" : $[
	    "help"	: "add a network card" ],
	"delete" : $[
	    "help"	: "delete a network card" ]
    ],
    "options" : $[
	"propose" : $[
	    "help"	: "propose a configuration",
	    "example"	: "lan add propose",
	    "type"	: ""
	],
	"configured" : $[
	    "help"	: "list only configured cards"
	],
	"unconfigured" : $[
	    "help"	: "list only not configured cards"
	],
	"device" : $[
	    "help"	: "device ID",
	    "type"	: "string",
	    "example"	: "lan add device=eth0"
	],
	"ip" : $[
	    "help": "device address",
	    "type": "ip"
	],
	"netmask" : $[
	    "help": "network mask",
	    "type": "netmask"
	],
    ],
    "mappings" : $[
	"list"	: [ "configured", "unconfigured" ],
	"add"	: [ "device", "ip", "netmask" ],
	"delete": [ "device" ],
    ]
];


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

/* Initialize the arguments */
if(!CommandLine::Init(cmdline, Args())) {
    y2error("Commandline init failed");
    return false;
}

/* Start GUI */
if(CommandLine::StartGUI()) {
    any ret = nil;
    ret = LanSequence();
    y2debug("ret=%1", ret);
    y2milestone("Lan module finished");
    y2milestone("----------------------------------------");
    return ret;
}

/* Init */
CommandLine::Print("Initializing");
import "Lan";
import "Progress";
CommandLine::Print("Reading");
Progress::off();
Lan::Read();

/* Init variables */
string command = "";
list flags = [];
map options = $[];
string exit = "";
list l = [];

while(!CommandLine::Done()) {
    map m = CommandLine::Command();
    command = m["command"]:"exit";
    options = m["options"]:$[];

    /* list */
    if(command == "list") {
	CommandLine::Print("\nSummary\n");
	string summary = sformat("%1\n", Lan::Summary(false));
	CommandLine::Print(summary);
    }
    /* del */
    else if(command == "delete") {
	string dev = options["device"]:"";
	CommandLine::Print("Deleting: "+dev);

	if(Lan::Delete(dev) && Lan::Commit())
	CommandLine::Print("Success");
	else
	CommandLine::Print("Error");
    }
    /* add */
    else if(command == "add") {
	string dev = options["device"]:"";
    }
    else {
	/* maybe we got "exit" or "quit" */
	if( !CommandLine::Done() ) {
	    CommandLine::Print("Unknown command (should not happen)");
	    continue;
	}
    }
}

if(!CommandLine::Aborted()) {
    CommandLine::Print("Writing");
    Lan::Write();
    CommandLine::Print("Finish");
}
else {
    CommandLine::Print("Quit (without saving)");
}

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

/* EOF */
}

For an example with the standard event-loop provided by the commandline.ycp include, see lan-simpler.ycp.

/**
 * File:	clients/lan.ycp
 * Package:	Network configuration
 * Summary:	Network cards main file
 * Authors:	Michal Svec <msvec@suse.cz>
 *
 * $Id: lan-simpler.ycp 24831 2005-08-11 15:51:55Z visnov $
 *
 * Main file for network card configuration.
 * Uses all other files.
 */

{

/***
 * <h3>Network configuration</h3>
 */

import "CommandLine";

include "network/lan/wizards.ycp";

/**
 * Command line definition
 */
map cmdline = $[
    "help"	: "Configuration of network cards",
    "id"	: "lan",
    "guihandler": ``(LanSequence()),
    "initialize": ``(Lan::Read()),
    "finish"	: ``(Lan::Write()),
    "actions"	: $[
	"list" : $[
	    "help"	: "display configuration summary",
	    "example"	: "lan list configured",
	    "handler"	: ``(listHandler())
	],
	"add" : $[
	    "help"	: "add a network card",
	    "handler"	: ``(addHandler())
	],
	"delete" : $[
	    "help"	: "delete a network card",
	    "handler"	: ``(deleteHandler())
	]
    ],
    "options" : $[
	"propose" : $[
	    "help"	: "propose a configuration",
	    "example"	: "lan add propose",
	    "type"	: ""
	],
	"configured" : $[
	    "help"	: "list only configured cards"
	],
	"unconfigured" : $[
	    "help"	: "list only not configured cards"
	],
	"device" : $[
	    "help"	: "device ID",
	    "type"	: "string",
	    "example"	: "lan add device=eth0"
	],
	"ip" : $[
	    "help": "device address",
	    "type": "ip"
	],
	"netmask" : $[
	    "help": "network mask",
	    "type": "netmask"
	],
    ],
    "mappings" : $[
	"list"	: [ "configured", "unconfigured" ],
	"add"	: [ "device", "ip", "netmask" ],
	"delete": [ "device" ],
    ]
];

/** handler for action "list" */
define void listHandler( map options ) ``{
    CommandLine::Print("\nSummary\n");
    string summary = CommandLine::Rich2Plain( sformat("%1\n", mergestring( Lan::Summary(false), "") ) );
    CommandLine::Print(summary);
}

/** handler for action "add" */
define void addHandler( map options ) ``{
    string dev = options["device"]:"";
}

/** handler for action "delete" */
define void deleteHandler( map options ) ``{
    string dev = options["device"]:"";
    CommandLine::Print("Deleting: "+dev);

    if(Lan::Delete(dev) && Lan::Commit())
	CommandLine::Print("Success");
    else
	CommandLine::Print("Error");
}

import "Lan";

CommandLineRun( cmdline );

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

/* EOF */
}

1.5. Runlevel Editor Library

If you need to activate or de-activate services in YaST, use module Service. It is a replacement for the old client runlevel_adjust that you should have been using before You may also use it to obtain information about services. Following functions are self-containing.

[Note]Note

You do not need to run Service::Read if you want to use any of them.

1.5.1. Enabling/Disabling Service

Service::Adjust (string name, string action) does some operation with init script. name is the name of the init script. action is one of:

  • "enable"

    enables service, which means that if service is not set to run in any runlevel, it will set it to run in default runlevels. Otherwise it will not touch the service. Use it when you want the service to run, because it preserves user-defined state. If user sets service to run in some non-default runlevels, this will not destroy her own settings.

  • "disable"

    disables service, which means that it sets it not to run in any runlevel.

  • "default"

    sets service to run in its default runlevels, which are the runlevels listed in init script comment, fiels Default-Start.

"enable" and "disable" take no action when init script links are OK, but "default" always runs insserv.

Service::Adjust ("cups", "disable");

1.5.2. Specifying Runlevels for Service

Service::Finetune (string name, list rl) sets service to run in list rl. rl is list of strings.

Service::Finetune ("cups", [ "5" ]);

Service::Finetune ("cups", [ "S", "3", "5" ]);

1.5.3. Init Script Actions

Service::RunInitScript (string name, string param) runs init script name with parameter param. Returns init scripts exit value. I do not think it is worth to import module Service only because of this one function when you may simply call SCR::Execute (.target.bash, ...) with the same result. But if you use Service for something else, this function may increase readability of the code.

Service::RunInitScript ("cups", "restart");

1.5.4. Service Info

Service::Status (string name), Service::Info (string name) and Service::FullInfo (string name)

Service::Status tells whether service runs. It calls init script status and returns exit value.

Service::Info returns information about service. The map contains:

$[ "defstart" : [ ... ], // list of strings
   "defstop"  : [ ... ], // list of default runlevels

   "start"    : [ ... ], // list of strings
   "stop"     : [ ... ], // list of real runlevels

   "reqstart" : [ ... ], // list of strings
   "reqstop"  : [ ... ], // prerequisites

   "description": string,// description
]

Values "def{start|stop}", "req{start|stop}" and "description" are taken from init script comments. "start" and "stop" are taken from links. Service::FullInfo combines info from Service::Info and Service::Status into one map. It adds key "started" with integeral value of script status. Service is enabled if "start" is not empty.

1.5.5. Is Service Enabled?

Service::Enabled (string name) returns true if service is set to run in any runlevel. False otherwise.

1.5.6. Example of Usage

import "Service";

boolean Read () {
        if (0 != Service::RunInitScript ("foo", "restart"))
        {
                y2error ("Can not start service");
                return false;
        }
        // ... your code ...
}

boolean Write () {
        // ... write settings ...

        // set service to correct state
        if (start_it)
        {
                // enable service
                Service::Adjust ("foo", "enable");
                // reload changed settings
                Service::RunInitScript ("foo", "reload");
        }
        else
        {
                // disable service
                Service::Adjust ("foo", "disable");
                // and stop it
                Service::RunInitScript ("foo", "stop");
        }
}

1.5.7. What Should You Know?

It has sense to run some services only in case that some other service runs. Now the only case known to me is portmap that is needed by NFS, NIS and others. Runlevel UI cares about this case. But if you use functions like Service::Adjust, you must take care about these dependencies yourself. If you know about other dependencies, let me know, I will implement them in UI.

Just for your info: Runleve UI warns user who wants to stop service xdm. If you know about another cases where it is wise idea to warn user, please let us know...

UI Layout and Events


Table of Contents

1. YaST UI
1.1. YaST2 Layout
1.1.1. Summary: What's This All About?
1.1.2. Basics and Terms
1.1.2.1. The UI
1.1.2.2. Widgets
1.1.2.3. UI Independence and the libyui
1.1.2.4. The Nice Size
1.1.2.5. Initial Dialog Sizes
1.1.2.6. Full Screen Dialogs: `opt(`defaultsize)
1.1.3. Layout Building Blocks
1.1.3.1. Layout Boxes: HBox and VBox
1.1.3.2. Specifying Proportions: HWeight and VWeight
1.1.3.3. Rubber Bands: HStretch and VStretch
1.1.3.4. Making Common Widgets Stretchable: `opt(`hstretch) and `opt(`vstretch)
1.1.3.5. Spacings: HSpacing and VSpacing
1.1.3.6. Alignments: Left, Right, HCenter, Top, Bottom, VCenter, HVCenter
1.1.3.7. Compressing Excess Space: HSquash, VSquash, HVSquash
1.1.3.8. Optical Grouping: Frame
1.1.3.9. Grouping RadioButtons: RadioButtonGroup
1.1.3.10. The Esoterics: ReplacePoint
1.1.3.11. Obsolete: Split
1.1.4. Common Layout Techniques
1.1.4.1. Creating Widgets of Equal Size
1.1.4.2. Creating Widgets of Equal Size that don't Grow
1.1.4.3. Creating Widgets of Equal Size that don't Grow - with Spacings in between
1.1.4.4. Specifying the Size of Scrollable Widgets
1.1.5. Hints and Tips
1.1.5.1. Debugging Aids: The Log File
1.1.5.2. Keep it Simple - Do not Overcrowd Dialogs!
1.1.5.3. Always Keep Other UIs in Mind - What does it Look Like with NCurses?
1.1.5.4. Do not Neglect Mouseless Users - Always Provide Keyboard Shortcuts!
1.1.6. The Layout Algorithm - How the Layout Engine Works Internally
1.1.6.1. Primary and Secondary Dimensions
1.1.6.2. Calculating the Nice Size
1.1.6.2.1. Secondary Nice Size
1.1.6.2.2. Primary Nice Size
1.1.6.3. Setting the Size of a Layout - SetSize()
1.1.6.3.1. Running out of Space - the Pathological Cases
1.1.6.3.2. Centering in the Secondary Dimension
1.2. UI Events
1.2.1. Introduction
1.2.1.1. The YaST2 Event Model
1.2.1.1.1. Classic GUI Event Loops
1.2.1.1.2. The YaST2 Approach
1.2.1.1.3. Simplicity vs. Features
1.2.1.1.4. The notify Option
1.2.1.1.5. Downsides and Discussions
1.2.1.1.6. Design Alternatives
1.2.1.2. Event Delivery
1.2.1.2.1. Event Queues vs. One Single Pending Event
1.2.1.2.2. Event Reliability
1.2.1.2.3. Defensive Programming
1.2.2. Event-related UI Builtin Functions
1.2.3. Event Reference
1.2.3.1. Event Maps in General
1.2.3.2. Event Types
1.2.3.2.1. WidgetEvent
1.2.3.2.2. Activated WidgetEvent
1.2.3.2.3. ValueChanged WidgetEvent
1.2.3.2.4. SelectionChanged WidgetEvent
1.2.3.2.5. MenuEvent
1.2.3.2.6. TimeoutEvent
1.2.3.2.7. CancelEvent
1.2.3.2.8. KeyEvent
1.2.3.2.9. DebugEvent

Chapter 1. YaST UI

Table of Contents

1.1. YaST2 Layout
1.1.1. Summary: What's This All About?
1.1.2. Basics and Terms
1.1.2.1. The UI
1.1.2.2. Widgets
1.1.2.3. UI Independence and the libyui
1.1.2.4. The Nice Size
1.1.2.5. Initial Dialog Sizes
1.1.2.6. Full Screen Dialogs: `opt(`defaultsize)
1.1.3. Layout Building Blocks
1.1.3.1. Layout Boxes: HBox and VBox
1.1.3.2. Specifying Proportions: HWeight and VWeight
1.1.3.3. Rubber Bands: HStretch and VStretch
1.1.3.4. Making Common Widgets Stretchable: `opt(`hstretch) and `opt(`vstretch)
1.1.3.5. Spacings: HSpacing and VSpacing
1.1.3.6. Alignments: Left, Right, HCenter, Top, Bottom, VCenter, HVCenter
1.1.3.7. Compressing Excess Space: HSquash, VSquash, HVSquash
1.1.3.8. Optical Grouping: Frame
1.1.3.9. Grouping RadioButtons: RadioButtonGroup
1.1.3.10. The Esoterics: ReplacePoint
1.1.3.11. Obsolete: Split
1.1.4. Common Layout Techniques
1.1.4.1. Creating Widgets of Equal Size
1.1.4.2. Creating Widgets of Equal Size that don't Grow
1.1.4.3. Creating Widgets of Equal Size that don't Grow - with Spacings in between
1.1.4.4. Specifying the Size of Scrollable Widgets
1.1.5. Hints and Tips
1.1.5.1. Debugging Aids: The Log File
1.1.5.2. Keep it Simple - Do not Overcrowd Dialogs!
1.1.5.3. Always Keep Other UIs in Mind - What does it Look Like with NCurses?
1.1.5.4. Do not Neglect Mouseless Users - Always Provide Keyboard Shortcuts!
1.1.6. The Layout Algorithm - How the Layout Engine Works Internally
1.1.6.1. Primary and Secondary Dimensions
1.1.6.2. Calculating the Nice Size
1.1.6.2.1. Secondary Nice Size
1.1.6.2.2. Primary Nice Size
1.1.6.3. Setting the Size of a Layout - SetSize()
1.1.6.3.1. Running out of Space - the Pathological Cases
1.1.6.3.2. Centering in the Secondary Dimension
1.2. UI Events
1.2.1. Introduction
1.2.1.1. The YaST2 Event Model
1.2.1.1.1. Classic GUI Event Loops
1.2.1.1.2. The YaST2 Approach
1.2.1.1.3. Simplicity vs. Features
1.2.1.1.4. The notify Option
1.2.1.1.5. Downsides and Discussions
1.2.1.1.6. Design Alternatives
1.2.1.2. Event Delivery
1.2.1.2.1. Event Queues vs. One Single Pending Event
1.2.1.2.2. Event Reliability
1.2.1.2.3. Defensive Programming
1.2.2. Event-related UI Builtin Functions
1.2.3. Event Reference
1.2.3.1. Event Maps in General
1.2.3.2. Event Types
1.2.3.2.1. WidgetEvent
1.2.3.2.2. Activated WidgetEvent
1.2.3.2.3. ValueChanged WidgetEvent
1.2.3.2.4. SelectionChanged WidgetEvent
1.2.3.2.5. MenuEvent
1.2.3.2.6. TimeoutEvent
1.2.3.2.7. CancelEvent
1.2.3.2.8. KeyEvent
1.2.3.2.9. DebugEvent

1.1. YaST2 Layout

1.1.1. Summary: What's This All About?

This is both a tutorial and a reference on how to lay out YaST2 dialogs.

Since experience shows that most people begin this with only a vague perception of YaST2's concepts, it also includes some basics that might be covered in other YaST2 documentation as well - just enough to get started.

1.1.2. Basics and Terms

If you are in a hurry or - like most developers - you don't like to read docs, you can skip this section and move right on to the next section. That is, if you think you know what the next few headlines mean. You can always come back here later.

Just don't ask anything that is explained here on the yast2-hackers mailing list - you'll very likely just get a plain RTFM answer shot right back into your face. And this is the FM, so read it if you need explanations. ;-)

1.1.2.1. The UI

The UI (user interface) is that part of YaST2 that displays dialogs. It is a separate process which uses a separate interpreter. Always think of it as something running on a different machine: There is the machine you want to install with YaST2 (i.e. the machine where the disks will be formatted etc.) and there is the machine that displays the dialogs - the UI machine. In most cases, this will actually be the same machine. But it doesn't need to be. Both parts of YaST2 might as well run on different machines connected via a serial line, a network or by some other means of communication (telepathy? ;-) ).

The logical consequence of this is that the UI uses its own separate set of function definitions and variables. You need to be real careful not to mix that up. Always keep in mind what part of YaST2 needs to do what and what variables need to be stored where. You can easily tell by the UI prefix within the YCP code what parts are getting executed by the UI.

1.1.2.2. Widgets

A widget is the most basic building block of the UI. In short, each single dialog item like a PushButton, a SelectionBox or a TextEntry field is a widget. But there are more: Most static texts in dialogs are widgets, too. And there are a lot of widgets you can't see: Layout boxes like HBox or VBox and many more that don't actually display something but arrange other widgets in some way.

See the widget reference for details and a list of all available widgets.

1.1.2.3. UI Independence and the libyui

There are several different UIs for YaST2. There is the Qt based UI (y2qt) as a graphical frontend which requires the X Window System; this is what most people know as the "normal" YaST2 UI. But there is also a NCurses based UI (y2ncurses) for text terminals or consoles.

That means, of course, that all YaST2 dialogs need to be written in a way that is compatible with each of those UIs. This is why libyui was introduced as an intermediate abstract layer between the YCP application and the UI. You do not communicate directly with either y2qt, y2ncurses - you communicate with the libyui.

Thus, YaST2 dialogs need to be described logically rather than in terms of pixel sizes and positions: You specify some buttons to be arranged next to each other rather than at positions (200, 50), (200, 150), (200, 200) etc. - whatever exactly this "next to each other" means to the specific UI.

Add to that the fact that there are several dialog languages to choose from: User messages or button labels have different lengths in different languages. Just compare the length of English messages to those in German or French, and you'll discover another good reason not to hard-code coordinates.

In addition to that, always keep in mind that the same dialog might require a different amount of space in a different UI. Overcrowded dialogs don't look good in the Qt UI. In the NCurses UI, they will very likely break completely: There simply isn't as much space available (80x25 characters vs. 640x480 pixels).

1.1.2.4. The Nice Size

Each widget has a so-called nice size - this is the size the widget would like to have in order to look nice. E.g. for PushButtons that means the entire button label fits into the button. Likewise for labels.

Then there are widgets that don't have a natural nice size. For example, what size should a SelectionBox get? It can scroll anyway, so anything that makes at least one line of the list visible will satisfy the basic requirements. More space will make it look nicer; but how much is enough? The widget cannot tell that by itself.

Such widgets report a somewhat random size as their nice size. This is a number chosen for debugging purposes rather than for aesthetics. You almost always need to specify the size from the outside for that very reason. Always supply a weight for such widgets or surround them with spacings.

1.1.2.5. Initial Dialog Sizes

By default, all dialogs will be as large as they need to be - up to full screen size (which is UI dependent - 640x480 pixels for Qt, 80x25 characters for NCurses): The outermost widget is asked what size it would like to have, i.e. its nice size. If that outermost widget has any children, for example because it is a layout box, it will ask all of its children and sum up the individual sizes. Those in turn may have to ask their children and so on. The resulting size will be the dialog's initial size - unless, of course, this would exceed the screen size (UI dependent, see above).

1.1.2.6. Full Screen Dialogs: `opt(`defaultsize)

You can force full screen size for any dialog by setting the `defaultsize option when opening it:

OpenDialog(
           `opt(`defaultsize ),
           `VBox(...)
          );

This will create a dialog of 640x480 pixels (y2qt) or 80x25 characters (y2ncurses) - regardless of its contents.

Use this for main windows or for popup dialogs with very much the same semantics - e.g. many of the YaST2 installation wizard's "expert" dialogs. Even though they are technically popup dialogs and they return to the main thread of dialog sequence they have main window semantics to the user.

Use your common sense when considering whether or not to use this feature for a particular dialog.

Note: Not every UI may be capable of this feature. This is only a hint to the UI; you cannot blindly rely on it being honored.

1.1.3. Layout Building Blocks

This section covers the widgets used for creating dialog layouts - the kind of widgets that are less obvious to the user. If you are interested in the "real" widgets, i.e. the kind you can actually see, please refer to the widget reference.

1.1.3.1. Layout Boxes: HBox and VBox

This is the most basic and also the most natural layout widget. The HBox widget arranges two or more widgets horizontally, i.e. left to right. The VBox arranges two or more widgets vertically, i.e. top to bottom.

The strategy used for doing this is the same, just the dimensions (horizontal / vertical) are different. Each child widget will be positioned logically next to its neighbor. You don't have to care about exact sizes and positions; the layout box will do that for you.

See the description of the layout algorithm for details.

For creating more complex layouts, nest HBox and VBox widgets into each other. Usually you will have a structure very much like this:

`VBox(
      `HBox(...),
      `HBox(...),
      ...
      )

i.e. a VBox that has several HBoxes inside. Those in turn can have VBoxes inside etc. - nest as deep as you like.

Almost every kind of layout can be broken down into such columns (i.e. VBoxes) or rows (i.e. HBoxes). If you feel you can't do that with your special layout, try using weights.

1.1.3.2. Specifying Proportions: HWeight and VWeight

By default, each widget in a layout box (i.e. in a HBox or a VBox) will get its nice size, no more and no less. If for any reason you don't want that, you can exactly specify the proportions of each widget in the layout box. You do that by supplying the widgets with a weight (to be more exact: by making it the child of a weight widget, a HWeight or a VWeight).

You can specify percentages for weights, or you can choose random numbers. The layout engine will add the weights of all children of a layout box and calculate percentages for each widget automatically. Specify a HWeight for HBox children and a VWeight for VBox children.

Example 1.1. Specifying Proportions 1

`HBox(
      `HWeight( 20, `PushButton( "OK"     ) ),
      `HWeight( 50, `PushButton( "Cancel" ) ),
      `HWeight( 30, `PushButton( "Help"   ) )
     )

In this example, the "OK" button will get 20%, the "Cancel" button 50% and the "Help" button 30% of the available space. In this example, the weights add up to 100, but they don't need to.

Note: This dialog looks extremely ugly - don't try this at home, kids ;-)

The weight ratios will be maintained at all times, even if that means violating nice size restrictions (i.e. a widget gets less space than it needs). You are the boss; if you specify weights, the layout engine assumes you know what you are doing.


Example 1.2. Specifying Proportions 2

(See also creating widgets of equal size in the common layout techniques section)

`HBox(
      `HWeight( 1, `PushButton( "OK"     ) ),
      `HWeight( 1, `PushButton( "Cancel" ) ),
      `HWeight( 1, `PushButton( "Help"   ) )
     )

Note: This is a very common technique.

In this example all buttons will get an equal size. The button with the largest label will determine the overall size and thus the size of each individual button.

Please note how the weights do not add up to 100 here. The value "1" is absolutely random; we might as well have specified "42" for each button to achieve that effect.


Example 1.3. Specifying Proportions 3

The YaST2 wizard layout reserves 30% of horizontal space for the help text (a RichText widget) and the remaining 70% for the rest of the dialog. The important part of that code (simplified for demonstration purposes) looks like that:

`HBox(
      `HWeight( 30, `RichText( "Help text") ),
      `HWeight( 70, `VBox(
                          ...   // the dialog contents
                          `HBox(
                                `PushButton( "Back"),
                                `HCenter( `PushButton( "Abort Installation") ),
                                `PushButton( "Next")
                               )
                          )
              )
      )

Specifying the size of the help text like that is important for most kinds of widgets that can scroll - like the RichText widget used here, for example. The RichText widget can take any amount of space available; it will wrap lines by itself as long as possible and provide scroll bars as necessary. Thus, it cannot supply any reasonable default size on its own - you must supply one. We chose 30% of the screen space - which of course is absolutely random but suits well for the purposes of YaST2.

Use this technique for widgets like the SelectionBox, the Table widget, the Tree widget, the RichText, but also for less obvious ones like the InputField.

Note: This list may be incomplete. Use your common sense.


1.1.3.3. Rubber Bands: HStretch and VStretch

When you don't want parts of a dialog to be resized just because some neighboring widget needs more space, you can insert stretch widgets to take any excess space. Insert a HStretch in a HBox or a VStretch in a VBox. Those will act as "rubber bands" and leave the other widgets in the corresponding layout box untouched.

You can also insert several stretches in one layout box; excess space will be evenly distributed among them.

If there is no excess space, stretch widgets will be invisible. They don't consume any space unless there is too much of it (or unless you explicitly told them to - e.g. by using weights).

1.1.3.4. Making Common Widgets Stretchable: `opt(`hstretch) and `opt(`vstretch)

Some widgets that are not stretchable by default can be made stretchable by setting the `hstretch option. PushButtons are typical candidates for this: They normally consume only as much space as they really need, i.e. their nice size. With the `hstretch option, however, they can grow and take any extra space - very much like stretch widgets.

Please note, however, that all widgets for with a weight are implicitly stretchable anyway, so specifying `opt(`hstretch) or `opt(`vstretch) for them as well is absolutely redundant.

1.1.3.5. Spacings: HSpacing and VSpacing

Use HSpacing or VSpacing to create some empty space within a layout. This is normally used for aesthetical reasons only - to make dialogs appear less cramped.

The size of a spacing is specified as a float number, measured in units roughly equivalent to the size of a character in the respective UI (1/80 of the full screen width horizontally , 1/25 of the full screen width vertically). Fractional numbers can be used here, but text based UIs may choose to round the number as appropriate - even if this means simply ignoring a spacing when its size becomes zero.

You can combine the effects of a spacing and a stretch if you specify a hstretch or a vstretch option for it: You will have a rubber band that will take at least the specified amount of space. Use this to create nicely spaced dialogs with a reasonable resize behaviour.

Example 1.4. Spacings

`HBox(
      `PushButton( "OK" ),
      `HSpacing( `opt(`hstretch), 0.5),
      `PushButton( "Cancel" )
     )

This will create two buttons with a spacing between them. When the dialog is resized, the spacing will grow.


1.1.3.6. Alignments: Left, Right, HCenter, Top, Bottom, VCenter, HVCenter

Alignments are widgets that align their single child widget in some way.

HCenter centers horizontally, VCenter centers vertically, HVCenter centers both horizontally and vertically. The others align their child as the name implies.

More often than not, you could achieve the same effect with a clever combination of spacings, but sometimes this might require an additional HBox within a HBox or a VBox within a VBox, i.e. more overhead.

1.1.3.7. Compressing Excess Space: HSquash, VSquash, HVSquash

Sometimes you wish to squeeze any extra space from a part of a dialog. This might be necessary if you want to draw a frame around a RadioBox in a defaultsize dialog: You want the frame drawn as close as possible to the RadioButtons, not next to the window frame with lots of empty space between the frame and the RadioButtons. Use a squash widget for that purpose:

`HVCenter(
          `HVSquash(
                    `Frame( "Select Software categories",
                           `VBox(
                                 ...
                                )
                          )
                   )
         )

1.1.3.8. Optical Grouping: Frame

This is not exactly a layout-only widget - you can see it. It is being mentioned here more because like layout widgets it can have children.

Use a Frame to visually group widgets that logically belong together - such as the RadioButtons of a RadioBox or a group of CheckBoxes that have a meaning in common (e.g. individual file permissions, software categories to install, ...).

Note: Do not overuse frames. They have a nice visual effect, but only if used sparingly.

You may need to put a squash widget around the frame in order to avoid excessive empty space between the frame and its inner widgets.

1.1.3.9. Grouping RadioButtons: RadioButtonGroup

The RadioButtonGroup is a widget go logically group individual RadioButton widgets. It does not have a visual effect or an effect on the layout. All it does is to manage the one-out-of-many logic of a RadioBox: When one RadioButton is selected, all the others in the same RadioBox (i.e. in the same RadioButtonGroup) must be unselected.

Please notice that this might not be as trivial as it seems to be at first glance: There might be some outer RadioBox that switches between several general settings, enabling or disabling the others as necessary. Any of those general settings might contain another RadioBox - which of course is independent of the outer one. This is why you really need to specify the RadioButtonGroup.

You usually just surround the VBox containing the RadioButtons with a The RadioButtonGroup.

Don't forget to include your RadioBox within a frame! RadioButtonGroup, Frame and HVSquash usually all come together.

Example 1.5. Grouping RadioButtons

`HVCenter(
          `HVSquash(
                    `Frame( "Select Installation Type",
                           `RadioButtonGroup(
                                             `VBox(
                                                   `RadioButton(...),
                                                   `RadioButton(...),
                                                   `RadioButton(...)
                                                  )
                                            )
                          )
                   )
         )

1.1.3.10. The Esoterics: ReplacePoint

A ReplacePoint is a "marker" within the widget hierarchy of a layout. You can later refer to it with ReplaceWidget(). Use this to cut out a part of the widget hierarchy and paste some other sub-hierarchy to this point.

The YaST2 wizard dialogs use this a lot: The main window stays the same, just some parts are replaced as needed - usually the large part to the right of the help text, between the title bar and the "previous" and "next" buttons.

A ReplacePoint has no other visual or layout effect.

1.1.3.11. Obsolete: Split

This is not used any more. If you know anything about it, forget it. If you don't, don't bother. It's old and obsolete and nobody used it anyway.

1.1.4. Common Layout Techniques

Use the case studies in this section as building blocks for your own dialogs.

Remember that even though most of the examples use a horizontal layout (a HBox), the same rules and techniques apply in the vertical dimension as well - just replace HBox with VBox, HWeight with VWeight etc.

1.1.4.1. Creating Widgets of Equal Size

Screen shot of the Layout-Buttons-Equal-Growing.ycp example

You can easily make several widgets the same size - like in this example. Just specify equal weights for all widgets:

`HBox(
      `HWeight(1, `PushButton( "OK"                ) ),
      `HWeight(1, `PushButton( "Cancel everything" ) ),
      `HWeight(1, `PushButton( "Help"              ) )
     )

The widgets will grow or shrink when resized. They will always retain equal sizes:

The same example, resized larger.

The same example, resized smaller.

1.1.4.2. Creating Widgets of Equal Size that don't Grow

Screen shot of the Layout-Buttons-Equal-Even-Spaced1.ycp example

Widgets with a weight (such as these buttons) are implicitly stretchable. If you don't want the widgets to grow, insert stretches without any weight between them. They will take all excess space - but only if there is no weight specified (otherwise, the stretches would always maintain a size according to the specified weight - not what is desired here).

`HBox(
      `HWeight(1, `PushButton( "OK"                ) ),
      `HStretch(),
      `HWeight(1, `PushButton( "Cancel everything" ) ),
      `HStretch(),
      `HWeight(1, `PushButton( "Help"              ) )
     )

The same example, resized larger. Notice how the stretches take the excess space.

The same example, resized smaller. The stretches don't need any space if there is not enough space anyway.

1.1.4.3. Creating Widgets of Equal Size that don't Grow - with Spacings in between

Screen shot of the Layout-Buttons-Equal-Even-Spaced2.ycp example. Notice the spacing between the buttons.

If you want some space between the individual widgets, insert a spacing. You could use both a spacing and a stretch, but specifying the stretchable option for the spacing will do the trick as well - and save some unnecessary widgets:

`HBox(
      `HWeight(1, `PushButton( "OK"                ) ),
      `HSpacing(`opt(`hstretch), 3),
      `HWeight(1, `PushButton( "Cancel everything" ) ),
      `HSpacing(`opt(`hstretch), 3),
      `HWeight(1, `PushButton( "Help"              ) )
     )

The value "3" used here for the spacing is absolutely random, chosen just for aesthetics. Use your own as appropriate.

The same example, resized larger. Notice how the spacings take the excess space.

The same example, resized smaller.

As you can see, the spacings have one disadvantage here: They need the space you specified even if that means that there is not enough space for the other widgets.

1.1.4.4. Specifying the Size of Scrollable Widgets

As mentioned before, most kinds of widgets that can scroll don't have a natural nice size. If the overall size of your layout is fixed by some other means (e.g. because it is a full screen dialog), you can have it take the remaining space or specify proportions with weights.

If this is not the case, create such "other means" yourself: Surround the scrollable widget with widgets of a well-defined size, e.g. with spacings.

Prevent the spacings from actually using precious screen space themselves by putting a VSpacing in a HBox or a HSpacing in a VBox - it will resize the corresponding layout box in its secondary dimension. It will take no space in its primary dimension.

Example 1.6. Specifying the Size of Scrollable Widgets

`VBox(
      `HSpacing(40),            // make the scrollable widget at least 40 units wide
      `HBox(
            `VSpacing(10),      // make the scrollable widget at least 10 units high
            `Table(...)         // or any other scrollable widget
           )
     )

See also the Table2.ycp, Table3.ycp, Table4.ycp and Table5.ycp examples.

As a general rule of thumb, use this technique whenever you place a scrollable widget in a non-defaultsize dialog. Don't leave the size of such widgets to pure coincidence - always explicitly specify their sizes.


1.1.5. Hints and Tips

1.1.5.1. Debugging Aids: The Log File

printf() is your best friend when debugging - every seasoned programmer knows that. YaST2 has something very much like that: y2log(), available both in YCP and in the C++ sources. It is being used a lot, and you can add your own in your YCP code. Thus, if something strange happens, check the log file - either in your home directory (~/.y2log) or the system wide log file (/var/log/y2log).

You can increase the level of verbosity by setting the Y2DEBUG environment variable to 1 - both in your shell and at the boot prompt (for debugging during an installation) - boot with something like

linux Y2DEBUG=1

Log files will be wrapped when they reach a certain size - i.e. the current log file is renamed to ~/.y2log-1, ~/.y2log-2 etc. or /var/log/y2log-1, /var/log/y2log-2 etc., and a new log file is begun.

1.1.5.2. Keep it Simple - Do not Overcrowd Dialogs!

If the layout engine complains about widgets not getting their nice size and tell you to check the layout, please do that before you write a bug report. More often than not that just means that your dialog is overcrowded. That doesn't only raise technical problems: In that case your dialog most likely is too complex and not likely to be understood by novice users. In short, you very likely have a problem with your logical design, not with the layout engine. Consider making it easier or splitting it up into several dialogs - e.g. an easy-to-understand novice level base dialog and an advanced "expert" dialog. Use YaST2's partitioning, software selection and LILO configuration dialogs as examples for how to do this.

You might also consider replacing some widgets with others that don't use as much screen space - e.g. use a ComboBox rather than a SelectionBox, or a ComboBox rather than a RadioBox. But always keep in mind that this just reduces screen space usage, not complexity. Plus, widgets like the ComboBox frequently are harder to operate from a user's point of view because they require more mouse clicks or keys presses to get anything done. Use with caution.

1.1.5.3. Always Keep Other UIs in Mind - What does it Look Like with NCurses?

When you created a new dialog or substantially changed an existing one always remember to check it with the other UIs, too. If it looks good with the Qt UI that doesn't mean it looks good with the NCurses UI as well - it might even break completely. There might be too many widgets or parts of widgets may be invisible because of insufficient screen space.

If you don't like that idea always remember some day you might be that poor guy who can't run YaST2 with Qt - maybe because of a brand new graphics card the X server doesn't support yet or maybe because you have to install a server system that just has a serial console.

The text based version may not need to look as good (but it would sure be nice if it did), but it needs to work. That means all widgets must be there and be visible. If they are not, you really need to rearrange or even redesign your dialog. Possibly before somebody from the support department finds it out the hard way - because a user complained badly about it.

1.1.5.4. Do not Neglect Mouseless Users - Always Provide Keyboard Shortcuts!

Very much the same like the previous issue: Consider somebody who wants or needs to operate your dialog without a mouse. Maybe he doesn't have one or maybe it doesn't work - or maybe he uses the NCurses UI. There are even a lot of users who can work a whole lot quicker if they can use keyboard shortcuts for common tasks - e.g. activating buttons or jumping to text input fields. You can and should provide keyboard shortcuts for each of those kinds of widgets.

Of course this needs to be double-checked with each of the translated versions: Keyboard shortcuts not only are language dependent (so users can memorize them), they are even contained within messages files. The translators need to include their own in the respective language, and that means chances are some of the sort cuts are double used - e.g. Alt-K may be used twice in the same dialog, which renders the second use ineffective. Always check that, too.

1.1.6. The Layout Algorithm - How the Layout Engine Works Internally

You don't need to know the internals of the YaST2 UI layout engine in order to be able to create YaST2 dialogs. But this kind of background knowledge certainly helps a lot when you need to debug a layout - i.e. when a dialog you programmed behaves "strange" and doesn't look at all like you expected.

1.1.6.1. Primary and Secondary Dimensions

A HBox lays out its children horizontally, a VBox vertically. How they do that is very much the same except for the dimensions: The HBox uses horizontal as its primary dimension, the VBox vertical. The other dimension is called the secondary dimension (vertical for the HBox, horizontal for the VBox).

1.1.6.2. Calculating the Nice Size

1.1.6.2.1. Secondary Nice Size

Calculating the nice size in the secondary dimension is easy: It is the maximum of the nice sizes of all children. Thus, for a HBox this is the nice height of the highest child, for a VBox this is the nice width of the widest child.

If any child is a layout box itself (or any other container widget), this process will become recursive for the children of that layout box etc. - this holds true for both the primary and the secondary dimension.

1.1.6.2.2. Primary Nice Size

In the primary dimension things are a bit more complicated: First, the nice sizes of all children without weights are summed up.

Then the sizes of all children with weights are added to that sum - in such a way that each of those gets at least its nice size, yet all weights are maintained with respect to each other. I.e. when a button is supposed to get 30% he must get it, but its label must still be completely visible.

Maybe some of the children with weights need to be resized larger because of those restrictions. Exactly how large is calculated based on the so-called boss child. This is the one widget that commands the overall size of all children with weights, the one with

          

max ( nice size / weight )

The boss child's nice size and its weight determine the accumulated nice size of all children with weights. The other children with weights will be resized larger to get their share of that accumulated size according to their individual weights.

By the way this is why all children with weights are implicitly stretchable - most of them will be resized larger so the weights can be maintained at all times.

1.1.6.3. Setting the Size of a Layout - SetSize()

Each widget has a SetSize() method. This will be called recursively for all widgets from top (i.e. the outer dialog) to bottom. When a dialog is opened, the UI determines how large a dialog should become. The UI tries to use the dialog's nice size, if possible - unless the defaultsize option is set or the nice size exceeds the screen size, in which case the screen size is used.

After the dialog is opened, the SetSize() method will be called again when:

  • The user resizes a dialog.

  • A significant portion of the dialog changes - e.g. because of ReplaceWidget().

All of those cases will cause a re-layout of the entire dialog.

For layout boxes, the SetSize() method works like this:

If none of the children of a layout box has a weight, any extra space (i.e. space in excess of the nice size) is evenly distributed among the stretchable children. All non-stretchable children get their nice size, no more.

If there are not any stretchable children, there will be empty space at the end of the layout (i.e. to the right for a HBox and at the bottom of a VBox). If any child has a weight, all children without weights will get no more than their nice sizes - no matter whether or not they are stretchable.

The rest of the space will be distributed among the children with weights according to the individual weights.

There is one exception to that rule, however: If there is more space than the weighted childrens' nice size and there are any stretches or stretchable spacings without weights, the excess space will be evenly distributed among them.

This may sound like a very pathological case, but in fact only this gives the application programmer a chance to create equal sized widgets that don't grow, maybe with a little extra space between them. Simple popup dialogs with some buttons are typical examples for this, and this is quite common.

1.1.6.3.1. Running out of Space - the Pathological Cases

There should be enough space for any layout box: By default, the overall size of a dialog is calculated based on its nice size. But this might exceed the full screen size, or the user might manually have resized the dialog (some UIs are capable of that) - both of which cases will cause a dialog to get less than its nice size.

If there is not enough space, the layout engine will complain about that fact in the log file, asking you to "check the layout". Please do that if this message always appears when a certain dialog is opened - you may have to rearrange your dialog so all widgets properly fit into it.

Anyway, if it happens, some widgets will get less than their nice size and probably will not look good; some might even be completely invisible.

Even then, as long as there is enough space for all children without weights, those will get their nice sizes. Only the remaining space will be distributed among the children with weights.

If the space isn't even enough for the children without weights, each of them will have to spend some of its space to make up for the loss. The layout engine tries to treat each of them equally bad, i.e. each of them has to give some space.

1.1.6.3.2. Centering in the Secondary Dimension
stretchable

This behaviour may be somewhat unexpected, but not only is this compatible with older versions of the YaST2 UI, it also comes very handy for simple layout tasks like this (taken from the Label1.ycp example):

`VBox(
      `Label( "Hello, world" ),
      `PushButton( "OK" )
     )

This button will be centered horizontally - without the need for a HCenter around it.

1.2. UI Events

1.2.1. Introduction

1.2.1.1. The YaST2 Event Model

1.2.1.1.1. Classic GUI Event Loops

Classic graphical user interface (GUI) programming is almost always event-driven: The application initializes, creates its dialog(s) and then spends most of its time in one central event loop.

When the user clicks on a button or enters text in an input field, he generates events. The underlying GUI toolkit provides mechanisms so the application can react to those events - perform an action upon button click, store the characters the user typed etc.; all this is done from callback functions of one kind or the other (whatever they may be called in the respective GUI toolkit).

In any case, it all comes down to one single event loop in the application from where small functions (let's call them callbacks for the sake of simplicity) are called when events occur. Those callbacks each contain a small amount of the application's GUI logic to do whatever is to be done when the respective event occurs. The overall application logic is scattered among them all.

This approach is called event-driven. Most GUI toolkits have adopted it.

Depending on the primary goal of a GUI application, this event-driven approach may or may not be appropriate. It is perfectly suitable for example for word processor applications, for web browsers or for most other GUI applications that have one central main window the user works with most of his time: The user is the driving force behind those kinds of applications; only he knows what he next wishes to do. The application has no workflow in itself.

Thus the event-driven application model fits perfectly here: The callbacks can easily be self-contained; there is little context information, and there are limited application-wide data.

1.2.1.1.2. The YaST2 Approach

Applications like YaST2 with all its installation and configuration workflows, however, are radically different. The driving force here is the application workflow, the sequence of dialogs the user is presented with.

Of course this can be modeled with a traditional event loop, but doing that considerably adds to the complexity of the application: Either the application needs a lot more callbacks, or the callbacks need to keep track of a lot of status information (workflow step etc.) - or both.

For the YaST2 UI, a different approach was chosen: Rather than having one central event loop and lots of callbacks, the flow control remains in the interpreted YCP code. User input is requested on demand - very much like in simplistic programming languages like the first versions of BASIC.

This of course means that there is no single one central "waiting point" in the program (like the event loop in the event-driven model), but rather lots of such waiting points spread all over the YCP code within each UserInput() or WaitForEvent() statement.

Side note: Of course a graphical UI like the YaST2 Qt UI still has to be prepared to perform screen redraws whenever the underlying window system requires that - i.e. whenever X11 sends an Expose (or similar) event. For this purpose the Qt UI is multi-threaded: One thread takes care of X event handling, one thread is the actual YCP UI interpreter. This instant screen redraw is what you lose when you invoke y2base with the "--nothreads" command line option.

YCP was meant to be an easy-to-understand programming language for developers who specialize in a particular aspect of system configuration or installation, not in GUI programming.

Practical experience with all the YaST2 modules developed so far has shown that application developers tend to adopt this concept of UserInput() very easily. On the other hand it is a widely known fact that event-driven GUI programming means a steep learning curve because (as mentioned before) it requires splitting up the application logic into tiny pieces for all the callbacks.

Thus, this design decision of YaST2 seems to have proven right much more often than there are problems with its downsides (which of course also exist).

1.2.1.1.3. Simplicity vs. Features

The basic idea of YaST2 UI programming is to create a dialog asking the user for some data and then continue with the next such dialog - meaning that most of those dialogs are basically forms to be filled in with an "OK" (or "Next") and a "Cancel" (or "Back") button. The YCP application is usually interested only in those button presses, not in each individual keystroke the user performs.

This is why by default UserInput() and related functions react to little more than button presses - i.e. they ignore all other events, in particular low-level events the widgets handle all by themselves like keystrokes (this is the input fields' job) or selecting items in selection boxes, tables or similar. Most YCP applications simply don't need or even want to know anything about that.

This makes YCP UI programming pretty simple. The basic principle looks like this:

{
    UI::OpenDialog(
            `VBox(
                ... // Some input fields etc.
                `HBox(
                    `PushButton(`id(`back ), "Back" ),
                    `PushButton(`id(`next ), "Next" )
                    )
                )
            );

    symbol button_id = UI::UserInput();

    if ( button_id == `next )
    {
        // Handle "Next" button
    }
    else if ( button_id == `back )
    {
        // Handle "Back" button
    }

    UI::CloseDialog();
}
                

Strictly spoken, you don't even require a loop around that - even though this is very useful and thus strongly advised.

All that can make UserInput() return in this example are the two buttons. Other widgets like input fields ( InputField), selection boxes etc. by do not do anything that makes UserInput() return - unless explicitly requested.

1.2.1.1.4. The notify Option

If a YCP application is interested in events that occur in a widget other than a button, the notify widget option can be used when creating it with UI::OpenDialog().

Example 1.7. The notify option

UI::OpenDialog(...
  `SelectionBox(`id(`pizza ), `opt(`notify ), ... ),
   ...
  `Table(`id(`toppings), `opt(`notify, `immediate ), ... ),
  ...
)
                    

In general, the notify options makes UserInput() return when something "important" happens to that widget. The immediate option (always in combination with notify!) makes the widget even more "verbose".

Note: UserInput() always returns the ID of the widget that caused an event. You cannot tell the difference when many different types of event could have occured. This is why there are different levels of verbosity with `opt(`notify ) or `opt(`notify, `immediate ) and the new WaitForEvent() UI builtin function which returns more detailed information. A Table widget for example can generate both Activated and SelectionChanged WidgetEvents.

Exactly what makes UserInput() return for each widget class is described in full detail in the YaST2 event reference.


1.2.1.1.5. Downsides and Discussions

The YaST2 event handling model has been (and will probably always remain) a subject of neverending discussions. Each and every new team member and everybody who casually writes a YaST2 module (to configure the subsystem that is his real responsibility) feels compelled to restart this discussion.

The idea of having a function called UserInput() seems to conjure up ghastly memories of horrible times that we hoped to have overcome: The days of home-computer era BASIC programming or university Pascal lectures (remember Pascal's readln()?) or even low-tech primitive C programs (gets() or scanf() are not better, either).

But it's not quite like that. Even though the function name is similar, the concept is radically different: It is not just one single value that is being read, it is a whole dialog full of whatever widgets you see fit to put there. All the widgets take care of themselves; they all handle their values automatically. You just have to ask them (UI::QueryWidget()) for the values when you need them (leave them alone as long as you don't).

The similarity with computing stone age remains, however, in that you have to explicitly call UserInput() or related when you need user input. If you don't, you open your dialog, and a moment later when you continue in your code it closes again - with little chance for the user to enter anything.

Thus, the YaST2 approach has its intrinsic formalisms in that sequence:

OpenDialog(...);

UserInput();
QueryWidget(...);
QueryWidget(...);
QueryWidget(...);
...

CloseDialog();

                

This is the price to pay for this level of simplicity.

1.2.1.1.6. Design Alternatives

In the course of those discussions some design alternatives began to emerge:

  1. Use the single-event-loop and callback model like most other toolkits.

  2. Keep multiple event loops (like UserInput()), but add callbacks to individual widget events when needed so the YCP application can do some more fine-grained control of individual events.

  3. Keep multiple event loops, but return more information than this simplistic UserInput() that can return no more than one single ID.

Having just a single event loop would not really solve any problem, but create a lot of new ones: A sequence of wizard style dialogs would be really hard to program. Switching back and forth between individual wizard dialogs would have to be moved into some callbacks, and a lot of status data for them all to share (which dialog, widget status etc.) would have to be made global.

What a mess. We certainly don't want that.

All the callback-driven models have one thing in common: Most of the application logic would have to be split up and moved into the callbacks. The sequence of operations would be pretty much invisible to the application developer, thus the logical workflow would be pretty much lost.

Most who discussed that agreed that we don't want that, too.

Add to that the formalisms that would be required for having callbacks: Either add a piece of callback code (at least a function name) to UI::OpenDialog() for each widget that should get callbacks or provide a new UI builtin function like, say, UI::SetCallback() or UI::AddCallback() that gets a YCP map that specifies at least the widget to add the callback to, the event to react to and the code (or at least a function name) to execute and some transparent client data where the application can pass arbitrary data to the callback to keep the amount of required global data down.

UI::RemoveCallback()

It might look about like this:

define void selectionChanged( any widgetID, map event, any clientData ) {
   ...
   // Handle SelectionChanged event
   ...
};

define void activated( any widgetID, map event, any clientData ) {
   ...
   // Handle Activated event
   ...
};

...
UI::OpenDialog(
   ...
   `Table(`id(`devices ), ... ),
   ...
);
...
UI::AddCallback(`id(`devices ), `SelectionChanged, nil );
UI::AddCallback(`id(`devices ), `Activated, nil );

                

If you think "oh, that doesn't look all too bad", think twice. This example is trivial, yet there are already three separate places that address similar things:

  • The callback definitions. Agreed, you'll need some kind of code that actually does the application's business somewhere anyway. But chances are that the callbacks are no more than mere wrappers that call the functions that actually do the application's operations. You don't want to mix up all the back engine code with the UI related stuff.

  • Widget creation with UI::OpenDialog()

  • Adding callbacks with UI::AddCallback()

A lot of GUI toolkits do it very much this way - most Xt based toolkits for example (OSF/Motif, Athena widgets, ...). But this used to be a source of constant trouble: Change a few things here and be sure that revenge will come upon you shortly. It simply adds to the overall complexity of something that is already complex enough - way enough.

Bottom line: Having callbacks is not really an improvement.

What remains is to stick to the general model of YaST2 but return more information - of course while remaining compatible with existing YCP code. We don't want (neither can we economically afford to) break all existing YCP code. So the existing UI builtin functions like UserInput() or PollInput() have to remain exactly the same. But of course we can easily add a completely new UI builtin function that does return more information.

This is what we did. This is how WaitForEvent() came into existence. It behaves like UserInput(), but it returns more information about what really happened - in the form of an event map rather than just a single ID. That map contains that ID (of course) plus additional data depending on the event that occured.

One charming advantage of just adding another UI builtin is that existing code does not need to be touched at all. Only if you want to take advantage of the additional information returned by WaitForEvent() you need to do anything at all.

So let's all hope with this approach we found a compromise we all can live with. While that probably will not prevent those discussions by new team members, maybe it will calm down the current team members' discussion a bit. ;-)

1.2.1.2. Event Delivery

1.2.1.2.1. Event Queues vs. One Single Pending Event

Since the YaST2 UI doesn't have a single event loop where the program spends most of its time, an indefinite period of time may pass between causing an event (e.g., the user clicks on a widget) and event delivery - the time where the (YCP) application actually receives the event and begins processing it. That time gap depends on exactly when the YCP code executes the next UserInput() etc. statement.

This of course means that events that occured in the mean time need to be stored somewhere for the YCP code to pick them up with UserInput() etc.

The first approach that automatically comes to mind is "use a queue and deliver them first-in, first-out". But this brings along its own problems:

Events are only useful in the context of the dialog they belong to. When an event's dialog is closed or when a new dialog is opened on top of that event's dialog (a popup for example) it doesn't make any more sense to handle that event. Even worse, it will usually lead to utter confusion, maybe even damage.

Imagine this situation: The user opens a YaST2 partitioning module just to have a look at his current partitioning scheme.

Side note: This scenario is fictious. The real YaST2 partitioning module is not like that. Any similarities with present or past partitioning modules or present or past YaST2 hackers or users is pure coincidence and not intended. Ah yes, and no animals were harmed in the process of making that scenario. ;-)

  • The main dialog with an "OK" button (with, say, ID `ok) opens.

  • It takes some time to initialize data in the background.

  • The user clicks "OK".

  • The background initialization takes some more time.

  • The user becomes impatient and clicks "OK" again.

  • The background initialization still is not done.

  • The user clicks "OK" again.

  • The initialization is done. Usually, the YCP code would now reach UserInput() and ueued events would be delivered (remember, this is only a fictious scenario - the UI does not really do that). The first "OK" click from the queue is delivered - i.e. UserInput() returns `ok.

    But this doesn't happen this time: The initialization code found out that something might be wrong with the partitioning or file systems. It might make sense to convert, say, the mounted /usr file system from oldLameFs-3.0 to newCoolFs-0.95Beta - which usually works out allright, but of course you never know what disaster lies ahead when doing such things with file systems (and, even worse, with an experimental beta version).

  • The initialization code opens a popup dialog with some text to informs the user about that. The user can now click "OK" to do trigger the file system conversion or "Cancel" to keep everything as it is.

  • The handler for that popup dialog calls UserInput() - which happily takes the next event from the queue - the `ok button click that doesn't really belong to that dialog, but UserInput() cannot tell that. Neither can the caller. It simply gets `ok as if the user had clicked the "OK" button in the popup.

  • The program has to assume the user confirmed the request to convert the file system. The conversion starts.

  • The experimental beta code in newCoolFs-0.95Beta cannot handle the existing data in that partition as it should. It asks if it is allright to delete all data on that partition. Another popup dialog opens with that question.

  • The handler for that confirmation popup takes the next event from the queue which is the third `ok click that should have gone to the main window. But the handler doesn't know that and takes that `ok as the confirmation it asked for.

  • /usr is completely emptied. Half of the system is gone (along with most of YaST2's files). The disaster is complete - the system is wrecked beyond repair.

Argh. What a mess.

Yes, this example is contrived. But it shows the general problem: Events belong to one specific dialog. It never makes any sense to deliver events to other dialogs.

But this isn't all. Even if the internal UI engine (the libyui) could make sure that events are only delivered to the dialog they belong to (maybe with a separate queue for each dialog), events may never blindly be taken from any queue. If the user typed (or clicked) a lot ahead, disaster scenarios similar to the one described above might occur just as well.

Events are context specific. The dialog they belong to is not their only context; they also depend on the application logic (i.e. on YCP code). This is another byproduct of the YaST2 event handling approach.

It has been suggested to use (per-dialog) event queues, but to flush their contents when the dialog context changes:

  • When a new dialog is opened (OpenDialog())

  • When the current dialog is closed (CloseDialog())

  • When parts of the dialog are replaced (ReplaceWidget())

  • Upon the YCP application's specific request (new UI builtin FlushEvents())

Exactly when and how this should happen is unclear. Every imaginable way has its downsides or some pathologic scenarios. You just can't do this right. And YCP application developers would have to know when and how this happens - which is clearly nothing they should be troubled with.

This is why all current YaST2 UIs have onle one single pending event and not a queue of events. When a new event occurs, it usually overwrites any event that may still be pending - i.e. events get lost if there are too many of them (more than the YCP application can and wants to handle).

1.2.1.2.2. Event Reliability

While it may sound critical to have only one single pending event, on this works out just as everybody expects:

  • When the YCP application is busy and the user clicks wildly around in the dialog, only the last of his clicks is acted upon. This is what all impatient users want anyway: "do this, no, do that, no, do that, no, cancel that all". The "Cancel" is what he will get, not everything in the sequence he clicked.

  • The YCP application does not get bogged down by a near-endless sequence of events from the event queues. If things are so sluggish that there are more events than the application can handle in the first place, getting even more to handle will not help any.

  • YaST2 dialogs are designed like fill-in forms with a few (not too many) buttons. The input field widgets etc. are self-sufficient; they do their own event handling (so no typed text will get lost). No more than one button click in each dialog makes sense anyway. After that the user has to wait for the next dialog to answer more questions. It does not make any sense to queue events here; the context in the next dialog is different anyway.

As described above, events can and do get lost if there are too many of them. This is not a problem for button clicks (the most common type of event), and it should not be a problem for any other events if the YCP application is written defensively.

1.2.1.2.3. Defensive Programming

Don't take anything for granted. Never rely on any specific event to always occur to make the application work allright.

In particular, never rely on individual SelectionChanged WidgetEvents to keep several widgets in sync with each other. If the user clicks faster than the application can handle, don't simply count those events to find out what to do. Always treat that as a hint to find out what exactly happened: Ask the widgets about their current status. They know best. They are what the user sees on the screen. Don't surprise the user with other values than what he can see on-screen.

In the past, some widgets that accepted initially selected items upon creation had sometimes triggered events for that initial selection, sometimes not. Even though it is a performance optimization goal of the UI to suppress such program-generated events, it cannot be taken for granted if they occur or not. But it's easy not to rely on that. Instead of writing code like this:

{
    // Example how NOT to do things

    UI::OpenDialog(
                    ...
                    `SelectionBox(`id(`colors ),
                        [
                            `item(`id("FF0000"), "Red" ),
                            `item(`id("00FF00"), "Blue",true),  // Initially selected
                            `item(`id("0000FF"), "Green" )
                        ] 
                    )
    );

    // Intentionally NOT setting the initial color:
    //
    // Selecting an item in the SelectionBox upon creation will trigger a
    // SelectionChanged event right upon entering the event loop.
    // The SelectionChanged handler code will take care of setting the initial color.
    // THIS IS A STUPID IDEA!

    map event = $[];

    repeat
    {
          event = UI::WaitForEvent();

          if ( event["ID"]:nil == `colors )
          {
              if ( event["EventReason"]:nil == "SelectionChanged" )
              {
                  // Handle color change
                  setColor( UI::QueryWidget(`id(`colors ), `SelectedItem ) );
              }
          }
          ...
    } until ( event["ID"]:nil == `close );
}

                
{
    // Fixed the broken logic in the example above

    UI::OpenDialog(
                    ...
                    `SelectionBox(`id(`colors ),
                    [
                    `item(`id("FF0000"), "Red" ),
                    `item(`id("00FF00"), "Blue",true),  // Initially selected
                    `item(`id("0000FF"), "Green" )
                    ] ),
                    );

    // Set initial color
    setColor( UI::QueryWidget(`id(`colors ), `SelectedItem ) );

    map event = $[];

    repeat
    {
        event = UI::WaitForEvent();

        if ( event["ID"]:nil == `colors )
        {
            if ( event["EventReason"]:nil == "SelectionChanged" )
            {
                // Handle color change
                setColor( UI::QueryWidget(`id(`colors ), `SelectedItem ) );
            }
        }
        ...
    } until ( event["ID"]:nil == `close );
}

                

It's that easy. This small change can make code reliable or subject to failure on minor outside changes - like a version of the Qt lib that handles things differently and sends another SelectionChanged Qt signal that might be mapped to a SelectionChanged WidgetEvents - or does not send that signal any more like previous versions might have done.

Being sceptical and not believing anything, much less taking anything for granted is an attitude that most programmers adopt as they gain more an more programming experience.

Keep it that way. It's a healthy attitude. It helps to avoid a lot of problems in the first place that might become hard-to-find bugs after a while.

1.2.2. Event-related UI Builtin Functions

This section describes only those builtin functions of the YaST2 user interface that are relevant for event handling. The YaST2 UI has many more builtin functions that are not mentioned here. Refer to the UI builtin reference for details.

The Event-related UI Builtin are available in the reference

1.2.3. Event Reference

1.2.3.1. Event Maps in General

Use WaitForEvent() to get full information about a YaST2 UI event. UserInput() only returns a small part of that information, the ID field of the event map.

The event map returned by WaitForEvent() always contains at least the following elements:

Map KeyValue TypeValid ValuesDescription
EventTypestring
  • The type of this event.

  • Use this for general event classification.

IDany The ID (a widget ID for WidgetEvents) that caused the event. This is what UserInput() returns.
EventSerialNointeger>= 0The serial number of this event. Intended for debugging.

1.2.3.2. Event Types

1.2.3.2.1. WidgetEvent

All WidgetEvents have these map fields in common:

Map KeyValue TypeValid ValuesDescription
EventTypestringWidgetEvent(constant)
EventReasonstring The reason for this event. This is something like an event sub-type. Use this to find out what the user really did with the widget.
IDany The ID of the widget that caused the event. This is what UserInput() returns.
WidgetIDany The ID of the widget that caused the event. This is nothing but an alias for "ID", but with this alias you can easily find out if this is a widget event at the same time as you retrieve the widget ID: No other events than WidgetEvent have this field.
WidgetClassstringPushButton SelectionBox Table CheckBox ...The class (type) of the widget that caused the event.
WidgetDebugLabelstring 

The label (more general: the widget's shortcut property) of the widget that caused the event - in human readable form without any shortcut markers ("&"), maybe abbreviated to a reasonable length.

This label is translated to the current locale (the current user's language).

This is intended for debugging so you can easily dump something into the log file when you get an event.

Wigets that don't have a label don't add this field to the event map, so make sure you use a reasonable default when using a map lookup for this field: Don't use nil, use "" (the emtpy string) instead.

1.2.3.2.2. Activated WidgetEvent
Map KeyValue TypeValid ValuesDescription
EventReasonstringActivated(constant)

An Activated WidgetEvent is sent when the user explicitly wishes to activate an action.

Traditionally, this means clicking on a PushButton or activating it with some other means like pressing its shortcut key combination, moving the keyboard focus to it and pressing space.

Some other widgets (Table, SelectionBox, Tree) can also trigger this kind of event if they have the notify option set.

User interface style hint: YCP applications should use this to do the "typical" operation of that item - like editing an entry if the dialog has an "Edit" button. Use this Activated WidgetEvent only as a redundant way (for "power users") of invoking an action. Always keep that "Edit" (or similar) button around for novice users; double-clicks are by no way obvious. The user shouldn't need to experiment how to get things done.

Widget TypeWidget OptionsAction to Trigger the Event
PushButton(none)
  • Single click on the button (Qt).

  • Press space on the button.

  • Press return anywhere in the dialog. This activates the dialog's default button if it has any and if the respective UI can handle default buttons.

Table`opt(`notify)
  • Double click on an item (Qt).

  • Press space on an item.

SelectionBox`opt(`notify)
  • Double click on an item (Qt).

  • Press space on an item.

Tree`opt(`notify)
  • Double click on an item (Qt).

    Note: This will also open or close items that have children!

  • Press space on an item.

Note that MenuButton and RichText don't ever send WidgetEvents. They send MenuEvents instead.

1.2.3.2.3. ValueChanged WidgetEvent
Map KeyValue TypeValid ValuesDescription
EventReasonstringValueChanged(constant)

A ValueChanged WidgetEvent is sent by most interactive widgets that have a value that can be changed by the user. They all require the notify option to be set to send this event.

Widgets that have the concept of a "selected item" like SelectionBox, Table, or Tree don't send this event - they send a SelectionChanged WidgetEvent instead. One exception to this rule is the MultiSelectionBox which can send both events, depending on what the user did.

Widget TypeWidget OptionsAction to Trigger the Event
MultiSelectionBox`opt(`notify)Toggle an item's on/off state:
  • Click on an item's checkbox (Qt).

  • Press space on an item.

CheckBox`opt(`notify)Toggle the on/off state:
  • Single click the widget (Qt).

  • Press space on the widget.

RadioButton`opt(`notify)Set this RadioButton to on:
  • Single click the widget (Qt).

  • Press space on the widget.

No event is sent when the button's status is set to off because another RadioButton of the same RadioButtonGroup is set to on to avoid generating a lot of useless events: Only the on case is relevant for most YCP applications.
`opt(`notify)Enter text.
ComboBox`opt(`notify)
  • Select another value from the drop-down list:

    • Open the drop-down list and click on one of its items (Qt).

    • Open the drop-down list, use the cursor keys to move the selection and press space or return to actually accept that item.

      Simply opening the drop-down list and moving the cursor around in it (i.e. changing its selection) does not trigger this event.

  • Enter text (with `opt(`editable ) ).

IntField`opt(`notify)Change the numeric value:
  • Enter a number.

  • Click on the up button (Qt).

  • Click on the down button (Qt).

  • Press cursor up in the widget (NCurses).

  • Press cursor down in the widget (NCurses).

Slider`opt(`notify)
  • Move the slider.

  • Enter a number in the embedded IntField.

  • Use one of the embedded IntField's up / down button.

PartitionSplitter`opt(`notify)
  • Move the slider.

  • Enter a number in one of the embedded IntFields.

  • Use one of the embedded IntFields' up / down button.

1.2.3.2.4. SelectionChanged WidgetEvent
Map KeyValue TypeValid ValuesDescription
EventReasonstringSelectionChanged(constant)

A SelectionChanged WidgetEvent is sent by most widgets that have the concept of a "selected item" like SelectionBox, Table, or Tree when the selected item changes.

Note that the MultiSelectionBox widget can send a SelectionChanged event, but also a ValueChanged WidgetEvent depending on what the user did. This is one reason to keep SelectionChanged and ValueChanged two distinct events: Widgets can have both concepts which may be equally important, depending on the YCP application.

The ComboBox never sends a SelectionChanged event. It only sends ValueChanged WidgetEvents.

The rationale behind this is that merely opening the drop-down list without actually accepting one of its items is just a temporary operation in a separate pop-up window (the drop-down list) that should not affect the YCP application or other widgets in the same dialog until the user actually accepts a value - upon which event a ValueChanged WidgetEvent is sent.

Widget TypeWidget OptionsAction to Trigger the Event
SelectionBox
Qt:`opt(`notify)
NCurses:`opt(`notify,`immediate)
Select another item:
  • Click on an item (Qt).

  • Press cursor up in the widget.

  • Press cursor down in the widget.

Qt:`opt(`notify) 
NCurses:`opt(`notify,`immediate) 
Table `opt(`notify,`immediate)Select another item:
  • Click on an item (Qt).

  • Press cursor up in the widget.

  • Press cursor down in the widget.

Tree `opt(`notify)Select another item:
  • Click on an item (Qt).

  • Press cursor up in the widget.

  • Press cursor down in the widget.

MultiSelectionBox`opt(`notify)Select another item:
  • Click on an item's text (not on the checkbox) (Qt).

  • Press cursor up in the widget.

  • Press cursor down in the widget.

1.2.3.2.5. MenuEvent
Map KeyValue TypeValid ValuesDescription
EventTypestringMenuEvent(constant)
IDany 

The ID of the menu item the user selected or the href target (as string) for hyperlinks in RichText widgets.

Notice:This is not the widget ID, it is a menu item or hyperlink ID inside that MenuButton or RichText widget!

A MenuEvent is sent when the user activates a menu entry in a MenuButton or a hyperlink in a RichText widget.

Since the ID of the MenuButton or RichText widget is irrelevant in either case, this is not another subclass of WidgetEvent; the ID field has different semantics - and remember, the ID field is the only thing what UserInput() returns so this is particularly important.

For most YCP applications this difference is purely academic. Simply use the ID and treat it like it were just another button's ID.

No notify option is necessary for getting this event. Both MenuButton and RichText deliver MenuEvents right away.

1.2.3.2.6. TimeoutEvent
Map KeyValue TypeValid ValuesDescription
EventTypestringTimeoutEvent(constant)
IDsymbol`timeout(constant)

A TimeoutEvent is sent when the timeout specified at WaitForEvent() or TimeoutUserInput() is expired and there is no other event pending (i.e. there is no other user input).

PollInput() never returns a TimeoutEvent; it simply returns nil if there is no input.

1.2.3.2.7. CancelEvent
Map KeyValue TypeValid ValuesDescription
EventTypestringCancelEvent(constant)
IDsymbol`cancel(constant)

A CancelEvent is an event that is sent when the user performs a general "cancel" action that is usually not part of the YCP application.

For the Qt UI, this means he used the window manager close button or a special key combination like Alt-F4 to close the active dialog's window. For the NCurses UI, this means he hit the ESC key.

User interface style hint: It is usually a good idea for each dialog to provide some kind of "safe exit" anyway. Most popup dialogs (at least those that have more than just a simple "OK" button) should provide a "Cancel" button. If you use the widget ID `cancel for that button, CancelEvents integrate seamlessly into your YCP application.

"Main window" type dialogs should have an "Abort" button or something similar. If you don't use the widget ID `cancel for that button, don't forget to handle `cancel or "CancelEvent" like that "Abort" button. The user should always have a safe way out of a dialog - preferably one that doesn't change anything. Don't forget to add a confirmation popup before you really exit if there are unsaved data that might get lost!

1.2.3.2.8. KeyEvent

KeyEvents are specific to the NCurses UI. They are not intended for general usage. The idea is to use them where the default keyboard focus handling is insufficient - for example, when the logical layout of a dialog is known and the keyboard focus should be moved to the logically right widget upon pressing the cursor right key.

Widgets deliver KeyEvents if they have `opt( keyEvent ) set. This is independent of the notify option.

It is completely up to the UI what key presses are delivered as key events. Never rely on each and every key press to be delivered.

Map KeyValue TypeValid ValuesDescription
EventTypestringKeyEvent(constant)
IDstring


                                    CursorRight
                                    CursorDown
                                    F1
                                    a
                                    A
                                    ...
                                

The key symbol of this event in human readable form. This is what UserInput() returns.
KeySymbolstring


                                    CursorRight
                                    CursorDown
                                    F1
                                    a
                                    A
                                    ...
                                

The key symbol of this event in human readable form. This is nothing but an alias for "ID", but with this alias you can easily find out if this is a key event at the same time as you retrieve the key symbol: No other events than KeyEvent have this field.
FocusWidgetIDany The ID of the widget that currently has the keyboard focus. Unlike a WidgetEvent, this is not the same as "ID".
FocusWidgetClassstringTextEntry SelectionBox ...The class (type) of the widget that has the keyboard focus.
FocusWidgetDebugLabelstring 

The label (more general: the widget's shortcut property)