diff --git a/additional/python/run_multiple_commands.py b/additional/python/run_multiple_commands.py index cbbbe36..e0ff1dc 100644 --- a/additional/python/run_multiple_commands.py +++ b/additional/python/run_multiple_commands.py @@ -43,6 +43,6 @@ env[key] = value print(f"-- COMMAND: '{command}' ".ljust(96, "-")) - jessentials.run_command(command=command, environment=env, user=user_id) + jessentials.run_command(command=f"bash -c \"{command}\"", environment=env, user=user_id) print(" FINISHED ".center(96, "-")) \ No newline at end of file diff --git a/lib/content/basic_entries.dart b/lib/content/basic_entries.dart index fc369a5..04f196c 100644 --- a/lib/content/basic_entries.dart +++ b/lib/content/basic_entries.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:linux_assistant/enums/desktops.dart'; import 'package:linux_assistant/enums/distros.dart'; import 'package:linux_assistant/enums/softwareManagers.dart'; +import 'package:linux_assistant/layouts/grub_config/grub_config.dart'; import 'package:linux_assistant/layouts/mint_y.dart'; import 'package:linux_assistant/main.dart'; import 'package:linux_assistant/models/action_entry.dart'; @@ -226,5 +227,18 @@ List getBasicEntries(BuildContext context) { Linux.disableCdromSourceInDebian(context); }, ), + ActionEntry( + name: AppLocalizations.of(context)!.grubConfiguration, + description: AppLocalizations.of(context)!.grubConfigurationDescription, + iconWidget: Icon(Icons.dns, size: 48, color: MintY.currentColor), + handlerFunction: (VoidCallback callback, BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => GrubConfigPage(), + ), + ); + }, + ), ]; } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index f0d14f7..e54b05c 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -350,6 +350,13 @@ "openSoftwareCenterDescription": "Öffne das Software-Center, um weitere Anwendungen/Apps zu installieren.", "disableCdromSource": "Deaktiviere CD-ROM-Quelle", "disableCdromSourceDescription": "Ein Relikt der Debian-Installation. Deaktiviere die CDROM-Quelle, um Fehlermeldungen zu vermeiden.", + "grubConfiguration": "Grub-Konfiguration", + "grubConfigurationDescription": "Konfiguriere den Grub-Bootloader. Dieser Schritt ist nur für fortgeschrittene Benutzer empfohlen.", + "grubVisible": "Bootmenü sichtbar", + "enableBigFont": "Große Schrift aktivieren", + "grubCountdown": "Zeit bis zum automatischen Start (in Sekunden)", + "startLastBootedEntry": "Starte den zuletzt gebooteten Eintrag", + "save": "Speichern", "@helloWorld": { "placeholders": {}, "description": "", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 49add17..3dda6e6 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -350,6 +350,13 @@ "openSoftwareCenterDescription": "Open the Software Center to install additional applications/apps.", "disableCdromSource": "Disable CD-ROM source", "disableCdromSourceDescription": "A relic of the Debian installation. Disable the CDROM source to avoid error messages.", + "grubConfiguration": "Grub configuration", + "grubConfigurationDescription": "Configure the Grub bootloader. This step is recommended for advanced users only.", + "grubVisible": "Bootmenu visible", + "enableBigFont": "Enable large font", + "grubCountdown": "Time until automatic start (in seconds)", + "startLastBootedEntry": "Start the last booted entry", + "save": "Save", "@helloWorld": { "placeholders": {}, "description": "The conventional newborn programmer greeting", diff --git a/lib/layouts/grub_config/grub_config.dart b/lib/layouts/grub_config/grub_config.dart new file mode 100644 index 0000000..ff92d0c --- /dev/null +++ b/lib/layouts/grub_config/grub_config.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:linux_assistant/layouts/mint_y.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:linux_assistant/services/linux.dart'; +import 'package:linux_assistant/services/main_search_loader.dart'; + +class GrubConfigPage extends StatelessWidget { + GrubConfigPage({super.key}); + + final _GrubSettings _grubSettings = _GrubSettings(Linux.getGrubSettings()); + + @override + Widget build(BuildContext context) { + return MintYPage( + title: AppLocalizations.of(context)!.grubConfiguration, + contentElements: [ + MintYCheckboxSetting( + text: AppLocalizations.of(context)!.grubVisible, + value: _grubSettings.grubVisible, + onChanged: (value) { + _grubSettings.grubVisible = value; + }, + ), + MintYCheckboxSetting( + text: AppLocalizations.of(context)!.enableBigFont, + value: _grubSettings.enableBigFont, + onChanged: (value) { + _grubSettings.enableBigFont = value; + }, + ), + MintYTextSetting( + text: AppLocalizations.of(context)!.grubCountdown, + textAlign: TextAlign.right, + value: _grubSettings.timeout.toString(), + onChanged: (value) { + int? parsed = int.tryParse(value); + if (parsed == null) { + return; + } + _grubSettings.timeout = parsed; + }, + ), + MintYCheckboxSetting( + text: AppLocalizations.of(context)!.startLastBootedEntry, + value: _grubSettings.startLastBootedOne, + onChanged: (value) { + _grubSettings.startLastBootedOne = value; + }, + ), + ], + bottom: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MintYButton( + text: + Text(AppLocalizations.of(context)!.back, style: MintY.heading4), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => MainSearchLoader(), + ), + ); + }, + ), + const SizedBox(width: 10), + MintYButton( + text: Text(AppLocalizations.of(context)!.save, + style: MintY.heading4White), + color: MintY.currentColor, + onPressed: () { + Linux.ensureGrubSettings( + context, + _grubSettings.grubVisible, + _grubSettings.enableBigFont, + _grubSettings.timeout, + _grubSettings.startLastBootedOne); + }, + ), + ], + ), + ); + } +} + +class _GrubSettings { + bool grubVisible = false; + bool enableBigFont = false; + int timeout = 100; + bool startLastBootedOne = false; + + _GrubSettings(Map settingsMap) { + grubVisible = settingsMap["grubVisible"]; + enableBigFont = settingsMap["enableBigFont"]; + timeout = settingsMap["timeout"]; + startLastBootedOne = settingsMap["startLastBootedOne"]; + } +} diff --git a/lib/layouts/mint_y.dart b/lib/layouts/mint_y.dart index 4c4ed5c..59b298d 100644 --- a/lib/layouts/mint_y.dart +++ b/lib/layouts/mint_y.dart @@ -908,3 +908,112 @@ class MintYLoadingPage extends StatelessWidget { ); } } + +class MintYCheckboxSetting extends StatefulWidget { + late String text; + late bool value; + + /// Callback function that takes as parameter the new value of the setting + late Function(bool) onChanged; + + MintYCheckboxSetting( + {super.key, + required this.text, + required this.value, + required this.onChanged}); + + @override + State createState() => _MintYCheckboxSettingState(); +} + +class _MintYCheckboxSettingState extends State { + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 100.0, vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.text, + style: Theme.of(context).textTheme.headlineMedium, + ), + Checkbox( + value: widget.value, + onChanged: (bool? newValue) { + setState(() { + widget.value = newValue!; + widget.onChanged.call(newValue); + }); + }, + activeColor: MintY.currentColor, + ), + ], + ), + ); + } +} + +class MintYTextSetting extends StatefulWidget { + late String text; + late String value; + late TextAlign textAlign; + late Function(String) onChanged; + + MintYTextSetting( + {super.key, + required this.text, + required this.value, + required this.textAlign, + required this.onChanged}); + + @override + State createState() => _MintYTextSettingState(); +} + +class _MintYTextSettingState extends State { + late TextEditingController controller; + + @override + void initState() { + super.initState(); + controller = TextEditingController(text: widget.value); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 100.0, vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.text, + style: Theme.of(context).textTheme.headlineMedium, + ), + SizedBox( + width: 200, + child: TextField( + controller: controller, + onChanged: (String newValue) { + widget.onChanged.call(newValue); + }, + textAlign: widget.textAlign, + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: BorderSide(color: MintY.currentColor), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: MintY.currentColor, + width: 2, + style: BorderStyle.solid), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/services/linux.dart b/lib/services/linux.dart index a38ba36..aeea255 100644 --- a/lib/services/linux.dart +++ b/lib/services/linux.dart @@ -2647,7 +2647,7 @@ class Linux { "git clone https://aur.archlinux.org/snapd.git /tmp/snapd; cd /tmp/snapd; makepkg -si --noconfirm;"; commandQueue.add(LinuxCommand( userId: currentenvironment.currentUserId, - command: "bash -c '$bashCode'", + command: bashCode, environment: {"PATH": getPATH(), "HOME": getHomeDirectory()}, )); commandQueue.add(LinuxCommand( @@ -2776,4 +2776,114 @@ class Linux { )); } } + + static Map getGrubSettings() { + String grubFileContent = File("/etc/default/grub").readAsStringSync(); + List lines = grubFileContent.split("\n"); + Map settingsFileMap = {}; + for (String line in lines) { + if (line.contains("=") && !line.trim().startsWith("#")) { + List parts = line.split("="); + settingsFileMap[parts[0]] = parts[1]; + } + } + + Map returnValue = {}; + returnValue["grubVisible"] = + settingsFileMap["GRUB_TIMEOUT_STYLE"] == "menu" || + settingsFileMap["GRUB_TIMEOUT_STYLE"] == null; + returnValue["enableBigFont"] = settingsFileMap["GRUB_GFXMODE"] == "640x480"; + if (settingsFileMap["GRUB_TIMEOUT"] == null) { + settingsFileMap["GRUB_TIMEOUT"] = "0"; + } + returnValue["timeout"] = + int.tryParse(settingsFileMap["GRUB_TIMEOUT"]!) ?? 0; + returnValue["startLastBootedOne"] = + settingsFileMap["GRUB_DEFAULT"] == "saved"; + + return returnValue; + } + + static void ensureGrubSettings(context, bool grubVisible, bool enableBigFont, + int timeout, bool startLastBootedOne) { + String grub_timeout_style = grubVisible ? "menu" : "hidden"; + String grub_timeout = timeout.toString(); + String grub_default = startLastBootedOne ? "saved" : "0"; + String grub_save_default = startLastBootedOne ? "true" : "false"; + String grub_gfxmode = enableBigFont ? "640x480" : ""; + + if (grub_timeout_style == "menu" && timeout < 1) { + grub_timeout = "1"; + } + + if (grub_timeout_style == "hidden" && timeout < 0) { + grub_timeout = "0"; + } + + ensureOptionInConfigFile( + "GRUB_TIMEOUT_STYLE", grub_timeout_style, "/etc/default/grub"); + ensureOptionInConfigFile("GRUB_TIMEOUT", grub_timeout, "/etc/default/grub"); + ensureOptionInConfigFile("GRUB_DEFAULT", grub_default, "/etc/default/grub"); + ensureOptionInConfigFile( + "GRUB_SAVEDEFAULT", grub_save_default, "/etc/default/grub"); + ensureOptionInConfigFile("GRUB_GFXMODE", grub_gfxmode, "/etc/default/grub"); + + if (currentenvironment.distribution != DISTROS.FEDORA) { + commandQueue.add(LinuxCommand( + userId: 0, + command: "/usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg", + )); + } else { + commandQueue.add(LinuxCommand( + userId: 0, + command: "/usr/sbin/grub2-mkconfig -o /boot/grub2/grub.cfg", + )); + } + + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => RunCommandQueue( + title: AppLocalizations.of(context)!.grubConfiguration, + route: MainSearchLoader()), + )); + } + + /// Used for config files like /etc/default/grub. + /// + /// Only adds the commands to the command queue to ensure that the key is set to the value. + /// If the value is empty, the key will be removed from the file. + static void ensureOptionInConfigFile(String key, String value, String path) { + /// Remove the key from the file if the value is empty + if (value.isEmpty) { + commandQueue.add(LinuxCommand( + userId: 0, + command: "sed -i '/$key/d' $path", + )); + return; + } + + bool settingFound = false; + // Check if the key is already in the file and is not commented out + String fileContent = File(path).readAsStringSync(); + List lines = fileContent.split("\n"); + for (String line in lines) { + if (line.contains("$key=") && !line.trim().startsWith("#")) { + settingFound = true; + break; + } + } + + if (!settingFound) { + // Add the key to the file + commandQueue.add(LinuxCommand( + userId: 0, + command: "echo '$key=$value' >> $path", + )); + } else { + // Replace the key in the file + commandQueue.add(LinuxCommand( + userId: 0, + command: "sed -i 's/$key=.*/$key=$value/' $path", + )); + } + } }