Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-msix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
run: ./flutterw build windows

- name: Copy assets into place
run: cp ./assets/* ./build/windows/runner/Release
run: mkdir ./build/windows/runner/Release/assets ; cp ./assets/* ./build/windows/runner/Release/assets

- name: Package the app as MSIX
run: ./flutterw pub run msix:create --install-certificate false
Expand Down
62 changes: 47 additions & 15 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,63 @@ import 'package:arquivolta/interfaces.dart';
import 'package:arquivolta/logging.dart';
import 'package:arquivolta/pages/install_page.dart';
import 'package:arquivolta/services/job.dart';
import 'package:arquivolta/services/util.dart';
import 'package:beamer/beamer.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get_it/get_it.dart';
import 'package:logger/logger.dart';

// ignore: implementation_imports
import 'package:logger/src/outputs/file_output.dart';

enum ApplicationMode { debug, production, test }

typedef BeamerRouteList
= Map<Pattern, dynamic Function(BuildContext, BeamState, Object?)>;

typedef BeamerPageList = List<PageInfo>;

Logger createLogger(ApplicationMode mode) {
if (mode == ApplicationMode.production) {
final appData = getLocalAppDataPath();
final ourAppDataDir = Directory('$appData/Arquivolta')
..createSync(recursive: true);

return Logger(
output: FileOutput(file: File('${ourAppDataDir.path}/log.txt')),
filter: ProductionFilter(),
printer: PrettyPrinter(
methodCount: 0,
errorMethodCount: 4,
excludeBox: {
Level.debug: true,
Level.info: true,
Level.verbose: true,
},
colors: false,
printEmojis: false, // NB: We add these in later
),
level: Level.info,
);
}

// NB: filter: ProductionFilter is not a typo :facepalm:
return Logger(
filter: ProductionFilter(),
printer: PrettyPrinter(
methodCount: 0,
errorMethodCount: 4,
excludeBox: {
Level.debug: true,
Level.info: true,
Level.verbose: true,
},
colors: false,
printEmojis: false, // NB: We add these in later
),
);
}

class App {
static GetIt find = GetIt.instance;

Expand All @@ -41,20 +86,7 @@ class App {

find
..registerSingleton(appMode)
..registerSingleton(
Logger(
printer: PrettyPrinter(
methodCount: 0,
errorMethodCount: 4,
excludeBox: {
Level.debug: true,
Level.info: true,
Level.verbose: true,
},
printEmojis: false, // NB: We add these in later
),
),
);
..registerSingleton(createLogger(appMode));

JobBase.setupRegistration(find);

Expand Down Expand Up @@ -86,7 +118,7 @@ class MainWindow extends StatelessWidget implements Loggable {
routesBuilder = RoutesLocationBuilder(routes: App.find<BeamerRouteList>());
delegate = BeamerDelegate(locationBuilder: routesBuilder);

d('Creating MainWindow!');
i('Starting Arquivolta!');
}

@override
Expand Down
3 changes: 2 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ void main() async {
unawaited(
windowManager.waitUntilReadyToShow().then((_) async {
await windowManager.setTitleBarStyle('hidden');
await windowManager.show();
await windowManager.setSkipTaskbar(false);
await windowManager.setMinimumSize(const Size(600, 400));
await windowManager.show();
}),
);

Expand Down
23 changes: 14 additions & 9 deletions lib/pages/install_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,23 +97,28 @@ class InstallPrompt extends HookWidget implements Loggable {
Widget build(BuildContext context) {
final distro = useTextEditingController(text: 'arch-foobar');
final user = useTextEditingController();
final password = useTextEditingController();
final passwordHidden = useState(true);
//final password = useTextEditingController();
//final passwordHidden = useState(true);

// Reevaluate shouldEnableButton whenever any of the text changes
[distro, user, password].forEach(useValueListenable);
[
distro,
user, /*password*/
].forEach(useValueListenable);

final shouldEnableButton = distro.text.length > 1 &&
user.text.length > 1 &&
password.text.length > 1;
user.text.length > 1; //&& password.text.length > 1;

final onPress = useCallback(
() => onPressedInstall(
distro.text,
user.text,
password.text,
'', //password.text,
),
[distro, user, password],
[
distro,
user, /*password*/
],
);

return Flex(
Expand All @@ -127,6 +132,7 @@ class InstallPrompt extends HookWidget implements Loggable {
controller: user,
header: 'Linux Username',
),
/*
TextBox(
controller: password,
header: 'Password',
Expand All @@ -138,6 +144,7 @@ class InstallPrompt extends HookWidget implements Loggable {
onPressed: () => passwordHidden.value = !passwordHidden.value,
),
),
*/
Align(
alignment: Alignment.centerRight,
child: Padding(
Expand Down Expand Up @@ -324,8 +331,6 @@ class ConsoleOutput extends StatelessWidget implements Loggable {

@override
Widget build(BuildContext context) {
d('Build! ${lines?.length}, scroller: ${consoleScroll.hashCode}');

final consoleFont = FluentTheme.of(context)
.typography
.body!
Expand Down
50 changes: 42 additions & 8 deletions lib/pages/page_base.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import 'package:arquivolta/app.dart';
import 'package:arquivolta/interfaces.dart';
import 'package:arquivolta/widgets/window_button.dart';
import 'package:beamer/beamer.dart';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:bitsdojo_window/bitsdojo_window.dart'
show MaximizeIcon, RestoreIcon, appWindow;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:window_manager/window_manager.dart';

Expand All @@ -13,23 +15,39 @@ mixin PageScaffolder implements RoutablePages {
final uri = beamState.uri.toString();
final idx = pages.indexWhere((p) => p.route.matchAsPrefix(uri) != null);

final style = FluentTheme.of(context);

return NavigationView(
appBar: NavigationAppBar(
title: const DragToMoveArea(
title: DragToMoveArea(
child: Align(
alignment: AlignmentDirectional.centerStart,
child: Text('Arquivolta Installer'),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Text(
'Arquivolta Installer',
style: style.typography.bodyStrong,
),
),
),
),
actions: DragToMoveArea(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [Spacer(), WindowButtons()],
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
Spacer(),
WindowButtons(
height: 50,
)
],
),
),
automaticallyImplyLeading: false,
),
pane: NavigationPane(
// NB: Auto looks nicer here but it's broken at the moment
// because when the pane is open the Title gets no padding
displayMode: PaneDisplayMode.open,
selected: idx,
size: const NavigationPaneSize(
openMinWidth: 250,
Expand All @@ -52,10 +70,22 @@ mixin PageScaffolder implements RoutablePages {
}

class WindowButtons extends StatelessWidget {
const WindowButtons({Key? key}) : super(key: key);
final double? height;
const WindowButtons({Key? key, this.height}) : super(key: key);

@override
Widget build(BuildContext context) {
var size = appWindow.titleBarButtonSize;

if (height != null) {
final ratio = height! / appWindow.titleBarButtonSize.height;

size = Size(
appWindow.titleBarButtonSize.width * ratio,
appWindow.titleBarButtonSize.height * ratio,
);
}

final ThemeData theme = FluentTheme.of(context);
final buttonColors = WindowButtonColors(
iconNormal: theme.inactiveColor,
Expand Down Expand Up @@ -83,11 +113,15 @@ class WindowButtons extends StatelessWidget {
children: [
Tooltip(
message: FluentLocalizations.of(context).minimizeWindowTooltip,
child: MinimizeWindowButton(colors: buttonColors),
child: MinimizeWindowButton(
colors: buttonColors,
buttonSize: size,
),
),
Tooltip(
message: FluentLocalizations.of(context).restoreWindowTooltip,
child: WindowButton(
buttonSize: size,
colors: buttonColors,
iconBuilder: (context) {
if (appWindow.isMaximized) {
Expand All @@ -100,7 +134,7 @@ class WindowButtons extends StatelessWidget {
),
Tooltip(
message: FluentLocalizations.of(context).closeWindowTooltip,
child: CloseWindowButton(colors: closeButtonColors),
child: CloseWindowButton(buttonSize: size, colors: closeButtonColors),
),
],
);
Expand Down
2 changes: 1 addition & 1 deletion lib/services/arch_to_rootfs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ JobBase<void> convertArchBootstrapToWSLRootFsJob(
String targetRootfsFile,
) {
return JobBase.fromBlock('Converting Arch Linux image to WSL format',
'Converting Arch Linux Bootstrap image to be importable via WSL2',
'Converting Arch Linux Bootstrap image to be usable via WSL2',
(job) async {
if (getOSArchitecture() == OperatingSystemType.aarch64) {
// NB: Arch Linux ARM images aren't brain-damaged like x86_64, so we can
Expand Down
14 changes: 11 additions & 3 deletions lib/services/install_arch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ set -eux

pacman --noconfirm -Syu
pacman --noconfirm -Sy base base-devel \
git zsh sudo docker htop tmux go vim \
git zsh sudo docker htop tmux go vim zenity \
wsl-use-windows-openssh wsl-set-up-wsld wsl-enable-systemd
''';

Expand All @@ -52,7 +52,8 @@ String addUser(String userName, String password) => '''
set -eux

useradd -m -G wheel -s /bin/zsh '$userName'
echo '$userName:$password' | chpasswd

echo "$userName:\$(zenity --password --title 'Enter a new password for $userName')" | chpasswd

## Set up sudo
echo '%wheel ALL=(ALL:ALL) ALL' > /etc/sudoers.d/00-enable-wheel
Expand Down Expand Up @@ -151,38 +152,45 @@ Future<void> runArchLinuxPostInstall(
setUpPacman(getLinuxArchitectureForOS().replaceAll('_', '-')),
[],
'Unable to configure Pacman updater',
friendlyDescription: 'Setup Pacman keychain and add Arquivolta repos',
),
await worker.runScriptInDistroAsJob(
'Set up locale',
configureLocale(ui.window.locale.toLanguageTag().replaceAll('-', '_')),
[],
"Couldn't set up locale",
friendlyDescription:
'Set Arch locale to ${ui.window.locale.toLanguageTag()}',
),
await worker.runScriptInDistroAsJob(
'Install base system',
installSystem,
[],
'Failed to install base packages',
friendlyDescription: 'Install developer tools and base packages',
),
await worker.runScriptInDistroAsJob(
'Create user $username',
addUser(username, password),
addUser(escapeStringForBash(username), escapeStringForBash(password)),
[],
"Couldn't create new user",
friendlyDescription: 'Setting up user and sudo access',
),
await worker.runScriptInDistroAsJob(
'Build Yay package',
buildYay,
[],
"Couldn't build package for Yay",
user: username,
friendlyDescription: 'Building Yay, a tool to install packages',
),
await worker.runScriptInDistroAsJob(
'Install Yay package',
installYay,
[],
"Couldn't install built package for Yay",
user: 'root',
friendlyDescription: 'Installing Yay, a tool to install packages',
),
];

Expand Down
1 change: 1 addition & 0 deletions lib/services/job.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ abstract class JobBase<T> extends CustomLoggable implements Loggable {
logOutput = so.stream;

_logger = Logger(
filter: ProductionFilter(),
output: so,
printer: ZeroAnnotationPrinter(),
level: App.find<ApplicationMode>() == ApplicationMode.production
Expand Down
16 changes: 16 additions & 0 deletions lib/services/util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,19 @@ Future<void> downloadUrlToFile(
.doOnData((buf) => bytes.add(buf.length))
.pipe(File(target).openWrite());
}

final re = RegExp('[a-zA-Z0-9,._+:@%/-]');
String escapeStringForBash(String str) {
final ret = StringBuffer();

// NB: Yes I know this is incredibly inefficient
for (int i = 0; i < str.length; i++) {
if (re.hasMatch(str[i])) {
ret.write(str[i]);
} else {
ret.write('\\${str[i]}');
}
}

return ret.toString();
}
Loading