mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-22 17:51:29 +03:00
Check for existing instance on startup using sockets.
This commit is contained in:
parent
cedc86b03e
commit
589b9fecc4
79
lib/desktop/single_instance.dart
Executable file
79
lib/desktop/single_instance.dart
Executable file
@ -0,0 +1,79 @@
|
||||
import 'dart:io';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
const _lockFileName = 'lockfile';
|
||||
const _pingMessage = 'YA-PING';
|
||||
const _pongMessage = 'YA-PONG';
|
||||
|
||||
final log = Logger('single_instance');
|
||||
|
||||
void _startServer(File lockfile) async {
|
||||
ServerSocket socket;
|
||||
if (Platform.isWindows) {
|
||||
socket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
|
||||
lockfile.writeAsString('${socket.port}');
|
||||
} else {
|
||||
socket = await ServerSocket.bind(
|
||||
InternetAddress(lockfile.path, type: InternetAddressType.unix), 0);
|
||||
}
|
||||
|
||||
log.info('Lock file and socket created.');
|
||||
socket.listen((client) {
|
||||
client.listen((data) async {
|
||||
final message = String.fromCharCodes(data);
|
||||
if (message == _pingMessage) {
|
||||
log.info('Got incomming connection');
|
||||
|
||||
if (!await WindowManager.instance.isMinimized()) {
|
||||
await WindowManager.instance.setAlwaysOnTop(true);
|
||||
await WindowManager.instance.setAlwaysOnTop(false);
|
||||
} else {
|
||||
await WindowManager.instance.restore();
|
||||
}
|
||||
|
||||
// This doesn't seem to always work
|
||||
await WindowManager.instance.focus();
|
||||
client.write(_pongMessage);
|
||||
}
|
||||
client.close();
|
||||
}, cancelOnError: true);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> ensureSingleInstance() async {
|
||||
final appSupport = await getApplicationSupportDirectory();
|
||||
final lockfile = File(path.join(appSupport.path, _lockFileName));
|
||||
log.info('Lock file: $lockfile');
|
||||
|
||||
if (await lockfile.exists()) {
|
||||
try {
|
||||
Socket client;
|
||||
if (Platform.isWindows) {
|
||||
final port = int.parse(await lockfile.readAsString());
|
||||
client = await Socket.connect(InternetAddress.loopbackIPv4, port);
|
||||
} else {
|
||||
client = await Socket.connect(
|
||||
InternetAddress(lockfile.path, type: InternetAddressType.unix), 0);
|
||||
}
|
||||
client.write(_pingMessage);
|
||||
await client.flush();
|
||||
client.listen((data) async {
|
||||
final message = String.fromCharCodes(data);
|
||||
await client.close();
|
||||
if (message == _pongMessage) {
|
||||
log.info('Other application instance already running, exit.');
|
||||
exit(0);
|
||||
}
|
||||
}, cancelOnError: true);
|
||||
} on Exception {
|
||||
await lockfile.delete();
|
||||
_startServer(lockfile);
|
||||
}
|
||||
} else {
|
||||
_startServer(lockfile);
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:yubico_authenticator/desktop/single_instance.dart';
|
||||
|
||||
import 'app/app.dart';
|
||||
import 'app/views/main_page.dart';
|
||||
@ -17,7 +18,11 @@ import 'error_page.dart';
|
||||
final log = Logger('main');
|
||||
|
||||
void main() async {
|
||||
_initLogging(Level.INFO);
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
log.info('Ensuring single instance...');
|
||||
await ensureSingleInstance();
|
||||
await windowManager.ensureInitialized();
|
||||
|
||||
// Either use the _YKMAN_EXE environment variable, or look relative to executable.
|
||||
@ -43,8 +48,8 @@ void main() async {
|
||||
try {
|
||||
var rpc = await RpcSession.launch(exe!);
|
||||
// Enable logging TODO: Make this configurable
|
||||
_initLogging(Level.INFO, rpc);
|
||||
log.info('ykman process started', exe);
|
||||
rpc.setLogLevel(Logger.root.level);
|
||||
overrides.add(rpcProvider.overrideWithValue(rpc));
|
||||
page = const MainPage();
|
||||
} catch (e) {
|
||||
@ -52,8 +57,8 @@ void main() async {
|
||||
page = ErrorPage(error: e.toString());
|
||||
}
|
||||
|
||||
// Only MacOS supports hiding the window at start currently.
|
||||
// For now, this size should match windows/runner/main.cpp and linux/flutter/my_application.cc
|
||||
// Linux doesn't currently support hiding the window at start currently.
|
||||
// For now, this size should match linux/flutter/my_application.cc to avoid window flicker at startup.
|
||||
windowManager.waitUntilReadyToShow().then((_) async {
|
||||
await windowManager.setSize(const Size(400, 720));
|
||||
windowManager.show();
|
||||
@ -65,7 +70,7 @@ void main() async {
|
||||
));
|
||||
}
|
||||
|
||||
void _initLogging(Level level, RpcSession rpc) {
|
||||
void _initLogging(Level level) {
|
||||
//TODO: Add support for logging to stderr and file
|
||||
Logger.root.onRecord.listen((record) {
|
||||
developer.log(
|
||||
@ -78,7 +83,6 @@ void _initLogging(Level level, RpcSession rpc) {
|
||||
});
|
||||
|
||||
Logger.root.level = level;
|
||||
rpc.setLogLevel(level);
|
||||
}
|
||||
|
||||
//TODO: Remove below this
|
||||
|
@ -5,10 +5,12 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import path_provider_macos
|
||||
import shared_preferences_macos
|
||||
import window_manager
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
|
||||
}
|
||||
|
28
pubspec.lock
28
pubspec.lock
@ -352,6 +352,27 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.11"
|
||||
path_provider_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_ios
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -359,6 +380,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -37,6 +37,7 @@ dependencies:
|
||||
|
||||
async: ^2.8.2
|
||||
logging: ^1.0.2
|
||||
path_provider: ^2.0.8
|
||||
shared_preferences: ^2.0.12
|
||||
flutter_riverpod: ^1.0.0
|
||||
json_annotation: ^4.4.0
|
||||
|
@ -117,7 +117,8 @@ bool Win32Window::CreateAndShow(const std::wstring& title,
|
||||
double scale_factor = dpi / 96.0;
|
||||
|
||||
HWND window = CreateWindow(
|
||||
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
|
||||
//original line: window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
|
||||
window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
|
||||
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
|
||||
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
|
||||
nullptr, nullptr, GetModuleHandle(nullptr), this);
|
||||
|
Loading…
Reference in New Issue
Block a user