OSDN Git Service

c52ba5e2a7717434e966acfb50d4265f50164b33
[opentween/open-tween.git] / OpenTween / ApplicationEvents.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2007-2012 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
3 //           (c) 2008-2012 Moz (@syo68k)
4 //           (c) 2008-2012 takeshik (@takeshik) <http://www.takeshik.org/>
5 //           (c) 2010-2012 anis774 (@anis774) <http://d.hatena.ne.jp/anis774/>
6 //           (c) 2010-2012 fantasticswallow (@f_swallow) <http://twitter.com/f_swallow>
7 //           (c) 2012      Egtra (@egtra) <http://dev.activebasic.com/egtra/>
8 //           (c) 2012      kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
9 // All rights reserved.
10 // 
11 // This file is part of OpenTween.
12 // 
13 // This program is free software; you can redistribute it and/or modify it
14 // under the terms of the GNU General public License as published by the Free
15 // Software Foundation; either version 3 of the License, or (at your option)
16 // any later version.
17 // 
18 // This program is distributed in the hope that it will be useful, but
19 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License
21 // for more details.
22 // 
23 // You should have received a copy of the GNU General public License along
24 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
25 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
26 // Boston, MA 02110-1301, USA.
27
28 using System;
29 using System.Collections.Generic;
30 using System.IO;
31 using System.Linq;
32 using System.Diagnostics;
33 using System.Windows.Forms;
34 using System.Text.RegularExpressions;
35 using System.Threading;
36 using System.Threading.Tasks;
37 using System.Globalization;
38 using System.Reflection;
39 using Microsoft.Win32;
40 using OpenTween.Setting;
41
42 namespace OpenTween
43 {
44     internal class MyApplication
45     {
46         public static readonly CultureInfo[] SupportedUICulture = new[]
47         {
48             new CultureInfo("en"), // 先頭のカルチャはフォールバック先として使用される
49             new CultureInfo("ja"),
50         };
51
52         /// <summary>
53         /// 起動時に指定されたオプションを取得します
54         /// </summary>
55         public static IDictionary<string, string> StartupOptions { get; private set; }
56
57         /// <summary>
58         /// アプリケーションのメイン エントリ ポイントです。
59         /// </summary>
60         [STAThread]
61         static int Main(string[] args)
62         {
63             if (!CheckRuntimeVersion())
64             {
65                 var message = string.Format(Properties.Resources.CheckRuntimeVersion_Error, ".NET Framework 4.5.1");
66                 MessageBox.Show(message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
67                 return 1;
68             }
69
70             StartupOptions = ParseArguments(args);
71
72             if (!SetConfigDirectoryPath())
73                 return 1;
74
75             SettingManager.LoadAll();
76
77             InitCulture();
78
79             // 同じ設定ファイルを使用する OpenTween プロセスの二重起動を防止する
80             string pt = MyCommon.settingPath.Replace("\\", "/") + "/" + Application.ProductName;
81             using (Mutex mt = new Mutex(false, pt))
82             {
83                 if (!mt.WaitOne(0, false))
84                 {
85                     var text = string.Format(MyCommon.ReplaceAppName(Properties.Resources.StartupText1), MyCommon.GetAssemblyName());
86                     MessageBox.Show(text, MyCommon.ReplaceAppName(Properties.Resources.StartupText2), MessageBoxButtons.OK, MessageBoxIcon.Information);
87
88                     TryActivatePreviousWindow();
89                     return 1;
90                 }
91
92                 TaskScheduler.UnobservedTaskException += (s, e) =>
93                 {
94                     e.SetObserved();
95                     OnUnhandledException(e.Exception.Flatten());
96                 };
97                 Application.ThreadException += (s, e) => OnUnhandledException(e.Exception);
98                 AppDomain.CurrentDomain.UnhandledException += (s, e) => OnUnhandledException((Exception)e.ExceptionObject);
99
100                 Application.EnableVisualStyles();
101                 Application.SetCompatibleTextRenderingDefault(false);
102                 Application.Run(new TweenMain());
103
104                 mt.ReleaseMutex();
105
106                 return 0;
107             }
108         }
109
110         /// <summary>
111         /// 動作中の .NET Framework のバージョンが適切かチェックします
112         /// </summary>
113         private static bool CheckRuntimeVersion()
114         {
115             // Mono 上で動作している場合はバージョンチェックを無視します
116             if (Type.GetType("Mono.Runtime", false) != null)
117                 return true;
118
119             // .NET Framework 4.5.1 以降で動作しているかチェックする
120             // 参照: http://msdn.microsoft.com/en-us/library/hh925568%28v=vs.110%29.aspx
121
122             using (var lmKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
123             using (var ndpKey = lmKey.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\"))
124             {
125                 var releaseKey = (int)ndpKey.GetValue("Release");
126                 return releaseKey >= 378675;
127             }
128         }
129
130         /// <summary>
131         /// “/key:value”形式の起動オプションを解釈し IDictionary に変換する
132         /// </summary>
133         /// <remarks>
134         /// 不正な形式のオプションは除外されます。
135         /// また、重複したキーのオプションが入力された場合は末尾に書かれたオプションが採用されます。
136         /// </remarks>
137         internal static IDictionary<string, string> ParseArguments(IEnumerable<string> arguments)
138         {
139             var optionPattern = new Regex(@"^/(.+?)(?::(.*))?$");
140
141             return arguments.Select(x => optionPattern.Match(x))
142                 .Where(x => x.Success)
143                 .GroupBy(x => x.Groups[1].Value)
144                 .ToDictionary(x => x.Key, x => x.Last().Groups[2].Value);
145         }
146
147         private static void TryActivatePreviousWindow()
148         {
149             // 実行中の同じアプリケーションのウィンドウ・ハンドルの取得
150             var prevProcess = GetPreviousProcess();
151             if (prevProcess == null)
152             {
153                 return;
154             }
155
156             IntPtr windowHandle = NativeMethods.GetWindowHandle((uint)prevProcess.Id, Application.ProductName);
157             if (windowHandle != IntPtr.Zero)
158             {
159                 NativeMethods.SetActiveWindow(windowHandle);
160             }
161         }
162
163         private static Process GetPreviousProcess()
164         {
165             var currentProcess = Process.GetCurrentProcess();
166             try
167             {
168                 return Process.GetProcessesByName(currentProcess.ProcessName)
169                     .Where(p => p.Id != currentProcess.Id)
170                     .FirstOrDefault(p => p.MainModule.FileName.Equals(currentProcess.MainModule.FileName, StringComparison.OrdinalIgnoreCase));
171             }
172             catch
173             {
174                 return null;
175             }
176         }
177
178         private static void OnUnhandledException(Exception ex)
179         {
180             if (MyCommon.ExceptionOut(ex))
181             {
182                 Application.Exit();
183             }
184         }
185
186         public static void InitCulture()
187         {
188             var currentCulture = CultureInfo.CurrentUICulture;
189
190             var settingCultureStr = SettingManager.Common.Language;
191             if (settingCultureStr != "OS")
192             {
193                 try
194                 {
195                     currentCulture = new CultureInfo(settingCultureStr);
196                 }
197                 catch (CultureNotFoundException) { }
198             }
199
200             var preferredCulture = GetPreferredCulture(currentCulture);
201             CultureInfo.DefaultThreadCurrentUICulture = preferredCulture;
202             Thread.CurrentThread.CurrentUICulture = preferredCulture;
203         }
204
205         /// <summary>
206         /// サポートしているカルチャの中から、指定されたカルチャに対して適切なカルチャを選択して返します
207         /// </summary>
208         public static CultureInfo GetPreferredCulture(CultureInfo culture)
209         {
210             if (SupportedUICulture.Any(x => x.Contains(culture)))
211                 return culture;
212
213             return SupportedUICulture[0];
214         }
215
216         private static bool SetConfigDirectoryPath()
217         {
218             if (StartupOptions.TryGetValue("configDir", out var configDir) && !string.IsNullOrEmpty(configDir))
219             {
220                 // 起動オプション /configDir で設定ファイルの参照先を変更できます
221                 if (!Directory.Exists(configDir))
222                 {
223                     var text = string.Format(Properties.Resources.ConfigDirectoryNotExist, configDir);
224                     MessageBox.Show(text, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
225                     return false;
226                 }
227
228                 MyCommon.settingPath = Path.GetFullPath(configDir);
229             }
230             else
231             {
232                 // OpenTween.exe と同じディレクトリに設定ファイルを配置する
233                 MyCommon.settingPath = Application.StartupPath;
234
235                 SettingManager.LoadAll();
236
237                 try
238                 {
239                     // 設定ファイルが書き込み可能な状態であるかテストする
240                     SettingManager.SaveAll();
241                 }
242                 catch (UnauthorizedAccessException)
243                 {
244                     // 書き込みに失敗した場合 (Program Files 以下に配置されている場合など)
245
246                     // 通常は C:\Users\ユーザー名\AppData\Roaming\OpenTween\ となる
247                     var roamingDir = Path.Combine(new[]
248                     {
249                         Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
250                         Application.ProductName,
251                     });
252                     Directory.CreateDirectory(roamingDir);
253
254                     MyCommon.settingPath = roamingDir;
255
256                     /*
257                      * 書き込みが制限されたディレクトリ内で起動された場合の設定ファイルの扱い
258                      *
259                      *  (A) StartupPath に存在する設定ファイル
260                      *  (B) Roaming に存在する設定ファイル
261                      *
262                      *  1. A も B も存在しない場合
263                      *    => B を新規に作成する
264                      *
265                      *  2. A が存在し、B が存在しない場合
266                      *    => A の内容を B にコピーする (警告を表示)
267                      *
268                      *  3. A が存在せず、B が存在する場合
269                      *    => B を使用する
270                      *
271                      *  4. A も B も存在するが、A の方が更新日時が新しい場合
272                      *    => A の内容を B にコピーする (警告を表示)
273                      *
274                      *  5. A も B も存在するが、B の方が更新日時が新しい場合
275                      *    => B を使用する
276                      */
277                     var startupDirFile = new FileInfo(Path.Combine(Application.StartupPath, "SettingCommon.xml"));
278                     var roamingDirFile = new FileInfo(Path.Combine(roamingDir, "SettingCommon.xml"));
279
280                     if (roamingDirFile.Exists && (!startupDirFile.Exists || startupDirFile.LastWriteTime <= roamingDirFile.LastWriteTime))
281                     {
282                         // 既に Roaming に設定ファイルが存在し、Roaming 内のファイルの方が新しい場合は
283                         // StartupPath に設定ファイルが存在しても無視する
284                         SettingManager.LoadAll();
285                     }
286                     else
287                     {
288                         if (startupDirFile.Exists)
289                         {
290                             // StartupPath に設定ファイルが存在し、Roaming 内のファイルよりも新しい場合のみ警告を表示する
291                             var message = string.Format(Properties.Resources.SettingPath_Relocation, Application.StartupPath, MyCommon.settingPath);
292                             MessageBox.Show(message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
293                         }
294
295                         // Roaming に設定ファイルを作成 (StartupPath に読み込みに成功した設定ファイルがあれば内容がコピーされる)
296                         SettingManager.SaveAll();
297                     }
298                 }
299             }
300
301             return true;
302         }
303     }
304 }