Skip to content

Commit e5df963

Browse files
committed
Add Simple Grub Configuration #176
1 parent d0dd9e3 commit e5df963

7 files changed

Lines changed: 346 additions & 2 deletions

File tree

additional/python/run_multiple_commands.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@
4343
env[key] = value
4444

4545
print(f"-- COMMAND: '{command}' ".ljust(96, "-"))
46-
jessentials.run_command(command=command, environment=env, user=user_id)
46+
jessentials.run_command(command=f"bash -c \"{command}\"", environment=env, user=user_id)
4747

4848
print(" FINISHED ".center(96, "-"))

lib/content/basic_entries.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
22
import 'package:linux_assistant/enums/desktops.dart';
33
import 'package:linux_assistant/enums/distros.dart';
44
import 'package:linux_assistant/enums/softwareManagers.dart';
5+
import 'package:linux_assistant/layouts/grub_config/grub_config.dart';
56
import 'package:linux_assistant/layouts/mint_y.dart';
67
import 'package:linux_assistant/main.dart';
78
import 'package:linux_assistant/models/action_entry.dart';
@@ -226,5 +227,18 @@ List<ActionEntry> getBasicEntries(BuildContext context) {
226227
Linux.disableCdromSourceInDebian(context);
227228
},
228229
),
230+
ActionEntry(
231+
name: AppLocalizations.of(context)!.grubConfiguration,
232+
description: AppLocalizations.of(context)!.grubConfigurationDescription,
233+
iconWidget: Icon(Icons.dns, size: 48, color: MintY.currentColor),
234+
handlerFunction: (VoidCallback callback, BuildContext context) {
235+
Navigator.push(
236+
context,
237+
MaterialPageRoute(
238+
builder: (context) => GrubConfigPage(),
239+
),
240+
);
241+
},
242+
),
229243
];
230244
}

lib/l10n/app_de.arb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,13 @@
350350
"openSoftwareCenterDescription": "Öffne das Software-Center, um weitere Anwendungen/Apps zu installieren.",
351351
"disableCdromSource": "Deaktiviere CD-ROM-Quelle",
352352
"disableCdromSourceDescription": "Ein Relikt der Debian-Installation. Deaktiviere die CDROM-Quelle, um Fehlermeldungen zu vermeiden.",
353+
"grubConfiguration": "Grub-Konfiguration",
354+
"grubConfigurationDescription": "Konfiguriere den Grub-Bootloader. Dieser Schritt ist nur für fortgeschrittene Benutzer empfohlen.",
355+
"grubVisible": "Bootmenü sichtbar",
356+
"enableBigFont": "Große Schrift aktivieren",
357+
"grubCountdown": "Zeit bis zum automatischen Start (in Sekunden)",
358+
"startLastBootedEntry": "Starte den zuletzt gebooteten Eintrag",
359+
"save": "Speichern",
353360
"@helloWorld": {
354361
"placeholders": {},
355362
"description": "",

lib/l10n/app_en.arb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,13 @@
350350
"openSoftwareCenterDescription": "Open the Software Center to install additional applications/apps.",
351351
"disableCdromSource": "Disable CD-ROM source",
352352
"disableCdromSourceDescription": "A relic of the Debian installation. Disable the CDROM source to avoid error messages.",
353+
"grubConfiguration": "Grub configuration",
354+
"grubConfigurationDescription": "Configure the Grub bootloader. This step is recommended for advanced users only.",
355+
"grubVisible": "Bootmenu visible",
356+
"enableBigFont": "Enable large font",
357+
"grubCountdown": "Time until automatic start (in seconds)",
358+
"startLastBootedEntry": "Start the last booted entry",
359+
"save": "Save",
353360
"@helloWorld": {
354361
"placeholders": {},
355362
"description": "The conventional newborn programmer greeting",
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:linux_assistant/layouts/mint_y.dart';
3+
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
4+
import 'package:linux_assistant/services/linux.dart';
5+
import 'package:linux_assistant/services/main_search_loader.dart';
6+
7+
class GrubConfigPage extends StatelessWidget {
8+
GrubConfigPage({super.key});
9+
10+
final _GrubSettings _grubSettings = _GrubSettings(Linux.getGrubSettings());
11+
12+
@override
13+
Widget build(BuildContext context) {
14+
return MintYPage(
15+
title: AppLocalizations.of(context)!.grubConfiguration,
16+
contentElements: [
17+
MintYCheckboxSetting(
18+
text: AppLocalizations.of(context)!.grubVisible,
19+
value: _grubSettings.grubVisible,
20+
onChanged: (value) {
21+
_grubSettings.grubVisible = value;
22+
},
23+
),
24+
MintYCheckboxSetting(
25+
text: AppLocalizations.of(context)!.enableBigFont,
26+
value: _grubSettings.enableBigFont,
27+
onChanged: (value) {
28+
_grubSettings.enableBigFont = value;
29+
},
30+
),
31+
MintYTextSetting(
32+
text: AppLocalizations.of(context)!.grubCountdown,
33+
textAlign: TextAlign.right,
34+
value: _grubSettings.timeout.toString(),
35+
onChanged: (value) {
36+
int? parsed = int.tryParse(value);
37+
if (parsed == null) {
38+
return;
39+
}
40+
_grubSettings.timeout = parsed;
41+
},
42+
),
43+
MintYCheckboxSetting(
44+
text: AppLocalizations.of(context)!.startLastBootedEntry,
45+
value: _grubSettings.startLastBootedOne,
46+
onChanged: (value) {
47+
_grubSettings.startLastBootedOne = value;
48+
},
49+
),
50+
],
51+
bottom: Row(
52+
mainAxisAlignment: MainAxisAlignment.center,
53+
children: [
54+
MintYButton(
55+
text:
56+
Text(AppLocalizations.of(context)!.back, style: MintY.heading4),
57+
onPressed: () {
58+
Navigator.of(context).push(
59+
MaterialPageRoute(
60+
builder: (context) => MainSearchLoader(),
61+
),
62+
);
63+
},
64+
),
65+
const SizedBox(width: 10),
66+
MintYButton(
67+
text: Text(AppLocalizations.of(context)!.save,
68+
style: MintY.heading4White),
69+
color: MintY.currentColor,
70+
onPressed: () {
71+
Linux.ensureGrubSettings(
72+
context,
73+
_grubSettings.grubVisible,
74+
_grubSettings.enableBigFont,
75+
_grubSettings.timeout,
76+
_grubSettings.startLastBootedOne);
77+
},
78+
),
79+
],
80+
),
81+
);
82+
}
83+
}
84+
85+
class _GrubSettings {
86+
bool grubVisible = false;
87+
bool enableBigFont = false;
88+
int timeout = 100;
89+
bool startLastBootedOne = false;
90+
91+
_GrubSettings(Map<String, dynamic> settingsMap) {
92+
grubVisible = settingsMap["grubVisible"];
93+
enableBigFont = settingsMap["enableBigFont"];
94+
timeout = settingsMap["timeout"];
95+
startLastBootedOne = settingsMap["startLastBootedOne"];
96+
}
97+
}

lib/layouts/mint_y.dart

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,3 +908,112 @@ class MintYLoadingPage extends StatelessWidget {
908908
);
909909
}
910910
}
911+
912+
class MintYCheckboxSetting extends StatefulWidget {
913+
late String text;
914+
late bool value;
915+
916+
/// Callback function that takes as parameter the new value of the setting
917+
late Function(bool) onChanged;
918+
919+
MintYCheckboxSetting(
920+
{super.key,
921+
required this.text,
922+
required this.value,
923+
required this.onChanged});
924+
925+
@override
926+
State<MintYCheckboxSetting> createState() => _MintYCheckboxSettingState();
927+
}
928+
929+
class _MintYCheckboxSettingState extends State<MintYCheckboxSetting> {
930+
@override
931+
Widget build(BuildContext context) {
932+
return Padding(
933+
padding: const EdgeInsets.symmetric(horizontal: 100.0, vertical: 8.0),
934+
child: Row(
935+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
936+
children: [
937+
Text(
938+
widget.text,
939+
style: Theme.of(context).textTheme.headlineMedium,
940+
),
941+
Checkbox(
942+
value: widget.value,
943+
onChanged: (bool? newValue) {
944+
setState(() {
945+
widget.value = newValue!;
946+
widget.onChanged.call(newValue);
947+
});
948+
},
949+
activeColor: MintY.currentColor,
950+
),
951+
],
952+
),
953+
);
954+
}
955+
}
956+
957+
class MintYTextSetting extends StatefulWidget {
958+
late String text;
959+
late String value;
960+
late TextAlign textAlign;
961+
late Function(String) onChanged;
962+
963+
MintYTextSetting(
964+
{super.key,
965+
required this.text,
966+
required this.value,
967+
required this.textAlign,
968+
required this.onChanged});
969+
970+
@override
971+
State<MintYTextSetting> createState() => _MintYTextSettingState();
972+
}
973+
974+
class _MintYTextSettingState extends State<MintYTextSetting> {
975+
late TextEditingController controller;
976+
977+
@override
978+
void initState() {
979+
super.initState();
980+
controller = TextEditingController(text: widget.value);
981+
}
982+
983+
@override
984+
Widget build(BuildContext context) {
985+
return Padding(
986+
padding: const EdgeInsets.symmetric(horizontal: 100.0, vertical: 8.0),
987+
child: Row(
988+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
989+
children: [
990+
Text(
991+
widget.text,
992+
style: Theme.of(context).textTheme.headlineMedium,
993+
),
994+
SizedBox(
995+
width: 200,
996+
child: TextField(
997+
controller: controller,
998+
onChanged: (String newValue) {
999+
widget.onChanged.call(newValue);
1000+
},
1001+
textAlign: widget.textAlign,
1002+
decoration: InputDecoration(
1003+
border: OutlineInputBorder(
1004+
borderSide: BorderSide(color: MintY.currentColor),
1005+
),
1006+
focusedBorder: OutlineInputBorder(
1007+
borderSide: BorderSide(
1008+
color: MintY.currentColor,
1009+
width: 2,
1010+
style: BorderStyle.solid),
1011+
),
1012+
),
1013+
),
1014+
),
1015+
],
1016+
),
1017+
);
1018+
}
1019+
}

lib/services/linux.dart

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2647,7 +2647,7 @@ class Linux {
26472647
"git clone https://aur.archlinux.org/snapd.git /tmp/snapd; cd /tmp/snapd; makepkg -si --noconfirm;";
26482648
commandQueue.add(LinuxCommand(
26492649
userId: currentenvironment.currentUserId,
2650-
command: "bash -c '$bashCode'",
2650+
command: bashCode,
26512651
environment: {"PATH": getPATH(), "HOME": getHomeDirectory()},
26522652
));
26532653
commandQueue.add(LinuxCommand(
@@ -2776,4 +2776,114 @@ class Linux {
27762776
));
27772777
}
27782778
}
2779+
2780+
static Map<String, dynamic> getGrubSettings() {
2781+
String grubFileContent = File("/etc/default/grub").readAsStringSync();
2782+
List<String> lines = grubFileContent.split("\n");
2783+
Map<String, String> settingsFileMap = {};
2784+
for (String line in lines) {
2785+
if (line.contains("=") && !line.trim().startsWith("#")) {
2786+
List<String> parts = line.split("=");
2787+
settingsFileMap[parts[0]] = parts[1];
2788+
}
2789+
}
2790+
2791+
Map<String, dynamic> returnValue = {};
2792+
returnValue["grubVisible"] =
2793+
settingsFileMap["GRUB_TIMEOUT_STYLE"] == "menu" ||
2794+
settingsFileMap["GRUB_TIMEOUT_STYLE"] == null;
2795+
returnValue["enableBigFont"] = settingsFileMap["GRUB_GFXMODE"] == "640x480";
2796+
if (settingsFileMap["GRUB_TIMEOUT"] == null) {
2797+
settingsFileMap["GRUB_TIMEOUT"] = "0";
2798+
}
2799+
returnValue["timeout"] =
2800+
int.tryParse(settingsFileMap["GRUB_TIMEOUT"]!) ?? 0;
2801+
returnValue["startLastBootedOne"] =
2802+
settingsFileMap["GRUB_DEFAULT"] == "saved";
2803+
2804+
return returnValue;
2805+
}
2806+
2807+
static void ensureGrubSettings(context, bool grubVisible, bool enableBigFont,
2808+
int timeout, bool startLastBootedOne) {
2809+
String grub_timeout_style = grubVisible ? "menu" : "hidden";
2810+
String grub_timeout = timeout.toString();
2811+
String grub_default = startLastBootedOne ? "saved" : "0";
2812+
String grub_save_default = startLastBootedOne ? "true" : "false";
2813+
String grub_gfxmode = enableBigFont ? "640x480" : "";
2814+
2815+
if (grub_timeout_style == "menu" && timeout < 1) {
2816+
grub_timeout = "1";
2817+
}
2818+
2819+
if (grub_timeout_style == "hidden" && timeout < 0) {
2820+
grub_timeout = "0";
2821+
}
2822+
2823+
ensureOptionInConfigFile(
2824+
"GRUB_TIMEOUT_STYLE", grub_timeout_style, "/etc/default/grub");
2825+
ensureOptionInConfigFile("GRUB_TIMEOUT", grub_timeout, "/etc/default/grub");
2826+
ensureOptionInConfigFile("GRUB_DEFAULT", grub_default, "/etc/default/grub");
2827+
ensureOptionInConfigFile(
2828+
"GRUB_SAVEDEFAULT", grub_save_default, "/etc/default/grub");
2829+
ensureOptionInConfigFile("GRUB_GFXMODE", grub_gfxmode, "/etc/default/grub");
2830+
2831+
if (currentenvironment.distribution != DISTROS.FEDORA) {
2832+
commandQueue.add(LinuxCommand(
2833+
userId: 0,
2834+
command: "/usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg",
2835+
));
2836+
} else {
2837+
commandQueue.add(LinuxCommand(
2838+
userId: 0,
2839+
command: "/usr/sbin/grub2-mkconfig -o /boot/grub2/grub.cfg",
2840+
));
2841+
}
2842+
2843+
Navigator.of(context).push(MaterialPageRoute(
2844+
builder: (context) => RunCommandQueue(
2845+
title: AppLocalizations.of(context)!.grubConfiguration,
2846+
route: MainSearchLoader()),
2847+
));
2848+
}
2849+
2850+
/// Used for config files like /etc/default/grub.
2851+
///
2852+
/// Only adds the commands to the command queue to ensure that the key is set to the value.
2853+
/// If the value is empty, the key will be removed from the file.
2854+
static void ensureOptionInConfigFile(String key, String value, String path) {
2855+
/// Remove the key from the file if the value is empty
2856+
if (value.isEmpty) {
2857+
commandQueue.add(LinuxCommand(
2858+
userId: 0,
2859+
command: "sed -i '/$key/d' $path",
2860+
));
2861+
return;
2862+
}
2863+
2864+
bool settingFound = false;
2865+
// Check if the key is already in the file and is not commented out
2866+
String fileContent = File(path).readAsStringSync();
2867+
List<String> lines = fileContent.split("\n");
2868+
for (String line in lines) {
2869+
if (line.contains("$key=") && !line.trim().startsWith("#")) {
2870+
settingFound = true;
2871+
break;
2872+
}
2873+
}
2874+
2875+
if (!settingFound) {
2876+
// Add the key to the file
2877+
commandQueue.add(LinuxCommand(
2878+
userId: 0,
2879+
command: "echo '$key=$value' >> $path",
2880+
));
2881+
} else {
2882+
// Replace the key in the file
2883+
commandQueue.add(LinuxCommand(
2884+
userId: 0,
2885+
command: "sed -i 's/$key=.*/$key=$value/' $path",
2886+
));
2887+
}
2888+
}
27792889
}

0 commit comments

Comments
 (0)