OSDN Git Service

Win32ApiクラスをNativeMethodsクラスに名前変更 (CA1060)
[opentween/open-tween.git] / OpenTween / ApplicationEvents.cs
index 4e10e56..8af9147 100644 (file)
 // Boston, MA 02110-1301, USA.
 
 using System;
+using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using System.Diagnostics;
 using System.Windows.Forms;
+using System.Text.RegularExpressions;
 using System.Threading;
+using System.Threading.Tasks;
 using System.Globalization;
 using System.Reflection;
+using Microsoft.Win32;
 
 namespace OpenTween
 {
     internal class MyApplication
     {
         /// <summary>
+        /// 起動時に指定されたオプションを取得します
+        /// </summary>
+        public static IDictionary<string, string> StartupOptions { get; private set; }
+
+        /// <summary>
         /// アプリケーションのメイン エントリ ポイントです。
         /// </summary>
         [STAThread]
-        static int Main()
+        static int Main(string[] args)
         {
-            CheckSettingFilePath();
+            if (!CheckRuntimeVersion())
+            {
+                var message = string.Format(Properties.Resources.CheckRuntimeVersion_Error, ".NET Framework 4.5.1");
+                MessageBox.Show(message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
+                return 1;
+            }
+
+            StartupOptions = ParseArguments(args);
+
+            if (!SetConfigDirectoryPath())
+                return 1;
+
             InitCulture();
 
-            string pt = Application.ExecutablePath.Replace("\\", "/") + "/" + Application.ProductName;
+            // 同じ設定ファイルを使用する OpenTween プロセスの二重起動を防止する
+            string pt = MyCommon.settingPath.Replace("\\", "/") + "/" + Application.ProductName;
             using (Mutex mt = new Mutex(false, pt))
             {
                 if (!mt.WaitOne(0, false))
                 {
-                    ShowPreviousWindow();
+                    var text = string.Format(MyCommon.ReplaceAppName(Properties.Resources.StartupText1), MyCommon.GetAssemblyName());
+                    MessageBox.Show(text, MyCommon.ReplaceAppName(Properties.Resources.StartupText2), MessageBoxButtons.OK, MessageBoxIcon.Information);
+
+                    TryActivatePreviousWindow();
                     return 1;
                 }
 
-                Application.ThreadException += MyApplication_UnhandledException;
+                TaskScheduler.UnobservedTaskException += (s, e) =>
+                {
+                    e.SetObserved();
+                    OnUnhandledException(e.Exception.Flatten());
+                };
+                Application.ThreadException += (s, e) => OnUnhandledException(e.Exception);
+                AppDomain.CurrentDomain.UnhandledException += (s, e) => OnUnhandledException((Exception)e.ExceptionObject);
 
                 Application.EnableVisualStyles();
                 Application.SetCompatibleTextRenderingDefault(false);
@@ -67,49 +98,83 @@ namespace OpenTween
             }
         }
 
-        private static void ShowPreviousWindow()
+        /// <summary>
+        /// 動作中の .NET Framework のバージョンが適切かチェックします
+        /// </summary>
+        private static bool CheckRuntimeVersion()
+        {
+            // Mono 上で動作している場合はバージョンチェックを無視します
+            if (Type.GetType("Mono.Runtime", false) != null)
+                return true;
+
+            // .NET Framework 4.5.1 以降で動作しているかチェックする
+            // 参照: http://msdn.microsoft.com/en-us/library/hh925568%28v=vs.110%29.aspx
+
+            using (var lmKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
+            using (var ndpKey = lmKey.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\"))
+            {
+                var releaseKey = (int)ndpKey.GetValue("Release");
+                return releaseKey >= 378675;
+            }
+        }
+
+        /// <summary>
+        /// “/key:value”形式の起動オプションを解釈し IDictionary に変換する
+        /// </summary>
+        /// <remarks>
+        /// 不正な形式のオプションは除外されます。
+        /// また、重複したキーのオプションが入力された場合は末尾に書かれたオプションが採用されます。
+        /// </remarks>
+        internal static IDictionary<string, string> ParseArguments(IEnumerable<string> arguments)
+        {
+            var optionPattern = new Regex(@"^/(.+?)(?::(.*))?$");
+
+            return arguments.Select(x => optionPattern.Match(x))
+                .Where(x => x.Success)
+                .GroupBy(x => x.Groups[1].Value)
+                .ToDictionary(x => x.Key, x => x.Last().Groups[2].Value);
+        }
+
+        private static void TryActivatePreviousWindow()
         {
             // 実行中の同じアプリケーションのウィンドウ・ハンドルの取得
-            var prevProcess = Win32Api.GetPreviousProcess();
-            if (prevProcess != null && prevProcess.MainWindowHandle == IntPtr.Zero)
+            var prevProcess = GetPreviousProcess();
+            if (prevProcess == null || prevProcess.MainWindowHandle == IntPtr.Zero)
             {
-                // 起動中のアプリケーションを最前面に表示
-                Win32Api.WakeupWindow(prevProcess.MainWindowHandle);
+                return;
             }
-            else
+
+            var form = Control.FromHandle(prevProcess.MainWindowHandle) as Form;
+            if (form != null)
             {
-                if (prevProcess != null)
+                if (form.WindowState == FormWindowState.Minimized)
                 {
-                    //プロセス特定は出来たが、ウィンドウハンドルが取得できなかった(アイコン化されている)
-                    //タスクトレイアイコンのクリックをエミュレート
-                    //注:アイコン特定はTooltipの文字列で行うため、多重起動時は先に見つけた物がアクティブになる
-                    var rslt = Win32Api.ClickTasktrayIcon(Application.ProductName);
-                    if (!rslt)
-                    {
-                        // 警告を表示(見つからない、またはその他の原因で失敗)
-                        var text = string.Format(MyCommon.ReplaceAppName(Properties.Resources.StartupText1), MyCommon.GetAssemblyName());
-                        MessageBox.Show(text, MyCommon.ReplaceAppName(Properties.Resources.StartupText2), MessageBoxButtons.OK, MessageBoxIcon.Information);
-                    }
-                }
-                else
-                {
-                    // 警告を表示(プロセス見つからない場合)
-                    var text = string.Format(MyCommon.ReplaceAppName(Properties.Resources.StartupText1), MyCommon.GetAssemblyName());
-                    MessageBox.Show(text, MyCommon.ReplaceAppName(Properties.Resources.StartupText2), MessageBoxButtons.OK, MessageBoxIcon.Information);
+                    NativeMethods.RestoreWindow(form);
                 }
+                form.Activate();
             }
         }
 
-        private static void MyApplication_UnhandledException(object sender, ThreadExceptionEventArgs e)
+        private static Process GetPreviousProcess()
         {
-            //GDI+のエラー原因を特定したい
-            if (e.Exception.Message != "A generic error occurred in GDI+." &&
-               e.Exception.Message != "GDI+ で汎用エラーが発生しました。")
+            var currentProcess = Process.GetCurrentProcess();
+            try
             {
-                if (MyCommon.ExceptionOut(e.Exception))
-                {
-                    Application.Exit();
-                }
+                return Process.GetProcessesByName(currentProcess.ProcessName)
+                    .Where(p => p.Id != currentProcess.Id)
+                    .FirstOrDefault(p => p.MainModule.FileName.Equals(currentProcess.MainModule.FileName, StringComparison.OrdinalIgnoreCase));
+            }
+            catch
+            {
+                return null;
+            }
+        }
+
+        private static void OnUnhandledException(Exception ex)
+        {
+            if (MyCommon.ExceptionOut(ex))
+            {
+                Application.Exit();
             }
         }
 
@@ -161,16 +226,34 @@ namespace OpenTween
             }
         }
 
-        private static void CheckSettingFilePath()
+        private static bool SetConfigDirectoryPath()
         {
-            if (File.Exists(Path.Combine(Application.StartupPath, "roaming")))
+            string configDir;
+            if (StartupOptions.TryGetValue("configDir", out configDir) && !string.IsNullOrEmpty(configDir))
             {
-                MyCommon.settingPath = MySpecialPath.UserAppDataPath();
+                // 起動オプション /configDir で設定ファイルの参照先を変更できます
+                if (!Directory.Exists(configDir))
+                {
+                    var text = string.Format(Properties.Resources.ConfigDirectoryNotExist, configDir);
+                    MessageBox.Show(text, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
+                    return false;
+                }
+
+                MyCommon.settingPath = Path.GetFullPath(configDir);
             }
             else
             {
-                MyCommon.settingPath = Application.StartupPath;
+                if (File.Exists(Path.Combine(Application.StartupPath, "roaming")))
+                {
+                    MyCommon.settingPath = MySpecialPath.UserAppDataPath();
+                }
+                else
+                {
+                    MyCommon.settingPath = Application.StartupPath;
+                }
             }
+
+            return true;
         }
     }
 }