/**
 * File:	modules/Sound.ycp
 * Package:	Sound configuration
 * Summary:	Sound data
 * Authors:	Ladislav Slezak <lslezak@suse.cz>
 *
 * $Id: Sound.ycp 22766 2005-03-23 15:43:06Z lslezak $
 */

{

    module "Sound";
    textdomain "sound";

    import "Arch";
    import "Mode";
    import "Summary";
    import "Crash";
    import "Progress";
    import "Label";
    import "Confirm";
    import "String";

    /**
     * what sound system we're using (true=alsa, false=oss)
     */
    global boolean use_alsa = true;

    include "sound/texts.ycp";

    /**
     * id of current card
     */
    global integer card_id	= 0;

    /**
     * configuration map of current card
     */
    global map save_entry	= $[];

    /**
     * text constants for sound module
     */
    global map STRINGS		= STRINGS_();

    /**
     * flag for letting the module know, that it's launched from then hardware
     * installation screen
     */
    global boolean installation = false;

    /**
     * was the proposal already generated?
     */
    global boolean proposal_created = false;

    /**
     * write only mode? (-> do not restart services during writing)
     */
    global boolean write_only = false;

    /**
     * list for storing detected cards
     */
    global list<map> detected_cards = nil;

    /**
     * list for storing information about removed cards
     */
    global list<map> removed_info = [];

    /**
     * list of detected and unconfigured cards
     */
    global list<map> unconfigured_cards = nil;

    /**
     * settings to save to /etc/modules.conf (only those concerning to sound)
     */
    global list<map> modules_conf = [];

    /**
     * settings to save using .audio.alsa ... mixer
     */
    global list volume_settings = [];

    /**
     * rc settings
     */
    global map rc_vars = $[];

    /**
     * alsa sound card database
     */

    /**
     * card list
     */
    global map db_cards = nil;
    /**
     * module details
     */
    global map db_modules = nil;
    /**
     *
     */
    global map db_indices = nil;

    /**
     * map of card vendors
     */
    global map db_vendors = nil;

    /**
     *
     */
    global map db_module_index = nil;

    /**
     * flag for showing/not showing ui stuff (eg when loading alsa database)
     */
    global boolean use_ui = false;

    /**
     * currently used card vendor (pointer to table)
     */
    global string curr_vendor = "";

    /**
     * currently used card driver (pointer to table)
     */
    global string curr_driver = "";

    /**
     * currently used card model (pointer to table)
     */
    global string curr_model = "";

    // if joystick dialog should be started after sound card config
    global boolean conf_joystick = true;

    /**
     * default value of volume for new card
     */
    global integer default_volume = 75;

    /**
     * This is true, if sound data were read from /etc/modprobe.conf
     * On write, they shoud be removed and written only to /etc/modprobe.d/sound
     */
    global boolean used_modprobe_conf	= false;


    /**
     * When true:
     * During autoinstallation, detected cards will be added automaticaly
     * even if they are not defined in control file
     */
    global boolean configure_detected	= false;

    /**
     * backup structures for sound settings
     */
    global list<map> modules_conf_b = nil;
    map rc_vars_b = nil;
    list volume_settings_b = nil;


    /* default value of settings modified */
    global boolean modified = false;

    /**
     * Function sets internal variable, which indicates, that any
     * settings were modified, to "true"
     */
    global define void SetModified () {
        modified = true;
    }

    /**
     * Functions which returns if the settings were modified
     * @return boolean  settings were modified
     */
    global define boolean GetModified () {
        return modified;
    }

    // ----------- prototypes for routines.ycp:

    global define void LoadDatabase (boolean use_ui);
    global define list CreateModprobeCommands();
    global define string ProbeOldChip (string chip);

    // ----------- rest of included files:

    include "sound/read_routines.ycp";
    include "sound/routines.ycp";

    // ----------- function definitions:

    /* tiwai: /usr/src/linux/Documentation/sound/alsa/Joystick.txt
PCI Cards
---------

For PCI cards, the joystick is enabled when the appropriate module
option is specified.  Some drivers don't need options, and the
joystick support is always enabled.  In the former ALSA version, there
was a dynamic control API for the joystick activation.  It was
changed, however, to the static module options because of the system
stability and the resource management.

The following PCI drivers support the joystick natively.
    */
    global map joystick_configuration	= $[
	"snd-als4000": $[
	    "joystick_port"	: "1",
	],
	"snd-azt3328": $[
	    "joystick"		: "1",
	],
	"snd-ens1370": $[
	    "joystick"		: "1",
	],
	"snd-ens1371": $[
	    "joystick_port"	: "1",
	],
	"snd-cmipci": $[
	    "joystick_port"	: "1",
	],
	"snd-es1968": $[
	    "joystick"		: "1",
	],
	"snd-intel8x0": $[
	    "joystick"		: "1",
	],
	"snd-via82xx": $[
	    "joystick"		: "1",
	],
	"snd-ymfpci" : $[
	    "joystick_port"	: "1",
	]
    ];

    /**
     * sound module constructor
     */
    global define void Sound() ``{
	if (Arch::sparc ())
	{
	    use_alsa = false;
	}
	y2debug("Args: %1", WFM::Args());

	maplist(any e, WFM::Args(), ``{
	    if (is(e, string) && (e == ".oss"))
	    {
		use_alsa = false;
		y2milestone("Using OSS");
	    }
	});

	y2debug("use ALSA: %1", use_alsa);
    }

    /**
     * Probe one card with alsaconf call
     * @param chip chip name
     * @return non-empty string with card options when card is present
     */
    global define string ProbeOldChip (string chip) ``{

	string command = sformat ("/usr/sbin/alsaconf -p %1", chip);
	string name = db_modules["snd-"+chip,"description"]:chip;

	// yes/no popup text, %1 is chip name
	if (Crash::AskRun(command, sformat (_("It looks like probing the chip
%1
failed last time.

Probe the chip now?
"), name)))
	{
	    Crash::Run(command);
	    map out = (map) SCR::Execute(.target.bash_output, command);
	    Crash::Finish(command);
	    if (out["stderr"]:"" != "")
		y2warning ("alsaconf returns error: %1", out["stderr"]:"");

	    if (out["exit"]:1 == 0)
		return out["stdout"]:"";
	}
	return "";
    }

    /**
     * Detect old ISA cards (which hwinfo doesn't know) using alsaconf
     * @return success
     */
    global define boolean DetectOldCards() ``{

	list<string> chips = ["opl3sa2", "cs4236", "cs4232", "cs4231", "es18xx", "es1688", "sb16", "sb8"];

	string command = "/usr/sbin/alsaconf -P";
	map out = (map) SCR::Execute(.target.bash_output, command);
	if (out["exit"]:1 != 0)
	    return false;
	string out_str = deletechars (out["stdout"]:"", "\n");
	if (out_str != "")
	{
	    chips = splitstring (out_str, " ");
	}

	list probelist = [];
	foreach (string chip, chips, ``{
	    string name = db_modules["snd-"+chip,"description"]:"";
	    if (name != "")
		name = sformat ("%1 (%2)", name, chip);
	    else
		name = chip;

	    probelist = add (probelist, `item(`id(chip), name, true));
	});

	integer height = size (chips) + 12;
	if (height > 25)
	    height = 25;

	UI::OpenDialog (`HBox (`VSpacing(height),`HSpacing(1), `VBox(
	    `HSpacing (50),
	    `VSpacing(0.5),
	    // label
	    `Label (`id(`l), _("No card was found.
Attempt to detect the presence some old chips?

Warning: The probe procedure can take some time and
could make your system unstable.
")),
	    `VSpacing(),
	    `MultiSelectionBox(`id(`probelist),
		// selection box label
		_("&Select the Drivers to Probe"), probelist),
	    `HBox(
		// button label
		`PushButton(`id(`ok),`opt(`key_F10, `default),_("&Yes, Probe")),
		// button label
		`PushButton(`id(`cancel),`opt(`key_F9), Label::CancelButton())
	    ),
	    `VSpacing(0.5)
	    ),
	    `HSpacing(1)
	));

	symbol ret = (symbol) UI::UserInput();
	if (ret == `ok)
	{
	    chips = (list<string>) UI::QueryWidget(`id(`probelist),`SelectedItems);

	    UI::OpenDialog(`HBox(
		`VSpacing(7),
		`VBox(
		    `HSpacing(40),
		    // popup dialog header
		    `Label(_("Probing:")),
		    `HBox( `HWeight(1, `Label(`id(`probed), ""))),
		    // progress bar label
		    `ProgressBar(`id(`progress), _("Progress:"), size(chips),0),
		    `VSpacing(0.5),
		    `PushButton(
			`id(`abort), `opt(`key_F9), Label::AbortButton())
		))
	    );
	    boolean aborted = false;
	    foreach (string chip, chips, ``{

		if (aborted) return;
		aborted = (UI::PollInput() == `abort);

		UI::ChangeWidget (`id(`probed), `Value,
		    db_modules["snd-"+chip,"description"]:chip);

		string ret = ProbeOldChip (chip);
		if (ret != "")
		{
		    // parse the output and add new entry to detected_cards
		    chip = sformat ("snd-%1", chip);
		    list returned = splitstring (ret, "\n");
		    y2milestone ("probed with alsaconf: %1", returned);
		    // label (%1 is name of the chip)
		    string default_name = sformat (_("Card with %1 Chip"),
			db_modules[chip, "description"]:chip);
		    string model = returned[1]:default_name;
		    if (model == "")
			model = default_name;
		    ret = returned[0]:"";

		    map options = $[];
		    foreach (string o, splitstring (ret, " "), ``{
			list op = splitstring (o, "=");
			if (size (op) == 2)
			{
			    options = add (options, op[0]:"", op[1]:"");
			}
		    });

		    detected_cards = add (detected_cards, $[
			"model": model,
			"module": chip,
			"options": options,
			"alsaconf_detected": true ]
		    );
		}
		// advance the progress bar
		UI::ChangeWidget (`id(`progress), `Value,
		    (integer)UI::QueryWidget (`id(`progress), `Value) + 1);
	    });
	    UI::CloseDialog (); //progress window
	}
	UI::CloseDialog();
	return true;
    }

    /**
     * do hardware detection
     * @return boolean success/failure
     */
    global define boolean DetectHardware() ``{

	if (Mode::test())
	{
	    detected_cards = [$[
		    "bus"	: "PCI",
		    "class_id"	: 4,
		    "device"	: "SB Live! EMU10000",
		    "device_id"	:65538,
		    "drivers"	:
		    [
			$[
			    "active"	: false,
			    "modprobe"	: true,
			    "modules"	:
			    [
				[
				    "emu10k1",
				    ""
				]
			    ]
			]
		    ],
		    "old_unique_key" : "LaV9.FfCiMJnnUxC",
		    "resource"	:
		    $[
			"io":
			[
			    $[
				"active"    : true,
				"length"    : 0,
				"mode"	    : "rw",
				"start"	    : 49152
			    ]
			],
			"irq":
			[
			    $[
				"count"	    : 41833,
				"enabled"   : true,
				"irq":10
			    ]
			]
		    ],
		    "rev"	    : "7",
		    "slot_id"	    : 5,
		    "sub_class_id"  : 1,
		    "sub_device"    : "CT4832 SBLive! Value",
		    "sub_device_id" : 98343,
		    "sub_vendor"    : "Creative Labs",
		    "sub_vendor_id" : 69890,
		    "unique_key"    : "CvwD.FfCiMJnnUxC",
		    "vendor"	    : "Creative Labs",
		    "vendor_id"	    : 69890
		]];
	    return true;
	}

	if (Mode::config())
	{
	    detected_cards = [];
	}

	/* Confirmation: label text (detecting hardware: xxx) */
	if (!Confirm::Detection (_("Sound Cards")))
	{
	    detected_cards = [];
	    return true;
	}

	// do noop if cards were already detected
	if (size (detected_cards) > 0)
	{
	    return true;
	}
	detected_cards  = (list<map>) SCR::Read(.probe.sound);
	return true;
    }


    /**
     * searches for sound alias in /etc/modules.conf
     * @return void
     */
    global define void ReadModulesConf() ``{
	modules_conf = read_save_info();
    }


    /**
     * returns list of autodetected sound cards that haven't been already
     * configured
     * @param save_info list of already configured cards
     * @return list of unconfigured cards
     */
    global define list<map> getConfigurableCards(list<map> save_info) ``{

        list<map> snd = Sound::detected_cards;
        snd = filter_configured(save_info, snd);

        // create save_info entries
        if (size(snd) > 0)
        {
	    if (!use_alsa)
	    {
		snd = maplist(map card, snd, ``{
		    list<map<string,any> > options = card["options"]:[];
		    map opts = $[];
		    maplist(map op, options, ``{
			if (op["default"]:"" != "")
			{
			    opts = add(opts, op["name"]:"", op["default"]:"");
			}
		    });
		    string modname = "";
		    list drivers = card["drivers"]:[];
		    if (size(drivers) > 0)
		    {
			map driver = drivers[0]:$[];
			list m = driver["modules"]:[];
			if (size(m) > 0)
			{
			    modname = m[0,0]:"";
			    modname = String::FirstChunk(modname, ".");
			}
		    }
		    return $[
			"model"     : get_card_label(card),
			"module"    : modname,
			"unique_key": card["unique_key"]:"",
			"options"   : opts];
		});
	    }
	    else
	    {
		snd = maplist(map card, snd, ``{

		if (card["alsaconf_detected"]:false)
		    return card;
		// get all the apropriate information from the database
		map mod = get_module (card);
		if (mod == $[])
		{
		    return $[];
		}
		map opts = $[];
		maplist(string op, map data, mod["params"]:$[],	``{

		    if (data["default"]:"" != "")
		    {
			opts = add (opts, op , data["default"]:"");
		    }
		});
		map entry = $[
		    "model"     : get_card_label(card),
		    "module"    : mod["name"]:"",
		    "unique_key": card["unique_key"]:"",
		    "options"   : opts,
		    "bus"	: card["bus_hwcfg"]:"",
		    "bus_id"	: card["sysfs_bus_id"]:""
		];
		return entry;
		});
	    }
            // filter out modules with unspecified module name
	    // (sound card not supported by alsa / no module found)
	    snd = filter (map e, snd, ``(e["module"]:"" != ""));
        }
        return snd;
    }

    /**
     * update list of unconfigured cards
     * (necessary when deleting configured card)
     * @return void
     */
    global define void UpdateUnconfiguredCards() ``{
	unconfigured_cards = getConfigurableCards(modules_conf);
    }

    global define boolean PollAbort() ``{
	return UI::PollInput() == `abort;
    }

    /**
     * opens alsa sound cards database
     * @return void
     */
    global define void LoadDatabase (boolean use_ui) ``{
	map sound_db =$[];
	if (db_cards == nil || db_cards == $[])
	{
	    if (use_ui)
	    {
		string message = _("Loading the sound card database...");
		UI::OpenDialog(`opt(`decorated), `Label(message));
	    }
	    textdomain "sound_db";
	    y2debug("Reading card database");
	    sound_db	= (map)eval(SCR::Read (.target.yast2, "sndcards.ycp"));

	    db_cards	= sound_db["cards"]:$[];
	    db_modules	= sound_db["modules"]:$[];
	    db_indices	= sound_db["indices"]:$[];
	    db_module_index = sound_db["mod_idx"]:$[];

	    db_vendors	= sound_db ["vendors"]:$[];

	    textdomain "sound";
	    if (use_ui)
	    {
		UI::CloseDialog();
	    }
	}
    }

    /**
     * Read all sound settings from the SCR
     * @param interactive if user could be asked for actions
     *	(currently only for detecting with alsaconf)
     * @return boolean True on success
     */
    global define boolean Read (boolean interactive) ``{

	// sound Read dialog caption:
	string caption = _("Initializing Sound Configuration");

	integer steps = 5;

	Progress::New( caption, " ", steps, [
		// progress stage
		_("Read card database"),
		// progress stage
		_("Read previously saved configuration"),
		// progress stage
		_("Detect sound cards"),
		// progress stage
		_("Read sysconfig values"),
		// progress stage
		_("Read volume settings"),
	    ], [
		// progress step label
		_("Reading card database..."),
		// progress step label
		_("Reading previously saved configuration..."),
		// progress step label
		_("Detecting sound cards..."),
		// progress step label
		_("Reading sysconfig values..."),
		// progress step label
		_("Reading volume settings..."),
		// progress step label
		_("Finished")
	    ],
	    ""
	);
	if (Mode::test())
	{
	    DetectHardware();
	    modules_conf = [];
	    rc_vars = $[];
	    UpdateUnconfiguredCards();
	    return true;
	}
	if (interactive && PollAbort ()) return false;

	// load cards database
	Progress::NextStage ();
	LoadDatabase (interactive);

	// load data from /etc/modules.conf
	Progress::NextStage ();
	ReadModulesConf();

	if (interactive && PollAbort()) return false;

	// detect sound cards
	Progress::NextStage ();
	DetectHardware();

	if (interactive && PollAbort()) return false;

	// check old isa cards (bug25285)
	if (interactive && detected_cards == [] && modules_conf == [])
	    DetectOldCards();


	if (interactive && PollAbort()) return false;
	// read rc.config values
	Progress::NextStage ();
	rc_vars = read_rc_vars ();

	if (interactive && PollAbort()) return false;

	// load volume settings
	Progress::NextStage ();
	volume_settings = get_vol_settings();

	if (interactive && PollAbort()) return false;

	// create list of unconfigured cards
	UpdateUnconfiguredCards();

	// increase the progress to "finish"
	Progress::NextStage ();
	return true;
    }

    /**
     * Just Set module data
     * @param settings Sound configuration settings
     * @return: void
     */
    global define void Set(map settings)  ``{
	modules_conf		= settings["modules_conf"]:[];
	rc_vars			= settings["rc_vars"]:$[];
	volume_settings		= settings["volume_settings"]:[];
	configure_detected	= settings["configure_detected"]:false;
    }

    /**
     * Get all sound settings from the first parameter
     * (For autoinstallation use.)
     * @param settings settings to import
     * @return boolean True on success
     */
    global define boolean Import(map settings) ``{
	// initialize these unneeded values
	detected_cards = [];
	unconfigured_cards = [];

	Set(settings);

	return true;
    }

    /**
     * Dump the sound settings to a single map. self explaining
     * (For use by autoinstallation.)
     * @return map Dumped settings (later acceptable by Import())
     */
    global define map Export() ``{
	return $[
	    "modules_conf"	: modules_conf,
	    "rc_vars"		: rc_vars,
	    "volume_settings"	: volume_settings,
	    "configure_detected": configure_detected
	];
    }

    /**
     * Get list of all kernel modules which are requied by the configured cards
     * @return list Unique list of modules
     */
    global define list<string> RequiredKernelModules()
    {
	// list of all unique modules
	list<string> mods = [];

	if (modules_conf != nil && size(modules_conf) > 0)
	{
	    foreach(map m, modules_conf, {
		    string modname = m["module"]:"";

		    if (m != nil && size(m) > 0)
		    {
			mods = add(mods, modname);
		    }
		}
	    );
	}

	// remove duplicates
	mods = toset(mods);

	y2milestone("Required kernel modules: %1", mods);

	return mods;
    }
    /**
     * Update the SCR according to sound settings
     * @return boolean True on success
     */
    global define boolean Write () ``{

	if (Mode::test() == false)
	{
	    map settings = Export();
	    WFM::CallFunction ("sound_write", [ settings ]);
	}
	return true;
    }

    /**
     * returns html formated configuration summary
     * @return string Summary string
     */
    global define string Summary() ``{

	list keys = maplist (map card, modules_conf, ``(
	    card["unique_key"]:"..."));

	list<string> retlist = maplist (map card, modules_conf, ``(
	    Summary::Device (card["model"]:"Sound card",
		// summary text: '(sound card is) Configured as snd-card-1'
		sformat(_("Configured as %1."), card["alias"]:"snd-card-0"))
	));

	foreach(map card, detected_cards, ``{
	    if (!contains(keys, card["unique_key"]:"..."))
	    {
		retlist = add (retlist, Summary::Device (
		    get_card_label(card), Summary::NotConfigured()));
	    }
	});
	return Summary::DevicesList (retlist);
    }

    /**
     * this function converts options for modules from 'modules_conf'
     * data structure to another - it is needed for writing options to
     * /etc/modules conf.
     * eg. we have these configured cards:
     * [
     *	$["model": "sblive", "module":"snd-emu10k1",
     *	  "options" :$["opt1": "a", "opt2": "b"]],
     *  $["model": "sblive", "module":"snd-emu10k1",
     *	  "options": $["opt2": "c"]],
     *  $["model": "other", "module":"mod2",
     *	  "options": $["opt1": "a", "opt2": "b"]]
     * ]
     * CollectOptions ("snd-emu10k1") returns $["opt1":"a,", "opt2":"b,c"]
     * @param modname module name
     * @return map Map with collected options
     *
     */
    global define map CollectOptions (string modname) ``{

	// first filter out entries with other module
	list mod_entries = filter (map e, modules_conf, ``(
	    e["module"]:"off" == modname));
	// create list of options (list of maps)
	list<map<string,string> > mod_opts = maplist (map e, (list<map>) mod_entries, ``(e["options"]:$[]));
	list<string> opts = [];

	foreach (map<string,string> card_opts, mod_opts, ``{
	    foreach(string name, string val, card_opts, ``{
	        opts = add (opts, name);
	    });
	});
	opts = toset (opts);
	opts = filter (string e, opts, ``(e != "snd_id"));// not neccessary?

	map res = $[];
	// get the default module parameters from database
	map params      = get_module_params (modname);
	// for each option collect their values
	foreach (string opname, opts, ``{
	    boolean coma = false;
	    string value = "";
	    foreach (map card_opts, mod_opts, ``{
	        if (coma)
	        {
	            value = value + ",";
	        }
		if (card_opts[opname]:"" != params[opname,"default"]:"")
		{
		    value = value + card_opts[opname]:"";
		}
		if (card_opts[opname]:"" != "")
		    coma = true;
	    });
	    if (value != "")
	    {
		res = add (res, opname, value);
	    }
	});
	return res;
    }

    /**
     * creates list of command that will be used for sound system start
     * (emulates 'rcalsasound start' somehow)
     * @return list of shell commands
     */
    global define list CreateModprobeCommands() ``{
	list outlist = [];
	// create distinct list of all modules
        list mods = toset (maplist (map e, modules_conf,``(e["module"]:"off")));
	maplist (string modname, (list<string>) mods, ``{
	    map opts = CollectOptions(modname);
	    string optstr = "";
	    maplist(string k, string v, (map<string,string>) opts, ``{
		optstr = optstr+ sformat(" %1=%2", k, v);
	    });
	    // we need to tell 'modprobe' not to look into modules.conf now,
	    // because it may contain messed options for the module %1 that
	    // would break the module loading. (modprobe will merge options
	    // specified in param %2 with those specified in modules.conf)
	    string modprobe = sformat("/sbin/modprobe -C /dev/null %1 %2",
		modname, optstr);
	    outlist = add(outlist, modprobe);
	});

	return outlist;

    }

    /**
     * reset settings.
     * used at installation time when user invokes 'reset to original proposal'
     */
    global define void ForceReset() ``{
	modules_conf = [];
	DetectHardware();
	UpdateUnconfiguredCards();
	proposal_created = false;
    }

    /**
     * create a proposal
     * @return boolean success/failure
     */
    global define boolean Propose() ``{
	if (proposal_created)
	{
	    return true;
	}
	// to enable initialization, run ForceReset for first time,
	// even if "force_reset" parameter was not set
	// TODO: but if force_reset is set, ForceReset is run twice!
	ForceReset ();

	// fix for nm256 cards: see bug #10384: leave this card unconfigured.
	// loading of this module fails under some notebooks, nm256 module
	// also makes some problems on other notebooks: there are detected
	// two cards on some machines, althrough there is a sinlge card.
	// two cards are detected: one that use opl3sa module and second one
	// that uses nm256.
	// let's solve this problem by filtering the nm256 module out.
	unconfigured_cards = filter (map card, unconfigured_cards, ``(
	    card["module"]:"" != "snd-nm256"));

	modules_conf = recalc_save_entries(unconfigured_cards);

	// card of Thinkpad 600E must be configured manually
	modules_conf = filter (map card, modules_conf, ``{
	    boolean ok = true;
	    foreach (map c, detected_cards, ``{
		if (c["unique_key"]:"" == card["unique_key"]:"" &&
		    c["sub_device_id"]:0 == 69648 &&
		    c["sub_vendor_id"]:0 == 69652 &&
		    card["module"]:"" != "snd-cs4236")
		{
		    ok = false;
		}
	    });
	    return ok;
	});

	rc_vars = $[
	    "LOAD_ALSA_SEQ": "yes"
	];
	proposal_created = true;
	return true;
    }

    /**
     * copy settings to backup variables
     */
    global define void StoreSettings() ``{
	modules_conf_b = modules_conf;
	rc_vars_b = rc_vars;
	volume_settings_b = volume_settings;
    }

    /**
     * restore settings from backup variables
     */
    global define void RestoreSettings() ``{
	if (modules_conf_b == nil)
	{
	    y2error("restoring sound setting without storing them before. bailing out.");
	}
	modules_conf = modules_conf_b;
	rc_vars = rc_vars_b;
	volume_settings = volume_settings_b;
    }

    /**
     * Status of configuration
     * @return boolean true if configuration was changed
     */
    global define boolean Changed() ``{
	if (modules_conf != modules_conf_b ||
	    rc_vars != rc_vars_b)
	{
	    return true;
	}
	return false;
    }

    /**
     * returns list of configured/proposed sound cards.
     * @return list of maps: [$["name": "ASDASD", "card_no": 0]...]
     */
    global define list GetSoundCardList() ``{
	if (installation || Mode::autoinst())
	{
	    integer pos = -1;
	    return maplist(map card, modules_conf, ``{
		    pos = pos + 1;
		    return $[
			"name": card["model"]:_("Sound card"),
			"card_no": pos
		    ];
	    });
	}
	else
	{
	    path cards_path	= .audio.alsa.cards;
	    list<string> cards_numbers	= (list<string>) SCR::Dir(cards_path);
	    list cards		= [];

	    if (cards_numbers != nil)
	    {
		cards = maplist(string card_no, cards_numbers, ``{
		    path curcard_path = add(cards_path, sformat("%1", card_no));
		    return $[ "card_no"	: tointeger(card_no),
                              "name"	: SCR::Read(curcard_path + .name) ];
		});
	    }
	    y2milestone ("List of the sound cards: %1", cards);
	    return cards;
	}
    }

    /**
     * stores unique keys. this function is not part of sound_write module
     * because it should not be called during autoinstallation
     *
     */
    global define void StoreUniqueKeys() ``{
	UpdateUnconfiguredCards();
	list conf = maplist (map e, modules_conf, ``(e["unique_key"]:""));
	list unconf = maplist(map e, unconfigured_cards,``(e["unique_key"]:""));
	SaveUniqueKeys(conf, unconf);
    }

    /**
     * Get joystick settings from sound database
     * @param modname name of sound module
     * @return map map with options
     */
    global define map GetJoystickSettings(string modname) ``{
	return db_modules [modname, "joystick"]:$[];
    }

    /**
     * store mixer settings
     */
    global define void StoreMixer() ``{
	if (size(SCR::Dir(.audio.alsa.cards)) == 0)
	{
	    return;
	}
	volume_settings = [];
	path p = nil;
	list<string> cards = (list<string>) SCR::Dir(.audio.alsa.cards);
	maplist(string c, cards, ``{
	    p = topath(sformat(".audio.alsa.cards.%1.channels", c));
	    list<string> channels = (list<string>) SCR::Dir(p);
	    maplist(string ch, channels, ``{
		p = topath (sformat(
		    ".audio.alsa.cards.%1.channels.%2.mute", c, ch));
		if (!(boolean)SCR::Read(p))
		{
		    p = topath (sformat(
			".audio.alsa.cards.%1.channels.%2.volume", c, ch));
		    volume_settings[tointeger(c)] = [ch, SCR::Read(p), "false"];
		}
	    });
	});
    }

    /**
     * sets the channel volume to value [0..100]
     * @param c_id card #
     * @param channel channel name
     * @param value volume of channel [0..100]
     * @return success
     */
    global define boolean SetVolume (integer c_id, string channel,
				     integer value) ``{
	// two cases: we have sound system already running:
	if (!Mode::config() && size(SCR::Dir(.audio.alsa.cards)) > c_id)
	{
            string p = sformat(".audio.alsa.cards.%1.channels.%2.volume", c_id, channel);
	    string p2 = sformat(".audio.alsa.cards.%1.channels.%2.mute", c_id, channel);
	    SCR::Write(topath(p2), false);
            return SCR::Write(topath(p), value);
	}
	else
	{
	    // alsa is not running. probably autoinstallation or someone wants
	    // to change proposed volume
	    // store volume to volume_settings datastricure in autoinstallation
            volume_settings [c_id] = [ channel, value ];
            return true;
	}
    }

    /**
     * initialises volume after adding a new card.
     * unmutes and sets volume for some channels
     * @param c_id card id.
     * @param modname name of sound module
     */
    global define void  InitMixer(integer c_id, string modname) ``{
        map devs = db_modules[modname, "mixer"]:$["Master":70, "PCM":70, "CD":70, "Synth": 70];

	// now let's merge settings from volume_settings list
	if (is (volume_settings[c_id]:nil, list))
	{
	    y2debug("volume_settings[c_id]:nil: %1", volume_settings[c_id]:nil);
	    foreach (list channel, volume_settings[c_id]:[], ``{
		y2debug("devs: %1", devs);
		y2debug("channel: %1", channel);
		devs = add (devs, channel[0]:"Master", channel[1]:70);
	    });
	}
        list devs2 = [];

        foreach (string dev, integer vol, (map<string,integer>)devs, ``{
	    SetVolume (c_id, dev, vol);
	    devs2 = add (devs2, dev);
	});

        unmute(devs2, c_id);
    }

    /**
     * Test whether sound card supports joystick
     * @param c_id id of sound card
     * @return boolean True if sound card c_id supports joystick
     */
    global define boolean HasJoystick(integer c_id) ``{
	if (c_id == nil)
	{
	    return false;
	}
	if (use_alsa == true)
	{
	    map entry		= modules_conf[c_id]:$[];
	    string modname	= entry["module"]:"";

	    return ( db_modules[modname, "joystick"]:$[] != $[] );
	}
	else
	{
	    return false;
	}
    }

    /**
     * Return list of configured/proposed sound cards which support joystick
     * @return list list of maps: [$["card_no":0, "name":"Sound Blaster Live!"]]
     */
    global define list GetSoundCardListWithJoy() ``{

	// get list of installed sound cards
	list cards = GetSoundCardList();

	// cards which support joysticks
	list filtered = [];

	// remove cards without joystick support
	foreach(map card, (list<map<string,any> >)cards, ``{

	    integer cid	= card["card_no"]:-2;
	    if (cid != -2 && HasJoystick(cid) == true)
	    {
		filtered = add (filtered, card);
	    }
	});

	return filtered;
    }

/* EOF */
}
