Skip to content

Commit

Permalink
feat: Enable macos desktop (#188)
Browse files Browse the repository at this point in the history
* feat: macos desktop app on Apple silicon, initial config [wip]

* feat: macos desktop app on Apple silicon, initial config [wip]

* fix: use "super.key"

* chore: .vscode settings

* fix: more use of "super.key"

* chore: formating

* feat: misc macos settings.

* feat: set window size

* fix: desktop notification not (yet?) supported on macos

* fix: don't use a private variable for $version

* fix : comment

* feat: macos desktop app on Apple silicon, initial config [wip]

* fix: use "super.key"

* chore: .vscode settings

* fix: more use of "super.key"

* chore: formating

* feat: misc macos settings.

* feat: set window size

* fix: desktop notification not (yet?) supported on macos

* fix: don't use a private variable for $version

* fix : comment

* feat: in debug mode, throw an exception is a message is not translated

* chore: localizations

* feat: macos desktop app on Apple silicon, initial config [wip]

* fix: use "super.key"

* chore: .vscode settings

* fix: more use of "super.key"

* chore: formating

* feat: misc macos settings.

* feat: set window size

* fix: desktop notification not (yet?) supported on macos

* fix: don't use a private variable for $version

* fix : comment

* feat: in debug mode, throw an exception is a message is not translated

* chore: localizations

* feat: detect and use the Terminal app to connect via SSH

* fix: don't try to connect via SSH with empty username

* feat: macos desktop app on Apple silicon, initial config [wip]

* fix: use "super.key"

* chore: .vscode settings

* fix: more use of "super.key"

* chore: formating

* feat: misc macos settings.

* feat: set window size

* fix: desktop notification not (yet?) supported on macos

* fix: don't use a private variable for $version

* fix : comment

* feat: in debug mode, throw an exception is a message is not translated

* chore: localizations

* feat: detect and use the Terminal app to connect via SSH

* fix: don't try to connect via SSH with empty username

* feat: macos desktop app on Apple silicon, initial config [wip]

* feat: misc macos settings.

* feat: set window size

* fix : comment

* feat: macos desktop app on Apple silicon, initial config [wip]

* feat: misc macos settings.

* fix: app icons

* feat: packaging config

* chore: install disk name

* feat: getting ready for publication on the mac app strore

* fix: missing translations
  • Loading branch information
ymauray authored Aug 2, 2024
1 parent 7d13a31 commit 62faac7
Show file tree
Hide file tree
Showing 57 changed files with 1,727 additions and 72 deletions.
24 changes: 22 additions & 2 deletions .metadata
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,27 @@
# This file should be version controlled and should not be manually edited.

version:
revision: 3595343e20a61ff16d14e8ecc25f364276bb1b8b
channel: stable
revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
channel: "stable"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: macos
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49

# User provided section

# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.organizeImports": true,
"source.fixAll": "explicit",
"source.organizeImports": "explicit"
},
"cmake.sourceDirectory": "${workspaceFolder}/linux"
}
6 changes: 6 additions & 0 deletions assets/i18n/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,9 @@ msgstr "SSH username"
# Connect to the VM via SSH
msgid "Connect"
msgstr "Connect"

msgid "Loading available downloads"
msgstr "Loading available downloads"

msgid "Powered by"
msgstr "Powered by"
6 changes: 6 additions & 0 deletions assets/i18n/fr.po
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,9 @@ msgstr "Nom d'utilisateur SSH"
# Connect to the VM via SSH
msgid "Connect"
msgstr "Connecter"

msgid "Loading available downloads"
msgstr "Chargement des téléchargements disponibles"

msgid "Powered by"
msgstr "Propulsé par"
6 changes: 6 additions & 0 deletions assets/i18n/quickgui.pot
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,9 @@ msgstr ""
# Connect to the VM via SSH
msgid "Connect"
msgstr ""

msgid "Loading available downloads"
msgstr ""

msgid "Powered by"
msgstr ""
7 changes: 7 additions & 0 deletions distribute_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,10 @@ releases:
dart-define:
APP_ENV: dev
publish_to: none
- name: macos-dev
jobs:
- name: quickgui-dev-macos
package:
platform: macos
target: dmg
publish_to: none
19 changes: 11 additions & 8 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import 'dart:io';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
import 'package:window_size/window_size.dart';
import 'package:flutter/services.dart';

import 'src/app.dart';
import 'src/mixins/app_version.dart';
import 'src/model/app_settings.dart';
import 'src/model/operating_system.dart';
import 'src/model/option.dart';
import 'src/model/version.dart';
import 'src/model/osicons.dart';
import 'src/model/version.dart';

Future<List<OperatingSystem>> loadOperatingSystems(bool showUbuntus) async {
var process = await Process.run('quickget', ['--list-csv']);
Expand Down Expand Up @@ -65,9 +65,7 @@ Future<void> getIcons() async {
.where((String key) => key.contains('.svg'))
.toList();
for (final imagePath in imagePaths) {
String filename = imagePath
.split('/')
.last;
String filename = imagePath.split('/').last;
String id = filename.substring(0, filename.lastIndexOf('.'));
osIcons[id] = imagePath;
}
Expand All @@ -76,8 +74,13 @@ Future<void> getIcons() async {
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Don't forget to also change the size in linux/my_application.cc:50
setWindowMinSize(const Size(692, 580));
setWindowMaxSize(const Size(692, 580));
if (Platform.isMacOS) {
setWindowMinSize(const Size(692 + 2, 580 + 30));
setWindowMaxSize(const Size(692 + 2, 580 + 30));
} else {
setWindowMinSize(const Size(692, 580));
setWindowMaxSize(const Size(692, 580));
}
final foundQuickGet = await Process.run('which', ['quickget']);
if (foundQuickGet.exitCode == 0) {
gOperatingSystems = loadOperatingSystems(false);
Expand Down
7 changes: 3 additions & 4 deletions lib/src/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import 'model/app_settings.dart';
import 'pages/main_page.dart';

class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
const App({super.key});

@override
State<App> createState() => _AppState();
Expand Down Expand Up @@ -61,11 +61,10 @@ class _AppState extends State<App> with PreferencesMixin {
builder: (context, appSettings, _) => MaterialApp(
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSwatch(
colorScheme: ColorScheme.fromSwatch(
primarySwatch: Colors.pink,
backgroundColor: Colors.white,
)
),
)),
darkTheme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSwatch(
Expand Down
8 changes: 4 additions & 4 deletions lib/src/pages/downloader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ import '../widgets/downloader/download_progress_bar.dart';

class Downloader extends StatefulWidget {
const Downloader({
Key? key,
required this.operatingSystem,
required this.version,
this.option,
}) : super(key: key);
super.key,
});

final OperatingSystem operatingSystem;
final Version version;
Expand All @@ -30,7 +30,7 @@ class Downloader extends StatefulWidget {
}

class _DownloaderState extends State<Downloader> {
final notificationsClient = NotificationsClient();
final notificationsClient = Platform.isMacOS ? null : NotificationsClient();
final curlPattern = RegExp("( [0-9.]+%)");
late final Stream<double> _progressStream;
bool _downloadFinished = false;
Expand Down Expand Up @@ -71,7 +71,7 @@ class _DownloaderState extends State<Downloader> {
controller.close();
setState(() {
_downloadFinished = true;
notificationsClient.notify(
notificationsClient?.notify(
_cancelled
? context.t('Download cancelled')
: context.t('Download complete'),
Expand Down
2 changes: 1 addition & 1 deletion lib/src/pages/downloader_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import '../widgets/home_page/downloader_menu.dart';
import '../widgets/home_page/logo.dart';

class DownloaderPage extends StatelessWidget {
const DownloaderPage({Key? key}) : super(key: key);
const DownloaderPage({super.key});

@override
Widget build(BuildContext context) {
Expand Down
3 changes: 2 additions & 1 deletion lib/src/pages/main_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import '../widgets/home_page/main_menu.dart';
import '../widgets/left_menu.dart';

class MainPage extends StatefulWidget {
const MainPage({Key? key}) : super(key: key);
const MainPage({super.key});

@override
State<MainPage> createState() => _MainPageState();
Expand All @@ -23,6 +23,7 @@ class _MainPageState extends State<MainPage> {

@override
Widget build(BuildContext context) {
GettextLocalizations.of(context).enableExceptions(true);
return Scaffold(
appBar: AppBar(
title: Text(context.t('Main menu')),
Expand Down
46 changes: 32 additions & 14 deletions lib/src/pages/manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import 'package:version/version.dart';

import '../globals.dart';
import '../mixins/preferences_mixin.dart';
import '../model/vminfo.dart';
import '../model/osicons.dart';
import '../model/vminfo.dart';

/// VM manager page.
/// Displays a list of available VMs, running state and connection info,
/// with buttons to start and stop VMs.
class Manager extends StatefulWidget {
const Manager({Key? key}) : super(key: key);
const Manager({super.key});

@override
State<Manager> createState() => _ManagerState();
Expand All @@ -33,6 +33,7 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
final List<String> _sshVms = [];
String? _terminalEmulator;
final List<String> _supportedTerminalEmulators = [
if (Platform.isMacOS) 'osascript',
'alacritty',
'cool-retro-term',
'gnome-terminal',
Expand All @@ -49,7 +50,7 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
'uxrvt',
'xfce4-terminal',
'xrvt',
'xterm'
'xterm',
];
Timer? refreshTimer;

Expand Down Expand Up @@ -83,8 +84,7 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
// Find out which terminal emulator we have set as the default.
String result = whichSync('x-terminal-emulator') ?? '';
if (result.isNotEmpty) {
String terminalEmulator =
await File(result).resolveSymbolicLinks();
String terminalEmulator = await File(result).resolveSymbolicLinks();
terminalEmulator = path.basenameWithoutExtension(terminalEmulator);
if (_supportedTerminalEmulators.contains(terminalEmulator)) {
setState(() {
Expand All @@ -109,7 +109,7 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
void _detectSpice() async {
var result = whichSync('spicy') ?? '';
setState(() {
_spicy = result.isNotEmpty ;
_spicy = result.isNotEmpty;
});
}

Expand Down Expand Up @@ -273,7 +273,7 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
}
String vmStem = currentVm;
SvgPicture? osIcon;
while(vmStem.contains('-')) {
while (vmStem.contains('-')) {
vmStem = vmStem.substring(0, vmStem.lastIndexOf('-'));
if (osIcons.containsKey(vmStem)) {
osIcon = SvgPicture.asset(
Expand Down Expand Up @@ -301,7 +301,11 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
? null
: () async {
Map<String, VmInfo> activeVms = _activeVms;
List<String> command = ['quickemu', '--vm', '$currentVm.conf'];
List<String> command = [
'quickemu',
'--vm',
'$currentVm.conf'
];
if (_spicy) {
command.addAll(['--display', 'spice']);
}
Expand All @@ -327,7 +331,7 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
builder: (BuildContext context) => AlertDialog(
title: Text(context.t('Stop The Virtual Machine?')),
content: Text(context.t(
'You are about to terminate the virtual machine',
'You are about to terminate the virtual machine {0}',
args: [currentVm])),
actions: <Widget>[
TextButton(
Expand All @@ -346,9 +350,15 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
var shell = Shell();
// If Quickemu is newer than 4.9.6, use the new --kill option
// which is macOS compatible.
var quickemuVersion = Version.parse(await fetchQuickemuVersion());
var quickemuVersion =
Version.parse(await fetchQuickemuVersion());
if (quickemuVersion >= Version(4, 9, 6)) {
shell.run(['quickemu', '--vm', '$currentVm.conf', '--kill'].join(' '));
shell.run([
'quickemu',
'--vm',
'$currentVm.conf',
'--kill'
].join(' '));
} else {
shell.run(['killall', currentVm].join(' '));
}
Expand Down Expand Up @@ -432,8 +442,8 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
IconButton(
icon: SvgPicture.asset('assets/images/console.svg',
semanticsLabel: 'Connect with SSH',
colorFilter: ColorFilter.mode(sshy ? buttonColor : Colors.grey, BlendMode.srcIn)
),
colorFilter: ColorFilter.mode(
sshy ? buttonColor : Colors.grey, BlendMode.srcIn)),
tooltip: sshy
? context.t('Connect with SSH')
: context.t('SSH server not detected on guest'),
Expand Down Expand Up @@ -462,7 +472,10 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
child: Text(context.t('Cancel')),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
onPressed: () {
if (usernameController.text.isEmpty) return;
Navigator.of(context).pop(true);
},
child: Text(context.t('Connect')),
),
],
Expand All @@ -480,6 +493,11 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
// Strip the extension as x-terminal-emulator may point to a .wrapper
switch (path
.basenameWithoutExtension(_terminalEmulator!)) {
case 'osascript':
sshArgs = [
'-e \'tell app "Terminal" to do script "${sshArgs.join(' ')}"\''
];
break;
case 'gnome-terminal':
case 'mate-terminal':
sshArgs.insert(0, '--');
Expand Down
2 changes: 1 addition & 1 deletion lib/src/pages/operating_system_selection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import '../model/operating_system.dart';
import '../model/osicons.dart';

class OperatingSystemSelection extends StatefulWidget {
const OperatingSystemSelection({Key? key}) : super(key: key);
const OperatingSystemSelection({super.key});

@override
State<OperatingSystemSelection> createState() =>
Expand Down
2 changes: 1 addition & 1 deletion lib/src/pages/option_selection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:gettext_i18n/gettext_i18n.dart';
import '../model/version.dart';

class OptionSelection extends StatefulWidget {
const OptionSelection(this.version, {Key? key}) : super(key: key);
const OptionSelection(this.version, {super.key});

final Version version;

Expand Down
3 changes: 1 addition & 2 deletions lib/src/pages/version_selection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import '../model/version.dart';
import 'option_selection.dart';

class VersionSelection extends StatefulWidget {
const VersionSelection({Key? key, required this.operatingSystem})
: super(key: key);
const VersionSelection({required this.operatingSystem, super.key});

final OperatingSystem operatingSystem;

Expand Down
4 changes: 2 additions & 2 deletions lib/src/widgets/downloader/cancel_dismiss_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import 'package:gettext_i18n/gettext_i18n.dart';

class CancelDismissButton extends StatelessWidget {
const CancelDismissButton({
Key? key,
required this.downloadFinished,
required this.onCancel,
}) : super(key: key);
super.key,
});

final bool downloadFinished;
final VoidCallback onCancel;
Expand Down
Loading

0 comments on commit 62faac7

Please sign in to comment.