|
|
|
using System;
|
|
|
|
using System.Windows;
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
using System.IO;
|
|
|
|
using System.Threading;
|
|
|
|
|
|
|
|
namespace smx_config
|
|
|
|
{
|
|
|
|
public partial class App: Application
|
|
|
|
{
|
|
|
|
[DllImport("SMX.dll", CallingConvention = CallingConvention.Cdecl)]
|
|
|
|
private static extern void SMX_Internal_OpenConsole();
|
|
|
|
|
|
|
|
private System.Windows.Forms.NotifyIcon trayIcon;
|
|
|
|
private MainWindow window;
|
|
|
|
|
|
|
|
App()
|
|
|
|
{
|
|
|
|
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionEventHandler;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void OnStartup(StartupEventArgs e)
|
|
|
|
{
|
|
|
|
base.OnStartup(e);
|
|
|
|
|
|
|
|
// If an instance is already running, foreground it and exit.
|
|
|
|
if(ForegroundExistingInstance())
|
|
|
|
{
|
|
|
|
Shutdown();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is used by the installer to close a running instance automatically when updating.
|
|
|
|
ListenForShutdownRequest();
|
|
|
|
|
|
|
|
// If we're being launched on startup, but the LaunchOnStartup setting is false,
|
|
|
|
// then the user turned off auto-launching but we're still being launched for some
|
|
|
|
// reason (eg. a renamed launch shortcut that we couldn't find to remove). As
|
|
|
|
// a safety so we don't launch when the user doesn't want us to, just exit in this
|
|
|
|
// case.
|
|
|
|
if(Helpers.LaunchedOnStartup() && !LaunchOnStartup.Enable)
|
|
|
|
{
|
|
|
|
Shutdown();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
LaunchOnStartup.Enable = true;
|
|
|
|
if(!SMX.SMX.DLLExists())
|
|
|
|
{
|
|
|
|
MessageBox.Show("SMXConfig encountered an unexpected error.\n\nSMX.dll couldn't be found:\n\n" + Helpers.GetLastWin32ErrorString(), "SMXConfig");
|
|
|
|
Current.Shutdown();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!SMX.SMX.DLLAvailable())
|
|
|
|
{
|
|
|
|
MessageBox.Show("SMXConfig encountered an unexpected error.\n\nSMX.dll failed to load:\n\n" + Helpers.GetLastWin32ErrorString(), "SMXConfig");
|
|
|
|
Current.Shutdown();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(Helpers.GetDebug())
|
|
|
|
SMX_Internal_OpenConsole();
|
|
|
|
|
|
|
|
CurrentSMXDevice.singleton = new CurrentSMXDevice();
|
|
|
|
|
|
|
|
// Load animations.
|
|
|
|
Helpers.LoadSavedPanelAnimations();
|
|
|
|
|
|
|
|
CreateTrayIcon();
|
|
|
|
|
|
|
|
// Create the main window.
|
|
|
|
if(!Helpers.LaunchedOnStartup())
|
|
|
|
ToggleMainWindow();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open or close the main window.
|
|
|
|
//
|
|
|
|
// We don't create our UI until the first time it's opened, so we use
|
|
|
|
// less memory when we're launched on startup. However, when we're minimized
|
|
|
|
// back to the tray, we don't destroy the main window. WPF is just too
|
|
|
|
// leaky to recreate the main window each time it's called due to internal
|
|
|
|
// circular references. Instead, we just focus on minimizing CPU overhead.
|
|
|
|
void ToggleMainWindow()
|
|
|
|
{
|
|
|
|
if(window == null)
|
|
|
|
{
|
|
|
|
window = new MainWindow();
|
|
|
|
window.Closed += MainWindowClosed;
|
|
|
|
window.Show();
|
|
|
|
}
|
|
|
|
else if(IsMinimizedToTray())
|
|
|
|
{
|
|
|
|
window.Visibility = Visibility.Visible;
|
|
|
|
window.Activate();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
MinimizeToTray();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool IsMinimizedToTray()
|
|
|
|
{
|
|
|
|
return window.Visibility == Visibility.Collapsed;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void MinimizeToTray()
|
|
|
|
{
|
|
|
|
// Just hide the window. Don't actually set the window to minimized, since it
|
|
|
|
// won't do anything and it causes problems when restoring the window.
|
|
|
|
window.Visibility = Visibility.Collapsed;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void BringToForeground()
|
|
|
|
{
|
|
|
|
// Restore or create the window. Don't minimize if we're already restored.
|
|
|
|
if(window == null || IsMinimizedToTray())
|
|
|
|
ToggleMainWindow();
|
|
|
|
|
|
|
|
// Focus the window.
|
|
|
|
window.WindowState = WindowState.Normal;
|
|
|
|
window.Activate();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void MainWindowClosed(object sender, EventArgs e)
|
|
|
|
{
|
|
|
|
window = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void UnhandledExceptionEventHandler(object sender, UnhandledExceptionEventArgs e)
|
|
|
|
{
|
|
|
|
string message = e.ExceptionObject.ToString();
|
|
|
|
MessageBox.Show("SMXConfig encountered an unexpected error:\n\n" + message, "SMXConfig");
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void OnExit(ExitEventArgs e)
|
|
|
|
{
|
|
|
|
base.OnExit(e);
|
|
|
|
|
|
|
|
Console.WriteLine("Application exiting");
|
|
|
|
|
|
|
|
// Remove the tray icon.
|
|
|
|
if(trayIcon != null)
|
|
|
|
{
|
|
|
|
trayIcon.Visible = false;
|
|
|
|
trayIcon = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shut down cleanly, to make sure we don't run any threaded callbacks during shutdown.
|
|
|
|
if(CurrentSMXDevice.singleton != null)
|
|
|
|
{
|
|
|
|
CurrentSMXDevice.singleton.Shutdown();
|
|
|
|
CurrentSMXDevice.singleton = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If another instance other than this one is running, send it WM_USER to tell it to
|
|
|
|
// foreground itself. Return true if another instance was found.
|
|
|
|
private bool ForegroundExistingInstance()
|
|
|
|
{
|
|
|
|
bool createdNew = false;
|
|
|
|
EventWaitHandle SMXConfigEvent = new EventWaitHandle(false, EventResetMode.AutoReset, "SMXConfigEvent", out createdNew);
|
|
|
|
if(!createdNew)
|
|
|
|
{
|
|
|
|
// Signal the event to foreground the existing instance.
|
|
|
|
SMXConfigEvent.Set();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ThreadPool.RegisterWaitForSingleObject(SMXConfigEvent, ForegroundApplicationCallback, this, Timeout.Infinite, false);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void ForegroundApplicationCallback(Object self, Boolean timedOut)
|
|
|
|
{
|
|
|
|
// This is called when another instance sends us a message over SMXConfigEvent.
|
|
|
|
Application.Current.Dispatcher.Invoke(new Action(() => {
|
|
|
|
App application = (App) Application.Current;
|
|
|
|
application.BringToForeground();
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
private void ListenForShutdownRequest()
|
|
|
|
{
|
|
|
|
// We've already checked that we're the only instance when we get here, so this event shouldn't
|
|
|
|
// exist. If it already exists for some reason, we'll listen to it anyway.
|
|
|
|
EventWaitHandle SMXConfigShutdown = new EventWaitHandle(false, EventResetMode.AutoReset, "SMXConfigShutdown");
|
|
|
|
ThreadPool.RegisterWaitForSingleObject(SMXConfigShutdown, ShutdownApplicationCallback, this, Timeout.Infinite, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void ShutdownApplicationCallback(Object self, Boolean timedOut)
|
|
|
|
{
|
|
|
|
// This is called when another instance sends us a message over SMXConfigShutdown.
|
|
|
|
Application.Current.Dispatcher.Invoke(new Action(() => {
|
|
|
|
App application = (App) Application.Current;
|
|
|
|
application.Shutdown();
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a tray icon. For some reason there's no WPF interface for this,
|
|
|
|
// so we have to use Forms.
|
|
|
|
void CreateTrayIcon()
|
|
|
|
{
|
|
|
|
Stream iconStream = GetResourceStream(new Uri( "pack://application:,,,/Resources/window%20icon%20grey.ico")).Stream;
|
|
|
|
System.Drawing.Icon icon = new System.Drawing.Icon(iconStream);
|
|
|
|
|
|
|
|
trayIcon = new System.Windows.Forms.NotifyIcon();
|
|
|
|
trayIcon.Text = "StepManiaX";
|
|
|
|
trayIcon.Visible = true;
|
|
|
|
|
|
|
|
// Show or hide the application window on click.
|
|
|
|
trayIcon.Click += delegate (object sender, EventArgs e) { ToggleMainWindow(); };
|
|
|
|
trayIcon.DoubleClick += delegate (object sender, EventArgs e) { ToggleMainWindow(); };
|
|
|
|
|
|
|
|
CurrentSMXDevice.singleton.ConfigurationChanged += delegate(LoadFromConfigDelegateArgs args) {
|
|
|
|
RefreshTrayIcon(args);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Do the initial refresh.
|
|
|
|
RefreshTrayIcon(CurrentSMXDevice.singleton.GetState(), true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Refresh the tray icon when we're connected or disconnected.
|
|
|
|
bool wasConnected;
|
|
|
|
void RefreshTrayIcon(LoadFromConfigDelegateArgs args, bool force=false)
|
|
|
|
{
|
|
|
|
if(trayIcon == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
bool EitherControllerConnected = false;
|
|
|
|
for(int pad = 0; pad < 2; ++pad)
|
|
|
|
if(args.controller[pad].info.connected)
|
|
|
|
EitherControllerConnected = true;
|
|
|
|
|
|
|
|
// Skip the refresh if the connected state didn't change.
|
|
|
|
if(wasConnected == EitherControllerConnected && !force)
|
|
|
|
return;
|
|
|
|
wasConnected = EitherControllerConnected;
|
|
|
|
|
|
|
|
trayIcon.Text = EitherControllerConnected? "StepManiaX (connected)":"StepManiaX (disconnected)";
|
|
|
|
|
|
|
|
// Set the tray icon.
|
|
|
|
string filename = EitherControllerConnected? "window%20icon.ico":"window%20icon%20grey.ico";
|
|
|
|
Stream iconStream = GetResourceStream(new Uri( "pack://application:,,,/Resources/" + filename)).Stream;
|
|
|
|
trayIcon.Icon = new System.Drawing.Icon(iconStream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|