flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
166.27k stars 27.51k forks source link

[desktop] Easy way to prevent running multiple instances of the flutter application #90889

Open insinfo opened 3 years ago

insinfo commented 3 years ago

Ideally, there should be a way to prevent multiple instances of an application from running, perhaps an API that abstracts Mutex from the underlying operating system.

I'm developing a desktop app that depends on this feature.

insinfo commented 3 years ago

I'm currently doing this, see the code below, but the ideal is to have a way to do this in main.dart, because I want to run a certain background task if the program is already running.

code snippet ```cpp int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t *command_line, _In_ int show_command) { //add this lines for prevent multiple instances of application HANDLE hMutexHandle = CreateMutex(NULL, TRUE, L"com.mutex.fsbackup"); HWND handle=FindWindowA(NULL, "fsbackup"); if (GetLastError() == ERROR_ALREADY_EXISTS) { WINDOWPLACEMENT place = { sizeof(WINDOWPLACEMENT) }; GetWindowPlacement(handle, &place); switch(place.showCmd) { case SW_SHOWMAXIMIZED: ShowWindow(handle, SW_SHOWMAXIMIZED); break; case SW_SHOWMINIMIZED: ShowWindow(handle, SW_RESTORE); break; default: ShowWindow(handle, SW_NORMAL); break; } SetWindowPos(0, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE); SetForegroundWindow(handle); // Program already running somewhere return(1); // Exit program } // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); flutter::DartProject project(L"data"); std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"fsbackup", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } ::CoUninitialize(); //add this lines for prevent multiple instances of application // Upon app closing: ReleaseMutex( hMutexHandle ); // Explicitly release mutex CloseHandle( hMutexHandle ); // close handle before terminating return EXIT_SUCCESS; } ``````
TahaTesser commented 3 years ago

Hi @insinfo Can you please explain this proposal with some use cases? What is the current limitation? Define "multiple instances" with example or screenshot? Thank you

insinfo commented 3 years ago

I'm developing an application for windows that backsup servers via SCP/SFTP, this application will run on a server with windows server or windows 10 , it's vital that multiple instances are not run on the same server to avoid data race, attempts to back up the same directory at the same time. There are many cases where you do not want to have multiple instances of an application running, such as: antivirus applications, video and image (Photoshop and Premiere for example doesn't run multiple instances) editing applications that open multiple files or projects in different tabs, text editing applications that open multiple files in tabs rather than multiple instances, to a range of application types that for a variety of reasons it is often desirable to limit the number of running instances of a program to exactly one on a given computer. Mainly for applications dealing with the file system like antivirus applications and backup systems.

This article below is very enlightening on this subject: http://www.flounder.com/nomultiples.htm

Other references: https://stackoverflow.com/questions/67233239/run-only-single-instance-of-flutter-desktop-application

https://stackoverflow.com/questions/93989/prevent-multiple-instances-of-a-given-app-in-net https://stackoverflow.com/questions/8799646/preventing-multiple-instances-of-my-application https://social.msdn.microsoft.com/Forums/en-US/664845e5-d77c-4607-bd8f-403117d35c05/whats-the-best-way-to-prevent-multiple-instances-of-an-app-from-running-at-the-same-time?forum=windowsmobiledev https://knowledgebase.progress.com/articles/Article/P6956 https://www.addictivetips.com/windows-tips/disable-multiple-instances-of-an-app-on-windows-10/

In my specific use case, I need to prevent the execution of multiple instances of my backup app, my app will be called several times by the windows task scheduler, because in my app I will have the option to schedule the execution of a certain backup. At the moment I'm doing the following, when I run the app, in the main method I check if there is already the "fsbackup.exe" process running, if it's already running it notifies the running instance through a named pipe so that it runs backups scheduled for that moment and closes, if not running normally runs the app and starts the named pipe server. The problem with this approach is that since the application window is not created on the dart side, but rather on the c++ side, every time I open the app for the second time it opens and closes the window, this visually is very desegable.

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:fsbackup/app_injector.dart';
import 'package:fsbackup/constants.dart';

import 'package:fsbackup/screens/main/main_screen.dart';
import 'package:fsbackup/shared/utils/process_helper.dart';

// ignore: import_of_legacy_library_into_null_safe
import 'package:google_fonts/google_fonts.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:os/file_system.dart';

var pipe = NamedPipeWindows('fsbackup2');
void main() {

  var countRunning = ProcessHelper.countProcessInstance('fsbackup.exe');
  if (countRunning > 1) {
    notifyOpenInstance();
    exit(1);
  }
  //startPipeServer();
  notifyOpenInstance();
  runApp(MyApp());
}
//notify instance running
void notifyOpenInstance() {
  var w = pipe.openWrite();
  w.connect();
  w.addString('run backups');
  w.close();
  print('notifyInstace');
}

void startPipeServer() {}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        localizationsDelegates: [
          AppLocalizations.delegate, // Add this line
          GlobalMaterialLocalizations.delegate,
          GlobalWidgetsLocalizations.delegate,
          GlobalCupertinoLocalizations.delegate,
        ],
        supportedLocales: [
          const Locale('en', ''), // English, no country code
          const Locale('es', ''), // Spanish, no country code
          const Locale('pt', ''),
          // ... other locales the app supports
        ],
        debugShowCheckedModeBanner: false,
        title: 'FSBackup',
        theme: ThemeData.dark().copyWith(
          scaffoldBackgroundColor: bgColor,
          textTheme: GoogleFonts.poppinsTextTheme(Theme.of(context).textTheme).apply(bodyColor: Colors.white),
          canvasColor: secondaryColor,
        ),
        home: FutureBuilder(
          //inicializa o injetor de dependencias
          future: appInjector(),
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return MainScreen();
            }
            return Scaffold(backgroundColor: bgColor, body: Center(child: CircularProgressIndicator()));
          },
        ));
  }
}
TahaTesser commented 3 years ago

Hi @insinfo Thanks for the details, I also found this. Your proposal to have this support in the dart code and make it easier to enable this

darshankawar commented 5 months ago

Redirecting this to team-tool. @insinfo Maybe you can check this community package to see if it helps serve your use case and proposal https://pub.dev/packages/flutter_single_instance

amedwardson commented 4 months ago

Is there any update on this? I'm facing a similar issue. I found this package which does stop a second instance of my windows app from opening but I would like to be able to interact with the running instance of the app, ideally from within main.dart as @insinfo suggested

robert-ancell commented 4 months ago

On Linux this is normally done by owning a D-Bus name on the session bus - if the name can't be claimed then that application is already running. The Linux application contains the following in my_application.cc:

MyApplication* my_application_new() {
  return MY_APPLICATION(g_object_new(my_application_get_type(),
                                     "application-id", APPLICATION_ID,
                                     "flags", G_APPLICATION_NON_UNIQUE,
                                     nullptr));
}

If the G_APPLICATION_NON_UNIQUE flag is removed then the app will have the desired behaviour. Should there be a Flutter engine flag or a suitable channel then this could be determined in Dart code.

lavishmanghnani2 commented 3 months ago

Is there any solution for this? I am trying to run a single instance of my Flutter Windows desktop app. It runs successfully with the flutter_single_instance package (version 0.0.1), but the issue arises when I hide the window. When the user clicks on the app icon, the hidden instance does not open. Currently, I am using the tray_manager package (version 0.2.3). When the user clicks on the tray icon, the app opens, but it does not open from the app icon. Is there any way to fix this?

lavishmanghnani2 commented 3 months ago

Is there any solution for this? I am trying to run a single instance of my Flutter Windows desktop app. It runs successfully with the flutter_single_instance package (version 0.0.1), but the issue arises when I hide the window. When the user clicks on the app icon, the hidden instance does not open. Currently, I am using the tray_manager package (version 0.2.3). When the user clicks on the tray icon, the app opens, but it does not open from the app icon. Is there any way to fix this?

@robert-ancell @cbracken @Hixie @loic-sharma @gspencergoog guys if you know anything about that please help

VarunDev7777 commented 6 days ago

is there any update on this. Or do i have to use single_instance package only?

robert-ancell commented 5 days ago

There is no standard Flutter API for doing this, so using the single_instance package or modifying the runner source (as shown above) are the two available methods.