OSDN Git Service

bit.ly のアクセストークン取得に使用する認証ダイアログを追加
[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 using System.Security.Principal;
42
43 namespace OpenTween
44 {
45     internal class MyApplication
46     {
47         public static readonly CultureInfo[] SupportedUICulture = new[]
48         {
49             new CultureInfo("en"), // 先頭のカルチャはフォールバック先として使用される
50             new CultureInfo("ja"),
51         };
52
53         /// <summary>
54         /// 起動時に指定されたオプションを取得します
55         /// </summary>
56         public static IDictionary<string, string> StartupOptions { get; private set; }
57
58         /// <summary>
59         /// アプリケーションのメイン エントリ ポイントです。
60         /// </summary>
61         [STAThread]
62         static int Main(string[] args)
63         {
64             WarnIfRunAsAdministrator();
65
66             if (!CheckRuntimeVersion())
67             {
68                 var message = string.Format(Properties.Resources.CheckRuntimeVersion_Error, ".NET Framework 4.5.1");
69                 MessageBox.Show(message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
70                 return 1;
71             }
72
73             StartupOptions = ParseArguments(args);
74
75             if (!SetConfigDirectoryPath())
76                 return 1;
77
78             SettingManager.LoadAll();
79
80             InitCulture();
81
82             // 同じ設定ファイルを使用する OpenTween プロセスの二重起動を防止する
83             string pt = MyCommon.settingPath.Replace("\\", "/") + "/" + Application.ProductName;
84             using (Mutex mt = new Mutex(false, pt))
85             {
86                 if (!mt.WaitOne(0, false))
87                 {
88                     var text = string.Format(MyCommon.ReplaceAppName(Properties.Resources.StartupText1), MyCommon.GetAssemblyName());
89                     MessageBox.Show(text, MyCommon.ReplaceAppName(Properties.Resources.StartupText2), MessageBoxButtons.OK, MessageBoxIcon.Information);
90
91                     TryActivatePreviousWindow();
92                     return 1;
93                 }
94
95                 TaskScheduler.UnobservedTaskException += (s, e) =>
96                 {
97                     e.SetObserved();
98                     OnUnhandledException(e.Exception.Flatten());
99                 };
100                 Application.ThreadException += (s, e) => OnUnhandledException(e.Exception);
101                 AppDomain.CurrentDomain.UnhandledException += (s, e) => OnUnhandledException((Exception)e.ExceptionObject);
102
103                 Application.EnableVisualStyles();
104                 Application.SetCompatibleTextRenderingDefault(false);
105                 Application.Run(new TweenMain());
106
107                 mt.ReleaseMutex();
108
109                 return 0;
110             }
111         }
112
113         /// <summary>
114         /// OpenTween が管理者権限で実行されている場合に警告を表示します
115         /// </summary>
116         private static void WarnIfRunAsAdministrator()
117         {
118             using (var currentIdentity = WindowsIdentity.GetCurrent())
119             {
120                 var principal = new WindowsPrincipal(currentIdentity);
121                 if (principal.IsInRole(WindowsBuiltInRole.Administrator))
122                 {
123                     var message = string.Format(Properties.Resources.WarnIfRunAsAdministrator_Message, Application.ProductName);
124                     MessageBox.Show(message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
125                 }
126             }
127         }
128
129         /// <summary>
130         /// 動作中の .NET Framework のバージョンが適切かチェックします
131         /// </summary>
132         private static bool CheckRuntimeVersion()
133         {
134             // Mono 上で動作している場合はバージョンチェックを無視します
135             if (Type.GetType("Mono.Runtime", false) != null)
136                 return true;
137
138             // .NET Framework 4.5.1 以降で動作しているかチェックする
139             // 参照: http://msdn.microsoft.com/en-us/library/hh925568%28v=vs.110%29.aspx
140
141             using (var lmKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
142             using (var ndpKey = lmKey.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\"))
143             {
144                 var releaseKey = (int)ndpKey.GetValue("Release");
145                 return releaseKey >= 378675;
146             }
147         }
148
149         /// <summary>
150         /// “/key:value”形式の起動オプションを解釈し IDictionary に変換する
151         /// </summary>
152         /// <remarks>
153         /// 不正な形式のオプションは除外されます。
154         /// また、重複したキーのオプションが入力された場合は末尾に書かれたオプションが採用されます。
155         /// </remarks>
156         internal static IDictionary<string, string> ParseArguments(IEnumerable<string> arguments)
157         {
158             var optionPattern = new Regex(@"^/(.+?)(?::(.*))?$");
159
160             return arguments.Select(x => optionPattern.Match(x))
161                 .Where(x => x.Success)
162                 .GroupBy(x => x.Groups[1].Value)
163                 .ToDictionary(x => x.Key, x => x.Last().Groups[2].Value);
164         }
165
166         private static void TryActivatePreviousWindow()
167         {
168             // 実行中の同じアプリケーションのウィンドウ・ハンドルの取得
169             var prevProcess = GetPreviousProcess();
170             if (prevProcess == null)
171             {
172                 return;
173             }
174
175             IntPtr windowHandle = NativeMethods.GetWindowHandle((uint)prevProcess.Id, Application.ProductName);
176             if (windowHandle != IntPtr.Zero)
177             {
178                 NativeMethods.SetActiveWindow(windowHandle);
179             }
180         }
181
182         private static Process GetPreviousProcess()
183         {
184             var currentProcess = Process.GetCurrentProcess();
185             try
186             {
187                 return Process.GetProcessesByName(currentProcess.ProcessName)
188                     .Where(p => p.Id != currentProcess.Id)
189                     .FirstOrDefault(p => p.MainModule.FileName.Equals(currentProcess.MainModule.FileName, StringComparison.OrdinalIgnoreCase));
190             }
191             catch
192             {
193                 return null;
194             }
195         }
196
197         private static void OnUnhandledException(Exception ex)
198         {
199             if (MyCommon.ExceptionOut(ex))
200             {
201                 Application.Exit();
202             }
203         }
204
205         public static void InitCulture()
206         {
207             var currentCulture = CultureInfo.CurrentUICulture;
208
209             var settingCultureStr = SettingManager.Common.Language;
210             if (settingCultureStr != "OS")
211             {
212                 try
213                 {
214                     currentCulture = new CultureInfo(settingCultureStr);
215                 }
216                 catch (CultureNotFoundException) { }
217             }
218
219             var preferredCulture = GetPreferredCulture(currentCulture);
220             CultureInfo.DefaultThreadCurrentUICulture = preferredCulture;
221             Thread.CurrentThread.CurrentUICulture = preferredCulture;
222         }
223
224         /// <summary>
225         /// サポートしているカルチャの中から、指定されたカルチャに対して適切なカルチャを選択して返します
226         /// </summary>
227         public static CultureInfo GetPreferredCulture(CultureInfo culture)
228         {
229             if (SupportedUICulture.Any(x => x.Contains(culture)))
230                 return culture;
231
232             return SupportedUICulture[0];
233         }
234
235         private static bool SetConfigDirectoryPath()
236         {
237             if (StartupOptions.TryGetValue("configDir", out var configDir) && !string.IsNullOrEmpty(configDir))
238             {
239                 // 起動オプション /configDir で設定ファイルの参照先を変更できます
240                 if (!Directory.Exists(configDir))
241                 {
242                     var text = string.Format(Properties.Resources.ConfigDirectoryNotExist, configDir);
243                     MessageBox.Show(text, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
244                     return false;
245                 }
246
247                 MyCommon.settingPath = Path.GetFullPath(configDir);
248             }
249             else
250             {
251                 // OpenTween.exe と同じディレクトリに設定ファイルを配置する
252                 MyCommon.settingPath = Application.StartupPath;
253
254                 SettingManager.LoadAll();
255
256                 try
257                 {
258                     // 設定ファイルが書き込み可能な状態であるかテストする
259                     SettingManager.SaveAll();
260                 }
261                 catch (UnauthorizedAccessException)
262                 {
263                     // 書き込みに失敗した場合 (Program Files 以下に配置されている場合など)
264
265                     // 通常は C:\Users\ユーザー名\AppData\Roaming\OpenTween\ となる
266                     var roamingDir = Path.Combine(new[]
267                     {
268                         Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
269                         Application.ProductName,
270                     });
271                     Directory.CreateDirectory(roamingDir);
272
273                     MyCommon.settingPath = roamingDir;
274
275                     /*
276                      * 書き込みが制限されたディレクトリ内で起動された場合の設定ファイルの扱い
277                      *
278                      *  (A) StartupPath に存在する設定ファイル
279                      *  (B) Roaming に存在する設定ファイル
280                      *
281                      *  1. A も B も存在しない場合
282                      *    => B を新規に作成する
283                      *
284                      *  2. A が存在し、B が存在しない場合
285                      *    => A の内容を B にコピーする (警告を表示)
286                      *
287                      *  3. A が存在せず、B が存在する場合
288                      *    => B を使用する
289                      *
290                      *  4. A も B も存在するが、A の方が更新日時が新しい場合
291                      *    => A の内容を B にコピーする (警告を表示)
292                      *
293                      *  5. A も B も存在するが、B の方が更新日時が新しい場合
294                      *    => B を使用する
295                      */
296                     var startupDirFile = new FileInfo(Path.Combine(Application.StartupPath, "SettingCommon.xml"));
297                     var roamingDirFile = new FileInfo(Path.Combine(roamingDir, "SettingCommon.xml"));
298
299                     if (roamingDirFile.Exists && (!startupDirFile.Exists || startupDirFile.LastWriteTime <= roamingDirFile.LastWriteTime))
300                     {
301                         // 既に Roaming に設定ファイルが存在し、Roaming 内のファイルの方が新しい場合は
302                         // StartupPath に設定ファイルが存在しても無視する
303                         SettingManager.LoadAll();
304                     }
305                     else
306                     {
307                         if (startupDirFile.Exists)
308                         {
309                             // StartupPath に設定ファイルが存在し、Roaming 内のファイルよりも新しい場合のみ警告を表示する
310                             var message = string.Format(Properties.Resources.SettingPath_Relocation, Application.StartupPath, MyCommon.settingPath);
311                             MessageBox.Show(message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
312                         }
313
314                         // Roaming に設定ファイルを作成 (StartupPath に読み込みに成功した設定ファイルがあれば内容がコピーされる)
315                         SettingManager.SaveAll();
316                     }
317                 }
318             }
319
320             return true;
321         }
322     }
323 }