Skip to content

Fixes #4139. Application.Run<T> isn't initializing properly by setting the Application.ForceDriver property #4142

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

Merged
merged 10 commits into from
Jun 12, 2025

Conversation

BDisp
Copy link
Collaborator

@BDisp BDisp commented Jun 10, 2025

Fixes

Proposed Changes/Todos

  • Add WSL: UICatalog --driver v2net to the launchSettings.json
  • Checking for v2 drivers in the InternalInit method
  • Using ForceDriver if the driverName is null
  • Ensuring the running the correct app Instance
  • Reset _lazyInstance on Shutdown
  • Avoiding run console functions when running unit tests

Pull Request checklist:

  • I've named my PR in the form of "Fixes #issue. Terse description."
  • My code follows the style guidelines of Terminal.Gui - if you use Visual Studio, hit CTRL-K-D to automatically reformat your files before committing.
  • My code follows the Terminal.Gui library design guidelines
  • I ran dotnet test before commit
  • I have made corresponding changes to the API documentation (using /// style comments)
  • My changes generate no new warnings
  • I have checked my code and corrected any poor grammar or misspellings
  • I conducted basic QA to assure all features are working

…setting the Application.ForceDriver property
@BDisp BDisp requested a review from tig as a code owner June 10, 2025 22:58
@tig
Copy link
Collaborator

tig commented Jun 11, 2025

Did you consider this?

ConfigurationManager.RuntimeConfig = """
                                     {
                                         "ForceDriver": "v2net"
                                     }
                                     """;

ConfigurationManager.Enable (ConfigLocations.Runtime);
Application.Run<MyWindow>();

@BDisp
Copy link
Collaborator Author

BDisp commented Jun 11, 2025

Did you consider this?

ConfigurationManager.RuntimeConfig = """
                                     {
                                         "ForceDriver": "v2net"
                                     }
                                     """;

ConfigurationManager.Enable (ConfigLocations.Runtime);
Application.Run<MyWindow>();

This is a good solution for a real app but in my case I using a launchSettings.json file. Please see my #4134 (comment). Thanks.

@tig
Copy link
Collaborator

tig commented Jun 11, 2025

Did you consider this?

ConfigurationManager.RuntimeConfig = """
                                     {
                                         "ForceDriver": "v2net"
                                     }
                                     """;

ConfigurationManager.Enable (ConfigLocations.Runtime);
Application.Run<MyWindow>();

This is a good solution for a real app but in my case I using a launchSettings.json file. Please see my #4134 (comment). Thanks.

If you want to temporarily force all apps on your machine to use a particular driver while testing things, simply put a config.json containing the following in your ~/.tui directory.

{
   "ForceDriver": "v2net"
}

Or, you could put this in .\Terminal.Gui\Examples\Example\bin\Debug\net8.0\.tui\config.json per-app.

@BDisp
Copy link
Collaborator Author

BDisp commented Jun 11, 2025

Thanks. The correct configuration is like bellow. With this PR works well but with the v2_develop branch I get the error bellow. I'm still working to fix the unit tests failures.

{
   "Application.ForceDriver": "v2net"
}

System.ArgumentException: 'Invalid driver name: v2net. Valid names are CursesDriver, FakeDriver, NetDriver, ConsoleDriverFacade1, WindowsDriver'`

@tig
Copy link
Collaborator

tig commented Jun 11, 2025

Valid names are

You should be able to fix this easily:

    public static List<Type?> GetDriverTypes ()
    {
        // use reflection to get the list of drivers
        List<Type?> driverTypes = new ();

        foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies ())
        {
            foreach (Type? type in asm.GetTypes ())
            {
                if (typeof (IConsoleDriver).IsAssignableFrom (type) && !type.IsAbstract && type.IsClass)
                {
                    driverTypes.Add (type);
                }
            }
        }

        return driverTypes;
    }

@tig
Copy link
Collaborator

tig commented Jun 11, 2025

... and if you do fix that, this code can be cleaned up in UI Catalog:

        Option<string> driverOption = new Option<string> ("--driver", "The IConsoleDriver to use.").FromAmong (
             Application.GetDriverTypes ()
                        .Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
                        .Select (d => d!.Name)
                        .Union (["v2", "v2win", "v2net"])
                        .ToArray ()
            );

@BDisp
Copy link
Collaborator Author

BDisp commented Jun 11, 2025

I had already thought the same think but remember the v2 drivers run his own Init and Run methods. With this PR implementation I don't need to add extra code to the UICatalog.

@tig
Copy link
Collaborator

tig commented Jun 11, 2025

                        .Union (["v2", "v2win", "v2net"])

I think you are overthinking this.

The bug here is that when we added v2win/net we hacked UI Catalog to work around the fact that GetDriverTypes didn't support them. The above code is a hack.

If you fix GetDriverTypes to correctly return v2win and v2net then everything will work, and the hack code in UI Catalog can be removed.

Of course, this needs to be done too:

image

@BDisp
Copy link
Collaborator Author

BDisp commented Jun 11, 2025

What I did meant is that the code that follow the driver check shouldn't be used if the driver is v2 type. The v2 already have it own Init and Run implementations. Your code is fine but I need to fine tunning and flow the way it will be run. Of course that the hack code in the UICatalog can be removed and it will. If I'm wrong I beg sorry.

        if (Driver is null)
        {
            PlatformID p = Environment.OSVersion.Platform;

            if (string.IsNullOrEmpty (ForceDriver))
            {
                if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
                {
                    Driver = new WindowsDriver ();
                }
                else
                {
                    Driver = new CursesDriver ();
                }
            }
            else
            {
                List<Type?> drivers = GetDriverTypes ();
                Type? driverType = drivers.FirstOrDefault (t => t!.Name.Equals (ForceDriver, StringComparison.InvariantCultureIgnoreCase));

                if (driverType is { })
                {
                    Driver = (IConsoleDriver)Activator.CreateInstance (driverType)!;
                }
                else
                {
                    throw new ArgumentException (
                                                 $"Invalid driver name: {ForceDriver}. Valid names are {string.Join (", ", drivers.Select (t => t!.Name))}"
                                                );
                }
            }
        }

        try
        {
            MainLoop = Driver!.Init ();
            SubscribeDriverEvents ();
        }
        catch (InvalidOperationException ex)
        {
            // This is a case where the driver is unable to initialize the console.
            // This can happen if the console is already in use by another process or
            // if running in unit tests.
            // In this case, we want to throw a more specific exception.
            throw new InvalidOperationException (
                                                 "Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.",
                                                 ex
                                                );
        }

        SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());

        // TODO: This is probably not needed
        if (Popover.GetActivePopover () is View popover)
        {
            popover.Visible = false;
        }

        MainThreadId = Thread.CurrentThread.ManagedThreadId;
        bool init = Initialized = true;
        InitializedChanged?.Invoke (null, new (init));
    }

@tig
Copy link
Collaborator

tig commented Jun 11, 2025

I just dove into this a little and I was incorrect on the simplest fix. It's def more complicated than I thought because v2win/net are not fully integrated.

I'm not sure how to fully fix this, but I suggest you start by making UICatalog.cs more correct:

public class UICatalog
{
-    private static string _forceDriver = string.Empty;
+     private static string? _forceDriver = null;

...

    private static void UICatalogMain (UICatalogCommandLineOptions options)
    {
        // By setting _forceDriver we ensure that if the user has specified a driver on the command line, it will be used
        // regardless of what's in a config file.
-        Application.ForceDriver = (_forceDriver = (string.IsNullOrEmpty(options.Driver) ? null : options.Driver))!;
+        Application.ForceDriver = _forceDriver = options.Driver;

Thanks.

@BDisp BDisp marked this pull request as draft June 11, 2025 17:28
@BDisp
Copy link
Collaborator Author

BDisp commented Jun 11, 2025

Changing the Application.GetDriverTypes method doesn't return what you want. We can get only one more type, the generic ConsoleDriverFacade1but notv2winorv2net`. I think that the alternative in the 85d3569 is enough for now.

@BDisp BDisp marked this pull request as ready for review June 11, 2025 22:23
@tig
Copy link
Collaborator

tig commented Jun 12, 2025

Looks nice. Ready?

@BDisp BDisp marked this pull request as draft June 12, 2025 13:18
@BDisp
Copy link
Collaborator Author

BDisp commented Jun 12, 2025

Looks nice. Ready?

Thanks. I think I can do a little better by removing the additional code in the UICatalog and change GetDriverTypes to return a List<string?>. Just a few minutes, please.

@BDisp BDisp marked this pull request as ready for review June 12, 2025 14:48
@BDisp
Copy link
Collaborator Author

BDisp commented Jun 12, 2025

I've finished this now. The UICatalog only have to call the GetDriverTypes without doing any hacking.

@tig tig merged commit ad1de25 into gui-cs:v2_develop Jun 12, 2025
11 checks passed
@BDisp BDisp deleted the v2_4139_application-run-t-fix branch June 12, 2025 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Application.Run<T> isn't initializing properly by setting the Application.ForceDriver property
2 participants