Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add option for minimize/restore window #123

Merged
merged 4 commits into from
Dec 16, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 38 additions & 4 deletions lib/active_window/src/active_window.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';

import '../../logs/logs.dart';
import '../../main.dart';
import '../../native_platform/native_platform.dart';
import '../../storage/storage_repository.dart';

Expand Down Expand Up @@ -65,8 +66,7 @@ class ActiveWindow {
if (windowId == null) {
log.e('Failed to find saved windowId, cannot restore.');
} else {
final restoreSuccessful = await _nativePlatform.restoreWindow(windowId);
if (!restoreSuccessful) log.e('Failed to restore window.');
await _restore(windowId);
}

log.v('Resumed $savedPid successfully.');
Expand All @@ -87,6 +87,8 @@ class ActiveWindow {

final _window = await _nativePlatform.activeWindow();

log.v('Active window: $_window');

if (defaultTargetPlatform == TargetPlatform.windows) {
// Once in a blue moon on Windows we get "explorer.exe" as the active
// window, even when no file explorer windows are open / the desktop
Expand All @@ -97,8 +99,7 @@ class ActiveWindow {
}
}

final minimized = await _nativePlatform.minimizeWindow(_window.id);
if (!minimized) log.e('Failed to minimize window.');
await _minimize(_window.id);

// Small delay on Windows to ensure the window actually minimizes.
// Doesn't seem to be necessary on Linux.
Expand Down Expand Up @@ -126,4 +127,37 @@ class ActiveWindow {

return true;
}

Future<void> _minimize(int windowId) async {
final shouldMinimize = await _getShouldMinimize();
if (!shouldMinimize) return;

log.v('Starting minimize');
final minimized = await _nativePlatform.minimizeWindow(windowId);
if (!minimized) log.e('Failed to minimize window.');
}

Future<void> _restore(int windowId) async {
final shouldRestore = await _getShouldMinimize();
if (!shouldRestore) return;

log.v('Starting restore');
final minimized = await _nativePlatform.restoreWindow(windowId);
if (!minimized) log.e('Failed to restore window.');
}

/// Checks for a user preference on whether to minimize/restore windows.
Future<bool> _getShouldMinimize() async {
// If minimize preference was set by flag it overrides UI-based preference.
final minimizeArg = argParser.minimize;
if (minimizeArg != null) {
log.v('Received no-minimize flag, affecting window state: $minimizeArg');
return minimizeArg;
}

bool? minimize = await _storageRepository.getValue('minimizeWindows');
minimize ??= true;
log.v('Minimizing / restoring window: $minimize');
return minimize;
}
}
24 changes: 17 additions & 7 deletions lib/apps_list/cubit/apps_list_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,24 @@ class AppsListCubit extends Cubit<AppsListState> {
final successful = await _processRepository.resume(window.process.pid);

// Restore the window _after_ resuming or it might not restore.
if (successful) await _nativePlatform.restoreWindow(window.id);
if (successful) await _restore(window);

return successful;
}

Future<bool> _suspend(Window window) async {
await _minimize(window);
final successful = await _processRepository.suspend(window.process.pid);

// If suspend failed, restore the window so the user won't mistakenly
// think that the suspend was successful.
if (!successful) await _restore(window);
return successful;
}

Future<void> _minimize(Window window) async {
if (!_prefsCubit.state.minimizeWindows) return;

// Minimize the window before suspending or it might not minimize.
await _nativePlatform.minimizeWindow(window.id);

Expand All @@ -168,14 +180,12 @@ class AppsListCubit extends Cubit<AppsListState> {
if (io.Platform.isWindows) {
await Future.delayed(const Duration(milliseconds: 500));
}
}

final successful = await _processRepository.suspend(window.process.pid);
Future<void> _restore(Window window) async {
if (!_prefsCubit.state.minimizeWindows) return;

// If suspend failed, restore the window so the user won't mistakenly
// think that the suspend was successful.
if (!successful) await _nativePlatform.restoreWindow(window.id);

return successful;
await _nativePlatform.restoreWindow(window.id);
}

/// Refresh the process status associated with [window].
Expand Down
39 changes: 30 additions & 9 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,19 @@ Future<void> main(List<String> args) async {
await windowManager.ensureInitialized();

// Parse command-line arguments.
final argParser = ArgumentParser();
argParser.parseArgs(args);
argParser = ArgumentParser() //
..parseArgs(args);

final storageRepository = await StorageRepository.initialize(Hive);
final nativePlatform = NativePlatform();
await LoggingManager.initialize(verbose: argParser.verbose);

// Handle platform errors not caught by Flutter.
PlatformDispatcher.instance.onError = (error, stack) {
log.e('Uncaught platform error', error, stack);
return true;
};

final processRepository = ProcessRepository.init();

final activeWindow = ActiveWindow(
Expand All @@ -53,24 +60,19 @@ Future<void> main(List<String> args) async {
exit(0);
} else {}

// Handle platform errors not caught by Flutter.
PlatformDispatcher.instance.onError = (error, stack) {
log.e('Uncaught platform error', error, stack);
return true;
};

final sharedPreferences = await SharedPreferences.getInstance();
final settingsService = SettingsService(sharedPreferences);

final nyrnaWindow = NyrnaWindow();

// Created outside runApp so it can be accessed for window settings below.
final _settingsCubit = SettingsCubit(
final _settingsCubit = await SettingsCubit.init(
assetToTempDir: assetToTempDir,
getWindowInfo: window.getWindowInfo,
prefs: settingsService,
hotkeyService: HotkeyService(activeWindow),
nyrnaWindow: nyrnaWindow,
storageRepository: storageRepository,
);

// Provides information on this app from the pubspec.yaml.
Expand Down Expand Up @@ -136,8 +138,11 @@ Supported arguments:

''';

late ArgumentParser argParser;

/// Parse command-line arguments.
class ArgumentParser {
bool? minimize;
bool toggleActiveWindow = false;
bool verbose = false;

Expand All @@ -146,6 +151,22 @@ class ArgumentParser {
/// Parse received arguments.
void parseArgs(List<String> args) {
_parser
..addFlag(
'minimize',
defaultsTo: true,
callback: (bool value) {
/// We only want to register when the user calls the negated version of
/// this flag: `--no-minimize`. Otherwise the [minimize] value will be
/// null and the UI-set preference can be checked.
if (value == true) {
return;
} else {
minimize = false;
}
},
help: '''
Used with the `toggle` flag, `no-minimize` instructs Nyrna not to automatically minimize / restore the active window - it will be suspended / resumed only.''',
)
..addFlag(
'toggle',
abbr: 't',
Expand Down
21 changes: 18 additions & 3 deletions lib/settings/cubit/settings_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:window_size/window_size.dart' show PlatformWindow;
import '../../apps_list/apps_list.dart';
import '../../core/core.dart';
import '../../hotkey/hotkey_service.dart';
import '../../storage/storage_repository.dart';
import '../../window/nyrna_window.dart';
import '../settings_service.dart';

Expand All @@ -25,27 +26,30 @@ class SettingsCubit extends Cubit<SettingsState> {
final SettingsService _prefs;
final HotkeyService _hotkeyService;
final NyrnaWindow _nyrnaWindow;
final StorageRepository _storageRepository;

SettingsCubit._(
this._assetToTempDir,
this._getWindowInfo,
this._prefs,
this._hotkeyService,
this._nyrnaWindow, {
this._nyrnaWindow,
this._storageRepository, {
required SettingsState initialState,
}) : super(initialState) {
settingsCubit = this;
_hotkeyService.updateHotkey(state.hotKey);
_nyrnaWindow.preventClose(state.closeToTray);
}

factory SettingsCubit({
static Future<SettingsCubit> init({
required Future<File> Function(String path) assetToTempDir,
required Future<PlatformWindow> Function() getWindowInfo,
required SettingsService prefs,
required HotkeyService hotkeyService,
required NyrnaWindow nyrnaWindow,
}) {
required StorageRepository storageRepository,
}) async {
HotKey? hotkey;
final String? savedHotkey = prefs.getString('hotkey');
if (savedHotkey != null) {
Expand All @@ -54,17 +58,22 @@ class SettingsCubit extends Cubit<SettingsState> {
hotkey = defaultHotkey;
}

bool? minimizeWindows = await storageRepository.getValue('minimizeWindows');
minimizeWindows ??= true;

return SettingsCubit._(
assetToTempDir,
getWindowInfo,
prefs,
hotkeyService,
nyrnaWindow,
storageRepository,
initialState: SettingsState(
autoStart: prefs.getBool('autoStart') ?? false,
autoRefresh: _checkAutoRefresh(prefs),
closeToTray: prefs.getBool('closeToTray') ?? false,
hotKey: hotkey,
minimizeWindows: minimizeWindows,
refreshInterval: prefs.getInt('refreshInterval') ?? 5,
showHiddenWindows: prefs.getBool('showHiddenWindows') ?? false,
startHiddenInTray: prefs.getBool('startHiddenInTray') ?? false,
Expand Down Expand Up @@ -136,6 +145,12 @@ class SettingsCubit extends Cubit<SettingsState> {
emit(state.copyWith(closeToTray: closeToTray));
}

/// Update the preference for auto minimizing windows.
Future<void> updateMinimizeWindows(bool value) async {
emit(state.copyWith(minimizeWindows: value));
await _storageRepository.saveValue(key: 'minimizeWindows', value: value);
}

Future<void> updateShowHiddenWindows(bool value) async {
await _prefs.setBool(key: 'showHiddenWindows', value: value);
emit(state.copyWith(showHiddenWindows: value));
Expand Down
8 changes: 8 additions & 0 deletions lib/settings/cubit/settings_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ class SettingsState extends Equatable {
/// The hotkey to toggle active application suspend.
final HotKey hotKey;

/// If true the window will be automatically minimized when suspending and
/// restored when resuming.
final bool minimizeWindows;

/// How often to automatically refresh the list of open windows, in seconds.
final int refreshInterval;

Expand All @@ -23,6 +27,7 @@ class SettingsState extends Equatable {
required this.autoRefresh,
required this.closeToTray,
required this.hotKey,
required this.minimizeWindows,
required this.refreshInterval,
required this.showHiddenWindows,
required this.startHiddenInTray,
Expand All @@ -35,6 +40,7 @@ class SettingsState extends Equatable {
autoRefresh,
closeToTray,
hotKey,
minimizeWindows,
refreshInterval,
showHiddenWindows,
startHiddenInTray,
Expand All @@ -46,6 +52,7 @@ class SettingsState extends Equatable {
bool? autoRefresh,
bool? closeToTray,
HotKey? hotKey,
bool? minimizeWindows,
int? refreshInterval,
bool? showHiddenWindows,
bool? startHiddenInTray,
Expand All @@ -55,6 +62,7 @@ class SettingsState extends Equatable {
autoRefresh: autoRefresh ?? this.autoRefresh,
closeToTray: closeToTray ?? this.closeToTray,
hotKey: hotKey ?? this.hotKey,
minimizeWindows: minimizeWindows ?? this.minimizeWindows,
refreshInterval: refreshInterval ?? this.refreshInterval,
showHiddenWindows: showHiddenWindows ?? this.showHiddenWindows,
startHiddenInTray: startHiddenInTray ?? this.startHiddenInTray,
Expand Down
14 changes: 14 additions & 0 deletions lib/settings/widgets/behaviour_section.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@ class BehaviourSection extends StatelessWidget {
},
),
),
ListTile(
title: const Text('Minimize / restore windows'),
leading: const Icon(Icons.minimize),
trailing: BlocBuilder<SettingsCubit, SettingsState>(
builder: (context, state) {
return Switch(
value: state.minimizeWindows,
onChanged: (value) async {
await settingsCubit.updateMinimizeWindows(value);
},
);
},
),
),
const ShowHiddenTile(),
],
);
Expand Down