Merge branch 'main' into feature/issuer_icons

This commit is contained in:
Adam Velebil 2023-02-28 13:39:05 +01:00
commit 8978298f01
No known key found for this signature in database
GPG Key ID: C9B1E4A3CBBD2E10
34 changed files with 788 additions and 117 deletions

View File

@ -17,7 +17,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: 'stable'
flutter-version: '3.7.3'
flutter-version: '3.7.5'
- run: |
flutter config
flutter --version

View File

@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
env:
PYVER: 3.11
FLUTTER: '3.7.3'
FLUTTER: '3.7.5'
container:
image: ubuntu:18.04
env:
@ -18,7 +18,7 @@ jobs:
- name: Install dependencies
run: |
apt-get update
apt-get install -qq software-properties-common
apt-get install -qq software-properties-common libnotify-dev libayatana-appindicator3-dev patchelf
add-apt-repository -y ppa:git-core/ppa
add-apt-repository -y ppa:deadsnakes/ppa
apt-get install -qq git python$PYVER-dev python$PYVER-venv
@ -82,6 +82,18 @@ jobs:
- name: Check generated files
run: git diff --exit-code
- name: Embedd appindicator
run: |
patchelf --set-rpath '$ORIGIN' build/linux/x64/release/bundle/lib/libtray_manager_plugin.so
cp -L /usr/lib/x86_64-linux-gnu/libayatana-appindicator3.so.1 build/linux/x64/release/bundle/lib/
patchelf --set-rpath '$ORIGIN' build/linux/x64/release/bundle/lib/libayatana-appindicator3.so.1
cp -L /usr/lib/x86_64-linux-gnu/libayatana-indicator3.so.7 build/linux/x64/release/bundle/lib/
patchelf --set-rpath '$ORIGIN' build/linux/x64/release/bundle/lib/libayatana-indicator3.so.7
cp -L /usr/lib/x86_64-linux-gnu/libdbusmenu-glib.so.4 build/linux/x64/release/bundle/lib/
patchelf --set-rpath '$ORIGIN' build/linux/x64/release/bundle/lib/libdbusmenu-glib.so.4
cp -L /usr/lib/x86_64-linux-gnu/libdbusmenu-gtk3.so.4 build/linux/x64/release/bundle/lib/
patchelf --set-rpath '$ORIGIN' build/linux/x64/release/bundle/lib/libdbusmenu-gtk3.so.4
- name: Rename and archive app
run: |
export REF=$(echo ${GITHUB_REF} | cut -d '/' -f 3)

View File

@ -49,7 +49,7 @@ jobs:
with:
channel: 'stable'
architecture: 'x64'
flutter-version: '3.7.3'
flutter-version: '3.7.5'
- run: flutter config --enable-macos-desktop
- run: flutter --version

View File

@ -45,7 +45,7 @@ jobs:
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
flutter-version: '3.7.3'
flutter-version: '3.7.5'
- run: flutter config --enable-windows-desktop
- run: flutter --version

View File

@ -112,6 +112,7 @@ extension OathFunctions on WidgetTester {
}
await shortWait();
/// find an AccountView with issuer/name in the account list
var matchingAccounts = find.descendant(
of: findAccountList(),
@ -146,8 +147,11 @@ extension OathFunctions on WidgetTester {
expect(accountView, isNotNull);
if (accountView != null) {
await ensureVisible(find.byWidget(accountView));
await tap(find.byWidget(accountView));
final accountFinder = find.byWidget(accountView);
await ensureVisible(accountFinder);
final codeButtonFinder = find.descendant(
of: accountFinder, matching: find.bySubtype<FilledButton>());
await tap(codeButtonFinder);
await shortWait();
}
}

View File

@ -162,6 +162,8 @@ class AboutPage extends ConsumerWidget {
data.insert(0, {
'app_version': version,
'dart': Platform.version,
'os': Platform.operatingSystem,
'os_version': Platform.operatingSystemVersion,
});
final text = const JsonEncoder.withIndent(' ').convert(data);
await ref.read(clipboardProvider).setText(text);

View File

@ -134,5 +134,6 @@ class WindowState with _$WindowState {
required bool focused,
required bool visible,
required bool active,
@Default(false) bool hidden,
}) = _WindowState;
}

View File

@ -799,6 +799,7 @@ mixin _$WindowState {
bool get focused => throw _privateConstructorUsedError;
bool get visible => throw _privateConstructorUsedError;
bool get active => throw _privateConstructorUsedError;
bool get hidden => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$WindowStateCopyWith<WindowState> get copyWith =>
@ -811,7 +812,7 @@ abstract class $WindowStateCopyWith<$Res> {
WindowState value, $Res Function(WindowState) then) =
_$WindowStateCopyWithImpl<$Res, WindowState>;
@useResult
$Res call({bool focused, bool visible, bool active});
$Res call({bool focused, bool visible, bool active, bool hidden});
}
/// @nodoc
@ -830,6 +831,7 @@ class _$WindowStateCopyWithImpl<$Res, $Val extends WindowState>
Object? focused = null,
Object? visible = null,
Object? active = null,
Object? hidden = null,
}) {
return _then(_value.copyWith(
focused: null == focused
@ -844,6 +846,10 @@ class _$WindowStateCopyWithImpl<$Res, $Val extends WindowState>
? _value.active
: active // ignore: cast_nullable_to_non_nullable
as bool,
hidden: null == hidden
? _value.hidden
: hidden // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
@ -856,7 +862,7 @@ abstract class _$$_WindowStateCopyWith<$Res>
__$$_WindowStateCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool focused, bool visible, bool active});
$Res call({bool focused, bool visible, bool active, bool hidden});
}
/// @nodoc
@ -873,6 +879,7 @@ class __$$_WindowStateCopyWithImpl<$Res>
Object? focused = null,
Object? visible = null,
Object? active = null,
Object? hidden = null,
}) {
return _then(_$_WindowState(
focused: null == focused
@ -887,6 +894,10 @@ class __$$_WindowStateCopyWithImpl<$Res>
? _value.active
: active // ignore: cast_nullable_to_non_nullable
as bool,
hidden: null == hidden
? _value.hidden
: hidden // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@ -895,7 +906,10 @@ class __$$_WindowStateCopyWithImpl<$Res>
class _$_WindowState implements _WindowState {
_$_WindowState(
{required this.focused, required this.visible, required this.active});
{required this.focused,
required this.visible,
required this.active,
this.hidden = false});
@override
final bool focused;
@ -903,10 +917,13 @@ class _$_WindowState implements _WindowState {
final bool visible;
@override
final bool active;
@override
@JsonKey()
final bool hidden;
@override
String toString() {
return 'WindowState(focused: $focused, visible: $visible, active: $active)';
return 'WindowState(focused: $focused, visible: $visible, active: $active, hidden: $hidden)';
}
@override
@ -916,11 +933,13 @@ class _$_WindowState implements _WindowState {
other is _$_WindowState &&
(identical(other.focused, focused) || other.focused == focused) &&
(identical(other.visible, visible) || other.visible == visible) &&
(identical(other.active, active) || other.active == active));
(identical(other.active, active) || other.active == active) &&
(identical(other.hidden, hidden) || other.hidden == hidden));
}
@override
int get hashCode => Object.hash(runtimeType, focused, visible, active);
int get hashCode =>
Object.hash(runtimeType, focused, visible, active, hidden);
@JsonKey(ignore: true)
@override
@ -933,7 +952,8 @@ abstract class _WindowState implements WindowState {
factory _WindowState(
{required final bool focused,
required final bool visible,
required final bool active}) = _$_WindowState;
required final bool active,
final bool hidden}) = _$_WindowState;
@override
bool get focused;
@ -942,6 +962,8 @@ abstract class _WindowState implements WindowState {
@override
bool get active;
@override
bool get hidden;
@override
@JsonKey(ignore: true)
_$$_WindowStateCopyWith<_$_WindowState> get copyWith =>
throw _privateConstructorUsedError;

View File

@ -24,6 +24,7 @@ import 'package:window_manager/window_manager.dart';
import '../about_page.dart';
import '../android/views/android_settings_page.dart';
import '../core/state.dart';
import '../desktop/state.dart';
import '../oath/keys.dart';
import '../settings_page.dart';
import 'message.dart';
@ -42,6 +43,10 @@ class CloseIntent extends Intent {
const CloseIntent();
}
class HideIntent extends Intent {
const HideIntent();
}
class SearchIntent extends Intent {
const SearchIntent();
}
@ -77,6 +82,12 @@ Widget registerGlobalShortcuts(
windowManager.close();
return null;
}),
HideIntent: CallbackAction<HideIntent>(onInvoke: (_) {
if (isDesktop) {
ref.read(desktopWindowStateProvider.notifier).setWindowHidden(true);
}
return null;
}),
SearchIntent: CallbackAction<SearchIntent>(onInvoke: (intent) {
// If the OATH view doesn't have focus, but is shown, find and select the search bar.
final searchContext = searchAccountsField.currentContext;
@ -136,8 +147,7 @@ Widget registerGlobalShortcuts(
child: Shortcuts(
shortcuts: {
LogicalKeySet(ctrlOrCmd, LogicalKeyboardKey.keyC): const CopyIntent(),
LogicalKeySet(ctrlOrCmd, LogicalKeyboardKey.keyW):
const CloseIntent(),
LogicalKeySet(ctrlOrCmd, LogicalKeyboardKey.keyW): const HideIntent(),
LogicalKeySet(ctrlOrCmd, LogicalKeyboardKey.keyF):
const SearchIntent(),
if (isDesktop) ...{

View File

@ -15,11 +15,13 @@
*/
import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:yubico_authenticator/app/logging.dart';
import '../core/state.dart';
@ -40,6 +42,32 @@ final supportedThemesProvider = StateProvider<List<ThemeMode>>(
(ref) => throw UnimplementedError(),
);
final _l10nProvider = StateNotifierProvider<_L10nNotifier, AppLocalizations>(
(ref) => _L10nNotifier());
final l10nProvider = Provider<AppLocalizations>(
(ref) => ref.watch(_l10nProvider),
);
class _L10nNotifier extends StateNotifier<AppLocalizations>
with WidgetsBindingObserver {
_L10nNotifier() : super(lookupAppLocalizations(window.locale)) {
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
@protected
void didChangeLocales(List<Locale>? locales) {
state = lookupAppLocalizations(window.locale);
}
}
final themeModeProvider = StateNotifierProvider<ThemeModeNotifier, ThemeMode>(
(ref) => ThemeModeNotifier(
ref.watch(prefProvider), ref.read(supportedThemesProvider)),

View File

@ -17,6 +17,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:local_notifier/local_notifier.dart';
import '../message.dart';
@ -130,6 +131,24 @@ UserInteractionController promptUserInteraction(
required String description,
Widget? icon,
void Function()? onCancel,
bool headless = false,
}) {
if (headless) {
// No support for icon or onCancel.
return _notificationUserInteraction(context,
title: title, description: description);
} else {
return _dialogUserInteraction(context,
title: title, description: description, icon: icon, onCancel: onCancel);
}
}
UserInteractionController _dialogUserInteraction(
BuildContext context, {
required String title,
required String description,
Widget? icon,
void Function()? onCancel,
}) {
var wasPopped = false;
final controller = _UserInteractionController(
@ -164,3 +183,60 @@ UserInteractionController promptUserInteraction(
return controller;
}
class _NotificationUserInteractionController extends UserInteractionController {
String title;
String description;
Widget? icon;
LocalNotification _notification;
_NotificationUserInteractionController({
required this.title,
required this.description,
}) : _notification = LocalNotification(
title: title,
body: description,
)..show();
@override
void close() {
_notification.close();
}
Future<void> _doUpdateNotification() async {
await _notification.close();
await Future.delayed(const Duration(milliseconds: 200));
_notification = LocalNotification(title: title, body: description);
await _notification.show();
}
@override
void updateContent({String? title, String? description, Widget? icon}) {
bool changed = false;
if (title != null) {
this.title = title;
changed = true;
}
if (description != null) {
this.description = description;
changed = true;
}
if (icon != null) {
this.icon = icon;
}
if (changed) {
_doUpdateNotification();
}
}
}
UserInteractionController _notificationUserInteraction(
BuildContext context, {
required String title,
required String description,
}) {
return _NotificationUserInteractionController(
title: title,
description: description,
);
}

View File

@ -23,6 +23,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:local_notifier/local_notifier.dart';
import 'package:logging/logging.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:window_manager/window_manager.dart';
@ -47,14 +48,15 @@ import 'rpc.dart';
import 'devices.dart';
import 'qr_scanner.dart';
import 'state.dart';
import 'systray.dart';
final _log = Logger('desktop.init');
const String _keyWidth = 'DESKTOP_WINDOW_WIDTH';
const String _keyHeight = 'DESKTOP_WINDOW_HEIGHT';
class _WindowResizeListener extends WindowListener {
class _WindowEventListener extends WindowListener {
final SharedPreferences _prefs;
_WindowResizeListener(this._prefs);
_WindowEventListener(this._prefs);
@override
void onWindowResize() async {
@ -62,6 +64,13 @@ class _WindowResizeListener extends WindowListener {
await _prefs.setDouble(_keyWidth, size.width);
await _prefs.setDouble(_keyHeight, size.height);
}
@override
void onWindowClose() async {
if (Platform.isMacOS) {
await windowManager.destroy();
}
}
}
Future<Widget> initialize(List<String> argv) async {
@ -69,14 +78,24 @@ Future<Widget> initialize(List<String> argv) async {
await windowManager.ensureInitialized();
final prefs = await SharedPreferences.getInstance();
final isHidden = prefs.getBool(windowHidden) ?? false;
unawaited(windowManager.waitUntilReadyToShow().then((_) async {
await windowManager.setMinimumSize(const Size(270, 0));
final width = prefs.getDouble(_keyWidth) ?? 400;
final height = prefs.getDouble(_keyHeight) ?? 720;
await windowManager.setSize(Size(width, height));
await windowManager.show();
windowManager.addListener(_WindowResizeListener(prefs));
unawaited(windowManager
.waitUntilReadyToShow(WindowOptions(
minimumSize: const Size(270, 0),
size: Size(
prefs.getDouble(_keyWidth) ?? 400,
prefs.getDouble(_keyHeight) ?? 720,
),
skipTaskbar: isHidden,
))
.then((_) async {
if (isHidden) {
await windowManager.setSkipTaskbar(true);
} else {
await windowManager.show();
}
windowManager.addListener(_WindowEventListener(prefs));
}));
// Either use the _HELPER_PATH environment variable, or look relative to executable.
@ -106,6 +125,11 @@ Future<Widget> initialize(List<String> argv) async {
final rpcFuture = _initHelper(exe!);
_initLicenses();
await localNotifier.setup(
appName: 'Yubico Authenticator',
shortcutPolicy: ShortcutPolicy.ignore,
);
return ProviderScope(
overrides: [
supportedAppsProvider.overrideWithValue([
@ -155,6 +179,9 @@ Future<Widget> initialize(List<String> argv) async {
ref.read(rpcProvider).valueOrNull?.setLogLevel(level);
});
// Initialize systray
ref.watch(systrayProvider);
// Show a loading or error page while the Helper isn't ready
return ref.watch(rpcProvider).when(
data: (data) => const MainPage(),

View File

@ -20,6 +20,7 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:logging/logging.dart';
import '../../app/logging.dart';
@ -192,9 +193,9 @@ class _DesktopOathStateNotifier extends OathStateNotifier {
}
final desktopOathCredentialListProvider = StateNotifierProvider.autoDispose
.family<OathCredentialListNotifier, List<OathPair>?, DevicePath>(
.family<DesktopCredentialListNotifier, List<OathPair>?, DevicePath>(
(ref, devicePath) {
var notifier = _DesktopCredentialListNotifier(
var notifier = DesktopCredentialListNotifier(
ref.watch(withContextProvider),
ref.watch(_sessionProvider(devicePath)),
ref.watch(oathStateProvider(devicePath)
@ -203,6 +204,7 @@ final desktopOathCredentialListProvider = StateNotifierProvider.autoDispose
ref.listen<WindowState>(windowStateProvider, (_, windowState) {
notifier._notifyWindowState(windowState);
}, fireImmediately: true);
return notifier;
},
);
@ -225,12 +227,12 @@ String _formatSteam(String response) {
return value;
}
class _DesktopCredentialListNotifier extends OathCredentialListNotifier {
class DesktopCredentialListNotifier extends OathCredentialListNotifier {
final WithContext _withContext;
final RpcNodeSession _session;
final bool _locked;
Timer? _timer;
_DesktopCredentialListNotifier(this._withContext, this._session, this._locked)
DesktopCredentialListNotifier(this._withContext, this._session, this._locked)
: super();
void _notifyWindowState(WindowState windowState) {
@ -251,7 +253,7 @@ class _DesktopCredentialListNotifier extends OathCredentialListNotifier {
@override
Future<OathCode> calculate(OathCredential credential,
{bool update = true}) async {
{bool update = true, bool headless = false}) async {
var now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
if (update) {
// Manually triggered, need to pad timer to avoid immediate expiration
@ -264,12 +266,16 @@ class _DesktopCredentialListNotifier extends OathCredentialListNotifier {
signaler.signals.listen((signal) async {
if (signal.status == 'touch') {
controller = await _withContext(
(context) async => promptUserInteraction(
context,
icon: const Icon(Icons.touch_app),
title: 'Touch Required',
description: 'Touch the button on your YubiKey now.',
),
(context) async {
final l10n = AppLocalizations.of(context)!;
return promptUserInteraction(
context,
icon: const Icon(Icons.touch_app),
title: l10n.oath_touch_required,
description: l10n.oath_touch_now,
headless: headless,
);
},
);
}
});

View File

@ -24,8 +24,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:window_manager/window_manager.dart';
import 'package:yubico_authenticator/app/logging.dart';
import '../app/logging.dart';
import '../app/models.dart';
import '../app/state.dart';
import '../core/state.dart';
@ -58,20 +58,23 @@ class _RpcStateNotifier extends StateNotifier<RpcState> {
}
}
final _windowStateProvider =
StateNotifierProvider<_WindowStateNotifier, WindowState>(
(ref) => _WindowStateNotifier());
final desktopWindowStateProvider =
StateNotifierProvider<DesktopWindowStateNotifier, WindowState>(
(ref) => DesktopWindowStateNotifier(ref.watch(prefProvider)));
final desktopWindowStateProvider = Provider<WindowState>(
(ref) => ref.watch(_windowStateProvider),
);
const String windowHidden = 'DESKTOP_WINDOW_HIDDEN';
class _WindowStateNotifier extends StateNotifier<WindowState>
class DesktopWindowStateNotifier extends StateNotifier<WindowState>
with WindowListener {
final SharedPreferences _prefs;
Timer? _idleTimer;
_WindowStateNotifier()
: super(WindowState(focused: true, visible: true, active: true)) {
DesktopWindowStateNotifier(this._prefs)
: super(WindowState(
focused: true,
visible: true,
active: true,
hidden: _prefs.getBool(windowHidden) ?? false)) {
_init();
}
@ -88,6 +91,17 @@ class _WindowStateNotifier extends StateNotifier<WindowState>
}
}
void setWindowHidden(bool hidden) async {
if (hidden) {
await windowManager.hide();
} else {
await windowManager.show();
}
await windowManager.setSkipTaskbar(hidden);
await _prefs.setBool(windowHidden, hidden);
state = state.copyWith(hidden: hidden);
}
@override
void dispose() {
windowManager.removeListener(this);
@ -101,6 +115,7 @@ class _WindowStateNotifier extends StateNotifier<WindowState>
}
@override
@protected
void onWindowEvent(String eventName) {
if (mounted) {
switch (eventName) {
@ -144,7 +159,29 @@ class _DesktopClipboard extends AppClipboard {
@override
Future<void> setText(String toClipboard, {bool isSensitive = false}) async {
await Clipboard.setData(ClipboardData(text: toClipboard));
// Wayland requires the window to be focused to copy to clipboard
final needsFocus = Platform.isLinux &&
Platform.environment['XDG_SESSION_TYPE'] == 'wayland';
var hidden = false;
try {
if (needsFocus && !await windowManager.isFocused()) {
if (!await windowManager.isVisible()) {
hidden = true;
await windowManager.setOpacity(0.0);
await windowManager.show();
}
await windowManager.focus();
// Window focus isn't immediate, wait until focused with 10s timeout
await Future.doWhile(() async => !await windowManager.isFocused())
.timeout(const Duration(seconds: 10));
}
await Clipboard.setData(ClipboardData(text: toClipboard));
} finally {
if (hidden) {
await windowManager.hide();
await windowManager.setOpacity(1.0);
}
}
}
}

221
lib/desktop/systray.dart Executable file
View File

@ -0,0 +1,221 @@
/*
* Copyright (C) 2023 Yubico.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:local_notifier/local_notifier.dart';
import 'package:tray_manager/tray_manager.dart';
import 'package:window_manager/window_manager.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../app/models.dart';
import '../app/shortcuts.dart';
import '../app/state.dart';
import '../core/models.dart';
import '../exception/cancellation_exception.dart';
import '../oath/models.dart';
import '../oath/state.dart';
import '../oath/views/utils.dart';
import 'oath/state.dart';
import 'state.dart';
final _favoriteAccounts =
Provider.autoDispose<Pair<DevicePath?, List<OathCredential>>>(
(ref) {
final deviceData = ref.watch(currentDeviceDataProvider).valueOrNull;
if (deviceData != null) {
final credentials =
ref.watch(desktopOathCredentialListProvider(deviceData.node.path));
final favorites = ref.watch(favoritesProvider);
final listed = credentials
?.map((e) => e.credential)
.where((c) => favorites.contains(c.id))
.toList() ??
[];
return Pair(deviceData.node.path, listed);
}
return Pair(null, []);
},
);
final systrayProvider = Provider.autoDispose((ref) {
final systray = _Systray(ref, ref.watch(l10nProvider));
// Keep track of which accounts to show
ref.listen(
_favoriteAccounts,
(_, next) {
systray._updateCredentials(next);
},
);
// Keep track of the shown/hidden state of the app
ref.listen(windowStateProvider.select((value) => value.hidden), (_, hidden) {
systray._setHidden(hidden);
}, fireImmediately: true);
ref.onDispose(systray.dispose);
return systray;
});
Future<OathCode?> _calculateCode(
DevicePath devicePath, OathCredential credential, Ref ref) async {
try {
return await (ref
.read(desktopOathCredentialListProvider(devicePath).notifier))
.calculate(credential, headless: true);
} on CancellationException catch (_) {
return null;
}
}
String _getIcon() {
if (Platform.isMacOS) {
return 'resources/icons/systray-template.eps';
}
if (Platform.isWindows) {
return 'resources/icons/com.yubico.yubioath.ico';
}
return 'resources/icons/com.yubico.yubioath-32x32.png';
}
class _Systray extends TrayListener {
final Ref _ref;
final AppLocalizations _l10n;
int _lastClick = 0;
DevicePath _devicePath = DevicePath([]);
List<OathCredential> _credentials = [];
bool _isHidden = false;
_Systray(this._ref, this._l10n) {
_init();
}
Future<void> _init() async {
await trayManager.setIcon(_getIcon(), isTemplate: true);
if (!Platform.isLinux) {
await trayManager.setToolTip(_l10n.general_app_name);
}
await _updateContextMenu();
// Doesn't seem to work on Linux
trayManager.addListener(this);
}
void dispose() {
trayManager.destroy();
}
void _updateCredentials(Pair<DevicePath?, List<OathCredential>> pair) {
if (!listEquals(_credentials, pair.second)) {
_devicePath = pair.first ?? _devicePath;
_credentials = pair.second;
_updateContextMenu();
}
}
Future<void> _setHidden(bool hidden) async {
_isHidden = hidden;
await _updateContextMenu();
}
@override
void onTrayIconMouseDown() {
if (Platform.isMacOS) {
trayManager.popUpContextMenu();
} else {
final now = DateTime.now().millisecondsSinceEpoch;
if (now - _lastClick < 500) {
_lastClick = 0;
if (_isHidden) {
_ref.read(desktopWindowStateProvider.notifier).setWindowHidden(false);
} else {
windowManager.focus();
}
} else {
_lastClick = now;
}
}
}
@override
void onTrayIconRightMouseDown() {
trayManager.popUpContextMenu();
}
Future<void> _updateContextMenu() async {
await trayManager.setContextMenu(
Menu(
items: [
..._credentials.map(
(e) {
final label = getTextName(e);
return MenuItem(
label: label,
onClick: (_) async {
final code = await _calculateCode(_devicePath, e, _ref);
if (code != null) {
await _ref
.read(clipboardProvider)
.setText(code.value, isSensitive: true);
final notification = LocalNotification(
title: _l10n.systray_oath_copied,
body: _l10n.systray_oath_copied_to_clipboard(label),
silent: true,
);
await notification.show();
await Future.delayed(const Duration(seconds: 4));
await notification.close();
}
},
);
},
),
if (_credentials.isEmpty)
MenuItem(
label: _l10n.systray_no_pinned,
disabled: true,
),
MenuItem.separator(),
MenuItem(
label: _isHidden
? _l10n.general_show_window
: _l10n.general_hide_window,
onClick: (_) {
_ref
.read(desktopWindowStateProvider.notifier)
.setWindowHidden(!_isHidden);
},
),
MenuItem.separator(),
MenuItem(
label: _l10n.general_quit,
onClick: (_) {
_ref.read(withContextProvider)(
(context) async {
Actions.invoke(context, const CloseIntent());
},
);
}),
],
),
);
}
}

View File

@ -25,6 +25,8 @@
"oath_scanned_qr": "Scanned QR code",
"oath_scan_qr": "Scan QR code",
"oath_require_touch": "Require touch",
"oath_touch_required": "Touch Required",
"oath_touch_now": "Touch the button on your YubiKey now",
"oath_sec": "sec",
"oath_digits": "digits",
"oath_success_delete_account": "Account deleted",
@ -114,6 +116,7 @@
"mgmt_toggle_applications": "Toggle applications",
"mgmt_save": "Save",
"general_app_name": "Yubico Authenticator",
"general_about": "About",
"general_terms_of_use": "Terms of use",
"general_privacy_policy": "Privacy policy",
@ -139,6 +142,9 @@
"general_setup": "Setup",
"general_manage": "Manage",
"general_configure_yubikey": "Configure YubiKey",
"general_show_window": "Show window",
"general_hide_window": "Hide window",
"general_quit": "Quit",
"fido_press_fingerprint_begin": "Press your finger against the YubiKey to begin.",
"fido_keep_touching_yubikey": "Keep touching your YubiKey repeatedly\u2026",
@ -281,5 +287,15 @@
"placeholders": {
"version": {}
}
}
},
"systray_oath_copied": "Code copied",
"systray_oath_copied_to_clipboard": "{label} copied to clipboard.",
"@systray_oath_copied_to_clipboard" : {
"placeholders": {
"label": {}
}
},
"systray_no_pinned": "No pinned accounts"
}

View File

@ -66,7 +66,14 @@ abstract class OathCredentialListNotifier
@override
@protected
set state(List<OathPair>? value) {
super.state = value != null ? List.unmodifiable(value) : null;
super.state = value != null
? List.unmodifiable(value
..sort((a, b) {
String searchKey(OathCredential c) =>
((c.issuer ?? '') + c.name).toLowerCase();
return searchKey(a.credential).compareTo(searchKey(b.credential));
}))
: null;
}
Future<OathCode> calculate(OathCredential credential);
@ -193,11 +200,6 @@ class FilteredCredentialsNotifier extends StateNotifier<List<OathPair>> {
.toLowerCase()
.contains(query.toLowerCase()))
.where((pair) => pair.credential.issuer != '_hidden')
.toList()
..sort((a, b) {
String searchKey(OathCredential c) =>
((c.issuer ?? '') + c.name).toLowerCase();
return searchKey(a.credential).compareTo(searchKey(b.credential));
}),
.toList(),
);
}

View File

@ -156,7 +156,10 @@ class AccountDialog extends ConsumerWidget {
if (helper.code == null &&
(isDesktop || node.transport == Transport.usb)) {
Timer.run(() {
Actions.invoke(context, const CalculateIntent());
// Only call if credential hasn't been deleted/renamed
if (ref.read(credentialsProvider)?.contains(credential) == true) {
Actions.invoke(context, const CalculateIntent());
}
});
}
return FocusScope(

View File

@ -26,6 +26,7 @@ import '../../widgets/responsive_dialog.dart';
import '../models.dart';
import '../state.dart';
import '../keys.dart' as keys;
import 'utils.dart';
class DeleteAccountDialog extends ConsumerWidget {
final DeviceNode device;
@ -34,10 +35,6 @@ class DeleteAccountDialog extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final label = credential.issuer != null
? '${credential.issuer} (${credential.name})'
: credential.name;
return ResponsiveDialog(
title: Text(AppLocalizations.of(context)!.oath_delete_account),
actions: [
@ -75,7 +72,8 @@ class DeleteAccountDialog extends ConsumerWidget {
AppLocalizations.of(context)!.oath_warning_disable_this_cred,
style: Theme.of(context).textTheme.bodyLarge,
),
Text('${AppLocalizations.of(context)!.oath_account} $label'),
Text(
'${AppLocalizations.of(context)!.oath_account} ${getTextName(credential)}'),
]
.map((e) => Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),

View File

@ -126,7 +126,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
}
return Actions(
actions: {
SearchIntent: CallbackAction(onInvoke: (_) {
SearchIntent: CallbackAction<SearchIntent>(onInvoke: (_) {
searchController.selection = TextSelection(
baseOffset: 0, extentOffset: searchController.text.length);
searchFocus.requestFocus();

View File

@ -96,10 +96,6 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
Widget build(BuildContext context) {
final credential = widget.credential;
final label = credential.issuer != null
? '${credential.issuer} (${credential.name})'
: credential.name;
final remaining = getRemainingKeySpace(
oathType: credential.oathType,
period: credential.period,
@ -142,7 +138,8 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(AppLocalizations.of(context)!.oath_rename(label)),
Text(AppLocalizations.of(context)!
.oath_rename(getTextName(credential))),
Text(AppLocalizations.of(context)!
.oath_warning_will_change_account_displayed),
TextFormField(

View File

@ -47,3 +47,10 @@ Pair<int, int> getRemainingKeySpace(
remaining - issuerSpace,
);
}
/// Gets a textual name for the account, based on the issuer and name.
String getTextName(OathCredential credential) {
return credential.issuer != null
? '${credential.issuer} (${credential.name})'
: credential.name;
}

View File

@ -7,7 +7,9 @@
#include "generated_plugin_registrant.h"
#include <desktop_drop/desktop_drop_plugin.h>
#include <local_notifier/local_notifier_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
#include <tray_manager/tray_manager_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
#include <window_manager/window_manager_plugin.h>
@ -15,9 +17,15 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
g_autoptr(FlPluginRegistrar) local_notifier_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "LocalNotifierPlugin");
local_notifier_plugin_register_with_registrar(local_notifier_registrar);
g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);
g_autoptr(FlPluginRegistrar) tray_manager_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin");
tray_manager_plugin_register_with_registrar(tray_manager_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

@ -4,7 +4,9 @@
list(APPEND FLUTTER_PLUGIN_LIST
desktop_drop
local_notifier
screen_retriever
tray_manager
url_launcher_linux
window_manager
)

View File

@ -6,17 +6,21 @@ import FlutterMacOS
import Foundation
import desktop_drop
import local_notifier
import path_provider_foundation
import screen_retriever
import shared_preferences_foundation
import tray_manager
import url_launcher_macos
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
}

View File

@ -2,6 +2,8 @@ PODS:
- desktop_drop (0.0.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- local_notifier (0.1.0):
- FlutterMacOS
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
@ -10,6 +12,8 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- tray_manager (0.0.1):
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
- window_manager (0.2.0):
@ -18,9 +22,11 @@ PODS:
DEPENDENCIES:
- desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- local_notifier (from `Flutter/ephemeral/.symlinks/plugins/local_notifier/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos`)
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos`)
- tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
@ -29,12 +35,16 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos
FlutterMacOS:
:path: Flutter/ephemeral
local_notifier:
:path: Flutter/ephemeral/.symlinks/plugins/local_notifier/macos
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos
screen_retriever:
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos
tray_manager:
:path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
window_manager:
@ -43,10 +53,12 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
local_notifier: e9506bc66fc70311e8bc7291fb70f743c081e4ff
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca
url_launcher_macos: c04e4fa86382d4f94f6b38f14625708be3ae52e2
shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7

View File

@ -4,6 +4,7 @@ import FlutterMacOS
@NSApplicationMain
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
// Keep app running if window closes
return false
}
}

View File

@ -5,18 +5,18 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "569ddca58d535e601dd1584afa117710abc999d036c0cd2c51777fb257df78e8"
sha256: e440ac42679dfc04bbbefb58ed225c994bc7e07fccc8a68ec7d3631a127e5da9
url: "https://pub.dev"
source: hosted
version: "53.0.0"
version: "54.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "10927c4b7c7c88b1adbca278c3d5531db92e2f4b4abf04e2919a800af965f3f5"
sha256: "2c2e3721ee9fb36de92faa060f3480c81b23e904352b087e5c64224b1a044427"
url: "https://pub.dev"
source: hosted
version: "5.5.0"
version: "5.6.0"
archive:
dependency: "direct main"
description:
@ -69,18 +69,18 @@ packages:
dependency: transitive
description:
name: build_daemon
sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf"
sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.1.1"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
sha256: "7c35a3a7868626257d8aee47b51c26b9dba11eaddf3431117ed2744951416aab"
sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.2.0"
build_runner:
dependency: "direct dev"
description:
@ -396,6 +396,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.1"
local_notifier:
dependency: "direct main"
description:
name: local_notifier
sha256: cc855aa6362c8840e3d3b35b1c3b058a3a8becdb2b03d5a9aa3f3a1e861f0a03
url: "https://pub.dev"
source: hosted
version: "0.1.5"
logging:
dependency: "direct main"
description:
@ -420,6 +428,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.0"
menu_base:
dependency: transitive
description:
name: menu_base
sha256: "820368014a171bd1241030278e6c2617354f492f5c703d7b7d4570a6b8b84405"
url: "https://pub.dev"
source: hosted
version: "0.1.1"
meta:
dependency: transitive
description:
@ -488,18 +504,18 @@ packages:
dependency: transitive
description:
name: path_provider_linux
sha256: "2e32f1640f07caef0d3cb993680f181c79e54a3827b997d5ee221490d131fbd9"
sha256: "525ad5e07622d19447ad740b1ed5070031f7a5437f44355ae915ff56e986429a"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
version: "2.1.9"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76
sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
url: "https://pub.dev"
source: hosted
version: "2.0.5"
version: "2.0.6"
path_provider_windows:
dependency: transitive
description:
@ -528,10 +544,10 @@ packages:
dependency: transitive
description:
name: plugin_platform_interface
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
pool:
dependency: transitive
description:
@ -591,58 +607,58 @@ packages:
dependency: "direct main"
description:
name: shared_preferences
sha256: "5949029e70abe87f75cfe59d17bf5c397619c4b74a099b10116baeb34786fad9"
sha256: ee6257848f822b8481691f20c3e6d2bfee2e9eccb2a3d249907fcfb198c55b41
url: "https://pub.dev"
source: hosted
version: "2.0.17"
version: "2.0.18"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "955e9736a12ba776bdd261cf030232b30eadfcd9c79b32a3250dd4a494e8c8f7"
sha256: a51a4f9375097f94df1c6e0a49c0374440d31ab026b59d58a7e7660675879db4
url: "https://pub.dev"
source: hosted
version: "2.0.15"
version: "2.0.16"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "2b55c18636a4edc529fa5cd44c03d3f3100c00513f518c5127c951978efcccd0"
sha256: "6b84fdf06b32bb336f972d373cd38b63734f3461ba56ac2ba01b56d052796259"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: f8ea038aa6da37090093974ebdcf4397010605fd2ff65c37a66f9d28394cb874
sha256: d7fb71e6e20cd3dfffcc823a28da3539b392e53ed5fc5c2b90b55fdaa8a7e8fa
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: da9431745ede5ece47bc26d5d73a9d3c6936ef6945c101a5aca46f62e52c1cf3
sha256: "824bfd02713e37603b2bdade0842e47d56e7db32b1dcdd1cae533fb88e2913fc"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: a4b5bc37fe1b368bbc81f953197d55e12f49d0296e7e412dfe2d2d77d6929958
sha256: "6737b757e49ba93de2a233df229d0b6a87728cea1684da828cbc718b65dcf9d7"
url: "https://pub.dev"
source: hosted
version: "2.0.4"
version: "2.0.5"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "5eaf05ae77658d3521d0e993ede1af962d4b326cd2153d312df716dc250f00c9"
sha256: bd014168e8484837c39ef21065b78f305810ceabc1d4f90be6e3b392ce81b46d
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
shelf:
dependency: transitive
description:
@ -659,6 +675,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.3"
shortid:
dependency: transitive
description:
name: shortid
sha256: d0b40e3dbb50497dad107e19c54ca7de0d1a274eb9b4404991e443dadb9ebedb
url: "https://pub.dev"
source: hosted
version: "0.1.2"
sky_engine:
dependency: transitive
description: flutter
@ -760,6 +784,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
tray_manager:
dependency: "direct main"
description:
name: tray_manager
sha256: b1975a05e0c6999e983cf9a58a6a098318c896040ccebac5398a3cc9e43b9c69
url: "https://pub.dev"
source: hosted
version: "0.2.0"
typed_data:
dependency: transitive
description:
@ -772,66 +804,74 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: e8f2efc804810c0f2f5b485f49e7942179f56eabcfe81dce3387fec4bb55876b
sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e"
url: "https://pub.dev"
source: hosted
version: "6.1.9"
version: "6.1.10"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "3e2f6dfd2c7d9cd123296cab8ef66cfc2c1a13f5845f42c7a0f365690a8a7dd1"
sha256: "1f4d9ebe86f333c15d318f81dcdc08b01d45da44af74552608455ebdc08d9732"
url: "https://pub.dev"
source: hosted
version: "6.0.23"
version: "6.0.24"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "0a5af0aefdd8cf820dd739886efb1637f1f24489900204f50984634c07a54815"
sha256: c9cd648d2f7ab56968e049d4e9116f96a85517f1dd806b96a86ea1018a3a82e5
url: "https://pub.dev"
source: hosted
version: "6.1.0"
version: "6.1.1"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: "318c42cba924e18180c029be69caf0a1a710191b9ec49bb42b5998fdcccee3cc"
sha256: e29039160ab3730e42f3d811dc2a6d5f2864b90a70fb765ea60144b03307f682
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.0.3"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "41988b55570df53b3dd2a7fc90c76756a963de6a8c5f8e113330cb35992e2094"
sha256: "2dddb3291a57b074dade66b5e07e64401dd2487caefd4e9e2f467138d8c7eb06"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.0.3"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "4eae912628763eb48fc214522e58e942fd16ce195407dbf45638239523c759a6"
sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "44d79408ce9f07052095ef1f9a693c258d6373dc3944249374e30eff7219ccb0"
sha256: "574cfbe2390666003c3a1d129bdc4574aaa6728f0c00a4829a81c316de69dd9b"
url: "https://pub.dev"
source: hosted
version: "2.0.14"
version: "2.0.15"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: b6217370f8eb1fd85c8890c539f5a639a01ab209a36db82c921ebeacefc7a615
sha256: "97c9067950a0d09cbd93e2e3f0383d1403989362b97102fbf446473a48079a4b"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.4"
uuid:
dependency: transitive
description:
name: uuid
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
url: "https://pub.dev"
source: hosted
version: "3.0.7"
vector_graphics:
dependency: "direct main"
description:
@ -908,10 +948,10 @@ packages:
dependency: "direct main"
description:
name: window_manager
sha256: "5bdd29dc5f1f3185fc90696373a571d77968e03e5e820fb1ecdbdade3f5d8fff"
sha256: "492806c69879f0d28e95472bbe5e8d5940ac8c6e99cc07052fe14946974555ba"
url: "https://pub.dev"
source: hosted
version: "0.3.0"
version: "0.3.1"
xdg_directories:
dependency: transitive
description:

View File

@ -60,6 +60,8 @@ dependencies:
file_picker: ^5.2.5
archive: ^3.3.2
crypto: ^3.0.2
tray_manager: ^0.2.0
local_notifier: ^0.1.5
dev_dependencies:
integration_test:
@ -99,6 +101,7 @@ flutter:
- assets/graphics/
- assets/licenses/
- assets/licenses/raw/
- resources/icons/
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 B

View File

@ -0,0 +1,122 @@
%!PS-Adobe-3.0 EPSF-3.0
%%Creator: cairo 1.16.0 (https://cairographics.org)
%%CreationDate: Wed Feb 22 15:58:05 2023
%%Pages: 1
%%DocumentData: Clean7Bit
%%LanguageLevel: 2
%%BoundingBox: 0 0 630 663
%%EndComments
%%BeginProlog
50 dict begin
/q { gsave } bind def
/Q { grestore } bind def
/cm { 6 array astore concat } bind def
/w { setlinewidth } bind def
/J { setlinecap } bind def
/j { setlinejoin } bind def
/M { setmiterlimit } bind def
/d { setdash } bind def
/m { moveto } bind def
/l { lineto } bind def
/c { curveto } bind def
/h { closepath } bind def
/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
0 exch rlineto 0 rlineto closepath } bind def
/S { stroke } bind def
/f { fill } bind def
/f* { eofill } bind def
/n { newpath } bind def
/W { clip } bind def
/W* { eoclip } bind def
/BT { } bind def
/ET { } bind def
/BDC { mark 3 1 roll /BDC pdfmark } bind def
/EMC { mark /EMC pdfmark } bind def
/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
/Tj { show currentpoint cairo_store_point } bind def
/TJ {
{
dup
type /stringtype eq
{ show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
} forall
currentpoint cairo_store_point
} bind def
/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
/Tf { pop /cairo_font exch def /cairo_font_matrix where
{ pop cairo_selectfont } if } bind def
/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
/cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
/cairo_font where { pop cairo_selectfont } if } bind def
/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
/g { setgray } bind def
/rg { setrgbcolor } bind def
/d1 { setcachedevice } bind def
/cairo_data_source {
CairoDataIndex CairoData length lt
{ CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
{ () } ifelse
} def
/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
/cairo_image { image cairo_flush_ascii85_file } def
/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
%%EndProlog
%%BeginSetup
%%EndSetup
%%Page: 1 1
%%BeginPageSetup
%%PageBoundingBox: 0 0 630 663
%%EndPageSetup
q 0 0 630 663 rectclip
1 0 0 -1 0 663 cm q
0 g
66.832 172.32 m 66.832 150.871 66.758 129.871 66.832 108.871 c 66.98 86.07
74.18 65.598 88.355 47.82 c 108.457 22.621 134.855 7.848 166.43 2.145 c
196.281 -3.254 224.707 1.695 251.555 15.195 c 275.031 27.047 293.707 44.145
305.555 68.07 c 311.855 80.746 315.008 94.172 315.008 108.348 c 315.008
172.172 l 319.957 172.547 324.68 172.473 329.258 173.297 c 356.18 178.395
371.332 194.297 375.68 221.223 c 375.758 221.82 375.68 222.496 375.68 223.098
c 375.68 296.973 l 375.68 297.57 375.605 298.246 375.605 298.32 c 295.355
336.57 253.055 399.57 249.156 488.973 c 245.633 488.973 l 180.906 488.973
116.18 489.496 51.383 488.746 c 24.906 488.445 5.031 473.672 0.758 445.02
c 0.383 442.32 0.082 439.621 0.082 436.848 c 0.082 366.723 -0.145 296.598
0.156 226.473 c 0.23 199.547 15.383 179.895 41.633 174.121 c 48.605 172.621
55.957 172.996 63.156 172.473 c 64.281 172.395 65.332 172.395 66.832 172.32
c h
268.055 172.246 m 267.457 152.672 267.605 133.547 266.105 114.57 c 264.457
93.945 255.383 76.32 240.008 62.297 c 216.906 41.223 184.133 36.047 157.656
50.145 c 128.633 65.598 115.281 91.395 113.48 123.348 c 112.656 138.422
113.332 153.57 113.332 168.723 c 113.332 172.246 l h
215.332 391.32 m 215.332 386.52 l 215.332 365.297 215.332 344.07 215.258
322.848 c 215.258 320.52 215.781 318.945 217.656 317.445 c 226.957 310.172
231.98 300.496 233.48 288.797 c 237.23 260.445 209.93 235.621 182.18 242.672
c 165.082 247.02 153.98 258.121 150.383 275.297 c 146.781 292.473 152.332
307.02 166.281 317.973 c 168.156 319.395 168.832 320.895 168.832 323.223
c 168.758 344.445 168.758 365.672 168.758 386.895 c 168.758 391.246 l 184.582
391.32 199.656 391.32 215.332 391.32 c h
215.332 391.32 m f
453.98 311.973 m 551.555 312.871 629.18 390.348 629.18 487.547 c 629.105
585.348 550.805 663.047 453.531 662.82 c 355.805 662.598 278.332 584.371
278.48 487.246 c 278.707 389.598 356.781 312.645 453.98 311.973 c h
404.855 597.871 m 420.98 597.871 436.281 597.797 451.656 597.945 c 454.508
597.945 455.707 596.973 456.758 594.422 c 479.707 537.723 502.73 481.098
525.68 424.473 c 528.758 416.895 531.832 409.246 534.98 401.445 c 534.082
401.297 533.633 401.145 533.18 401.145 c 518.707 401.145 504.156 401.223
489.68 401.07 c 486.98 401.07 486.68 402.723 486.082 404.445 c 478.957
424.695 471.906 444.945 464.781 465.195 c 461.555 474.27 458.332 483.348
454.805 493.246 c 453.98 491.07 453.383 489.723 452.93 488.297 c 442.582
460.32 432.23 432.422 421.957 404.371 c 421.055 401.82 419.855 400.996
417.156 401.07 c 403.281 401.223 389.406 401.145 375.531 401.145 c 374.48
401.145 373.355 401.297 372.008 401.371 c 372.531 402.871 372.906 403.996
373.355 405.121 c 376.582 413.371 379.805 421.621 383.031 429.871 c 397.281
466.246 411.457 502.621 425.781 538.922 c 427.133 542.371 427.43 545.297
425.781 548.82 c 422.707 555.195 420.156 561.871 417.383 568.395 c 413.258
577.996 409.133 587.598 404.855 597.871 c h
404.855 597.871 m f
Q Q
showpage
%%Trailer
end
%%EOF

View File

@ -7,15 +7,21 @@
#include "generated_plugin_registrant.h"
#include <desktop_drop/desktop_drop_plugin.h>
#include <local_notifier/local_notifier_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
#include <tray_manager/tray_manager_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
#include <window_manager/window_manager_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
DesktopDropPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopDropPlugin"));
LocalNotifierPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalNotifierPlugin"));
ScreenRetrieverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
TrayManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("TrayManagerPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
WindowManagerPluginRegisterWithRegistrar(

View File

@ -4,7 +4,9 @@
list(APPEND FLUTTER_PLUGIN_LIST
desktop_drop
local_notifier
screen_retriever
tray_manager
url_launcher_windows
window_manager
)

View File

@ -172,7 +172,9 @@ bool Win32Window::Create(const std::wstring& title,
}
bool Win32Window::Show() {
return ShowWindow(window_handle_, SW_SHOWNORMAL);
// We show the mindow manually
return true;
//return ShowWindow(window_handle_, SW_SHOWNORMAL);
}
// static