xref: /MusicPlayer2/MusicPlayer2/MusicPlayer2.cpp (revision 52ac3cc4b8aba52ef010f9b81f6e6b7af65e4edb)
1 
2 // MusicPlayer2.cpp : 定义应用程序的类行为。
3 //
4 
5 #include "stdafx.h"
6 #include "MusicPlayer2.h"
7 #include "WinVersionHelper.h"
8 #include "MusicPlayerDlg.h"
9 #include "MessageDlg.h"
10 #include "crashtool.h"
11 #include <Gdiplus.h>
12 #include "UpdateHelper.h"
13 #include "MusicPlayerCmdHelper.h"
14 #include "SongDataManager.h"
15 #include "UiMediaLibItemMgr.h"
16 
17 #ifdef _DEBUG
18 #define new DEBUG_NEW
19 #endif
20 
21 
22 // CMusicPlayerApp
23 
BEGIN_MESSAGE_MAP(CMusicPlayerApp,CWinApp)24 BEGIN_MESSAGE_MAP(CMusicPlayerApp, CWinApp)
25     //ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
26     ON_COMMAND(ID_HELP, &CMusicPlayerApp::OnHelp)
27     ON_COMMAND(ID_HELP_UPDATE_LOG, &CMusicPlayerApp::OnHelpUpdateLog)
28     ON_COMMAND(ID_HELP_CUSTOM_UI, &CMusicPlayerApp::OnHelpCustomUi)
29     ON_COMMAND(ID_HELP_FAQ, &CMusicPlayerApp::OnHelpFaq)
30 END_MESSAGE_MAP()
31 
32 
33 // CMusicPlayerApp 构造
34 
35 CMusicPlayerApp::CMusicPlayerApp()
36 {
37     // 支持重新启动管理器
38     //m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;		//不需要支持重新启动管理器
39 
40     // TODO: 在此处添加构造代码,
41     // 将所有重要的初始化放置在 InitInstance 中
42 
43     CRASHREPORT::StartCrashReport();
44 
45     //获取当前DPI
46     HDC hDC = ::GetDC(NULL);
47     m_dpi = GetDeviceCaps(hDC, LOGPIXELSY);
48     ::ReleaseDC(NULL, hDC);
49     if (m_dpi == 0)
50     {
51         WriteLog(L"Get system DPI failed!");
52         m_dpi = 96;
53     }
54 }
55 
56 
57 // 唯一的一个 CMusicPlayerApp 对象
58 
59 CMusicPlayerApp theApp;
60 
61 
62 // CMusicPlayerApp 初始化
63 
InitInstance()64 BOOL CMusicPlayerApp::InitInstance()
65 {
66     //替换掉对话框程序的默认类名
67     WNDCLASS wc;
68     ::GetClassInfo(AfxGetInstanceHandle(), _T("#32770"), &wc);	//MFC默认的所有对话框的类名为#32770
69     wc.lpszClassName = _T("MusicPlayer_l3gwYT");	//将对话框的类名修改为新类名
70     AfxRegisterClass(&wc);
71 
72     //初始化路径
73     wchar_t path[MAX_PATH];
74     GetModuleFileNameW(NULL, path, MAX_PATH);
75     m_module_path_reg = path;
76     if (m_module_path_reg.find(L' ') != std::wstring::npos)
77     {
78         //如果路径中有空格,则在程序路径前后添加双引号
79         m_module_path_reg = L'\"' + m_module_path_reg;
80         m_module_path_reg += L'\"';
81     }
82     m_module_dir = CCommon::GetExePath();
83     m_appdata_dir = CCommon::GetAppDataConfigDir();
84 
85     LoadGlobalConfig();
86 
87 #ifdef _DEBUG
88     m_local_dir = L".\\";
89 #else
90     m_local_dir = m_module_dir;
91 #endif // _DEBUG
92     if (m_general_setting_data.portable_mode)
93         m_config_dir = m_module_dir;
94     else
95         m_config_dir = m_appdata_dir;
96 
97     m_config_path = m_config_dir + L"config.ini";
98     m_song_data_path = m_config_dir + L"song_data.dat";
99     m_recent_list_dat_path = m_config_dir + L"recent_list.dat";
100     // 以下三项不再使用,但路径留作旧版兼容,新程序不再创建这三个文件
101     m_recent_path_dat_path = m_config_dir + L"recent_path.dat";
102     m_recent_playlist_data_path = m_config_dir + L"playlist\\recent_playlist.dat";
103     m_recent_medialib_playlist_path = m_config_dir + L"recent_medialib_item.dat";
104 
105     m_desktop_path = CCommon::GetDesktopPath();
106     m_lastfm_path = m_config_dir + L"lastfm.dat";
107     m_ui_data_path = m_config_dir + L"user_ui.dat";
108     //m_temp_path = CCommon::GetTemplatePath() + L"MusicPlayer2\\";
109     m_playlist_dir = m_config_dir + L"playlist\\";
110     CCommon::CreateDir(m_playlist_dir);
111 
112     wstring cmd_line{ m_lpCmdLine };
113     //当程序被Windows重新启动时,直接退出程序
114     if (cmd_line.find(L"RestartByRestartManager") != wstring::npos)
115     {
116         LoadConfig();   // m_str_table.Init之前需要先加载语言配置
117         // Init略花时间,不应当在互斥量成功创建之前执行,这里是特例
118         m_str_table.Init(m_local_dir + L"language\\", m_general_setting_data.language_);
119         CCommon::SetThreadLanguageList(m_str_table.GetLanguageTag());
120         // 将命令行参数写入日志文件
121         wstring info = theApp.m_str_table.LoadTextFormat(L"LOG_RESTART_EXIT", { cmd_line });
122         WriteLog(info);
123         return FALSE;
124     }
125 
126     bool cmd_control = CCommon::GetCmdLineCommand(cmd_line, m_cmd);		//命令行参数是否包含参数命令
127     if (cmd_control)		//如果从命令行参数解析到了命令,则将命令行参数清除
128         cmd_line.clear();
129 
130     LPCTSTR str_mutex{};
131 #ifdef _DEBUG
132     str_mutex = _T("EY7n2uGon722");
133 #else
134     str_mutex = _T("bXS1E7joK0Kh");
135 #endif
136     //检查是否已有实例正在运行
137     HANDLE hMutex = ::CreateMutex(NULL, TRUE, str_mutex);		//使用一个随机的字符串创建一个互斥量
138     if (hMutex != NULL)
139     {
140         if (GetLastError() == ERROR_ALREADY_EXISTS)		//互斥量创建失败,说明已经有一个程序的实例正在运行
141         {
142             //AfxMessageBox(_T("已经有一个程序正在运行。"));
143             bool add_files{ false };
144             HWND handle = FindWindow(_T("MusicPlayer_l3gwYT"), NULL);		//根据类名查找已运行实例窗口的句柄
145             if (handle == NULL)
146             {
147                 //如果没有找到窗口句柄,则可能程序窗口还未创建,先延时一段时间再查找。
148                 //这种情况只可能是在同时打开多个音频文件时启动了多个进程,这些进程几乎是同时启动的,
149                 //此时虽然检测到已有另一个程序实例在运行,但是窗口还未创建,
150                 //此时将命令行参数的文件添加到现有实例的播放列表中(通过将add_files设置为true来实现)
151                 Sleep(500);
152                 handle = FindWindow(_T("MusicPlayer_l3gwYT"), NULL);
153                 add_files = true;
154             }
155             if (handle != NULL)
156             {
157                 HWND minidlg_handle = FindWindow(_T("MiniDlg_ByH87M"), NULL);
158                 if (!cmd_control || m_cmd == ControlCmd::MINI_MODE)
159                 {
160                     if (minidlg_handle == NULL)			//没有找到“迷你模式”窗口,则激活主窗口
161                     {
162                         ShowWindow(handle, SW_SHOWNORMAL);		//激活并显示窗口
163                         SetForegroundWindow(handle);		//将窗口设置为焦点
164                         ::SendMessage(handle, WM_MAIN_WINDOW_ACTIVATED, 0, 0);
165                     }
166                     else				//找到了“迷你模式”窗口,则激活“迷你模式”窗口
167                     {
168                         ShowWindow(minidlg_handle, SW_SHOWNORMAL);
169                         SetForegroundWindow(minidlg_handle);
170                     }
171                 }
172 
173                 if (cmd_control)
174                 {
175                     if (m_cmd & ControlCmd::PLAY_PAUSE)
176                         ::SendMessage(handle, WM_COMMAND, ID_PLAY_PAUSE, 0);
177                     if (m_cmd & ControlCmd::_PREVIOUS)
178                         ::SendMessage(handle, WM_COMMAND, ID_PREVIOUS, 0);
179                     if (m_cmd & ControlCmd::_NEXT)
180                         ::SendMessage(handle, WM_COMMAND, ID_NEXT, 0);
181                     if (m_cmd & ControlCmd::STOP)
182                         ::SendMessage(handle, WM_COMMAND, ID_STOP, 0);
183                     if (m_cmd & ControlCmd::FF)
184                         ::SendMessage(handle, WM_COMMAND, ID_FF, 0);
185                     if (m_cmd & ControlCmd::REW)
186                         ::SendMessage(handle, WM_COMMAND, ID_REW, 0);
187                     if (m_cmd & ControlCmd::VOLUME_UP)
188                         ::SendMessage(handle, WM_COMMAND, ID_VOLUME_UP, 0);
189                     if (m_cmd & ControlCmd::VOLUME_DOWM)
190                         ::SendMessage(handle, WM_COMMAND, ID_VOLUME_DOWN, 0);
191                     if (m_cmd & ControlCmd::MINI_MODE)
192                         ::PostMessage(handle, WM_COMMAND, ID_MINI_MODE, 0);     //这里应该用PostMessage,因为响应ID_MINI_MODE的函数中有DoModel函数,只有当模态对话框关闭后函数才会返回
193                 }
194 
195                 if (!cmd_line.empty())		//如果通过命令行传递了打开的文件名,且已有一个进程在运行,则将打开文件的命令和命令行参数传递给该进程
196                 {
197                     //通过WM_COPYDATA消息向已有进程传递消息
198                     COPYDATASTRUCT copy_data;
199                     copy_data.dwData = add_files ? COPY_DATA_ADD_FILE : COPY_DATA_OPEN_FILE;
200                     copy_data.cbData = cmd_line.size() * sizeof(wchar_t);
201                     copy_data.lpData = (const PVOID)cmd_line.c_str();
202                     ::SendMessage(handle, WM_COPYDATA, 0, (LPARAM)&copy_data);
203                 }
204             }
205             else        //仍然找不到窗口句柄,说明程序还没有退出
206             {
207                 LoadConfig();
208                 m_str_table.Init(m_local_dir + L"language\\", m_general_setting_data.language_);
209                 CCommon::SetThreadLanguageList(m_str_table.GetLanguageTag());
210                 const wstring& info = theApp.m_str_table.LoadText(L"MSG_APP_RUNNING_INFO");
211                 AfxMessageBox(info.c_str(), MB_ICONINFORMATION | MB_OK);
212             }
213             return FALSE;		//退出当前程序
214         }
215     }
216 
217     LoadConfig();
218     // 获取互斥量后StrTable应尽早初始化以免某些LoadText后以静态变量保存字符串引用的地方加载到空字符串<error>
219     m_str_table.Init(m_local_dir + L"language\\", m_general_setting_data.language_);
220 
221     LoadSongData();
222     LoadLastFMData();
223 
224     // 获取默认线程语言
225     CCommon::GetThreadLanguageList(m_def_lang_list);
226     //初始化界面语言
227     CCommon::SetThreadLanguageList(m_str_table.GetLanguageTag());
228 
229     //检查bass.dll的版本是否和API的版本匹配
230     WORD dll_version{ HIWORD(BASS_GetVersion()) };
231     //WORD dll_version{ 0x203 };
232     if (dll_version != BASSVERSION)
233     {
234         wstring dll_version_str{ std::to_wstring(static_cast<int>(HIBYTE(dll_version))) + L'.' + std::to_wstring(static_cast<int>(LOBYTE(dll_version))) };
235         wstring bass_version_str{ std::to_wstring(static_cast<int>(HIBYTE(BASSVERSION))) + L'.' + std::to_wstring(static_cast<int>(LOBYTE(BASSVERSION))) };
236         wstring info = theApp.m_str_table.LoadTextFormat(L"MSG_BASS_VERSION_WARNING", { dll_version_str, bass_version_str });
237         if (AfxMessageBox(info.c_str(), MB_ICONWARNING | MB_OKCANCEL) == IDCANCEL)
238             return FALSE;
239     }
240 
241     //启动时检查更新
242 #ifndef _DEBUG		//DEBUG下不在启动时检查更新
243     if (m_general_setting_data.check_update_when_start)
244     {
245         AfxBeginThread(CheckUpdateThreadFunc, NULL);
246     }
247 #endif // !_DEBUG
248 
249     //启动后台线程将歌曲数据分类
250     //StartClassifySongData();
251 
252     // 如果一个运行在 Windows XP 上的应用程序清单指定要
253     // 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
254     //则需要 InitCommonControlsEx()。  否则,将无法创建窗口。
255     INITCOMMONCONTROLSEX InitCtrls;
256     InitCtrls.dwSize = sizeof(InitCtrls);
257     // 将它设置为包括所有要在应用程序中使用的
258     // 公共控件类。
259     InitCtrls.dwICC = ICC_WIN95_CLASSES;
260     InitCommonControlsEx(&InitCtrls);
261 
262     CWinApp::InitInstance();
263 
264 
265     AfxEnableControlContainer();
266 
267     // 创建 shell 管理器,以防对话框包含
268     // 任何 shell 树视图控件或 shell 列表视图控件。
269     CShellManager* pShellManager = new CShellManager;
270 
271     // 激活“Windows Native”视觉管理器,以便在 MFC 控件中启用主题
272     CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
273 
274     // 标准初始化
275     // 如果未使用这些功能并希望减小
276     // 最终可执行文件的大小,则应移除下列
277     // 不需要的特定初始化例程
278     // 更改用于存储设置的注册表项
279     // TODO: 应适当修改该字符串,
280     // 例如修改为公司或组织名
281     //SetRegistryKey(_T("Apps By ZhongYang"));
282 
283     //设置一个全局钩子以截获多媒体按键消息
284     if (m_hot_key_setting_data.global_multimedia_key_enable && !CWinVersionHelper::IsWindows81OrLater())
285         m_multimedia_key_hook = SetWindowsHookEx(WH_KEYBOARD_LL, CMusicPlayerApp::MultiMediaKeyHookProc, m_hInstance, 0);
286 
287     // 初始化ITaskbarList3
288     if (CWinVersionHelper::IsWindows7OrLater())
289         EnableTaskbarInteraction(TRUE);
290 
291     Gdiplus::GdiplusStartupInput gdiplusStartupInput;
292     GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
293 
294     m_hScintillaModule = LoadLibrary(_T("SciLexer.dll"));
295     m_accelerator_res.Init();
296     m_chinese_pingyin_res.Init();
297 
298     CMusicPlayerDlg dlg(cmd_line);
299     //CMusicPlayerDlg dlg(L"\"D:\\音乐\\纯音乐\\班得瑞\\05. Chariots Of Fire 火战车.mp3\"");
300     m_pMainWnd = &dlg;
301     INT_PTR nResponse = dlg.DoModal();
302     if (nResponse == IDOK)
303     {
304         // TODO: 在此放置处理何时用
305         //  “确定”来关闭对话框的代码
306     }
307     else if (nResponse == IDCANCEL)
308     {
309         // TODO: 在此放置处理何时用
310         //  “取消”来关闭对话框的代码
311     }
312     else if (nResponse == -1)
313     {
314         TRACE(traceAppMsg, 0, L"警告: 对话框创建失败,应用程序将意外终止。\n");
315         TRACE(traceAppMsg, 0, L"警告: 如果您在对话框上使用 MFC 控件,则无法 #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS。\n");
316     }
317 
318     SaveLastFMData();
319     SaveGlobalConfig();
320 
321     //如果媒体库正在更新,通知线程退出
322     m_media_update_para.thread_exit = true;
323     if (theApp.m_media_lib_updating)
324         WaitForSingleObject(m_media_lib_update_thread->m_hThread, 1000);	//等待线程退出
325 
326     SaveSongData();
327 
328     // 删除上面创建的 shell 管理器。
329     if (pShellManager != NULL)
330     {
331         delete pShellManager;
332     }
333 
334 
335     //if (m_multimedia_key_hook != NULL)
336     //{
337     //    UnhookWindowsHookEx(m_multimedia_key_hook);
338     //    m_multimedia_key_hook = NULL;
339     //}
340 
341     //如果音频音量处于淡出状态,则等待淡出结束再退出程序
342     for (int i{}; i < 100 && CPlayer::GetInstance().GetPlayerCore()->IsVolumeFadingOut(); i++)
343     {
344         Sleep(10);
345     }
346 
347     // 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
348     //  而不是启动应用程序的消息泵。
349     return FALSE;
350 }
351 
352 
353 
OnHelp()354 void CMusicPlayerApp::OnHelp()
355 {
356     // TODO: 在此添加命令处理程序代码
357     CMessageDlg helpDlg(L"HelpDlg");    // 此字符串对应CBaseDialog::GetDialogName,用于保证对话框不会重复打开
358     helpDlg.SetWindowTitle(theApp.m_str_table.LoadText(L"TITLE_HELP_DLG"));
359     helpDlg.SetInfoText(theApp.m_str_table.LoadText(L"TXT_HELP_DLG_WELCOM_TO_USE"));
360     helpDlg.SetLinkInfo(theApp.m_str_table.LoadText(L"TXT_HELP_DLG_SHOW_ONLINE_HELP"), L"https://github.com/zhongyang219/MusicPlayer2/wiki");
361     wstring info = theApp.m_str_table.LoadText(L"TXT_HELP_DLG_HELP_INFO");
362     info += L"\r\n\r\n" + GetSystemInfoString();
363     helpDlg.SetMessageText(info);
364     helpDlg.DoModal();
365 }
366 
SaveSongData()367 void CMusicPlayerApp::SaveSongData()
368 {
369     CSongDataManager::GetInstance().SaveSongData(m_song_data_path);
370 }
371 
CheckUpdate(bool message)372 void CMusicPlayerApp::CheckUpdate(bool message)
373 {
374     if (m_checking_update)      //如果还在检查更新,则直接返回
375         return;
376     CFlagLocker update_locker(m_checking_update);
377     CWaitCursor wait_cursor;
378 
379     wstring version;		//程序版本
380     wstring link;			//下载链接
381     CUpdateHelper update_helper;
382     update_helper.SetUpdateSource(static_cast<CUpdateHelper::UpdateSource>(m_general_setting_data.update_source));
383     if (!update_helper.CheckForUpdate())
384     {
385         if (message)
386         {
387             const wstring& info = theApp.m_str_table.LoadText(L"MSG_UPDATE_CHECK_FAILED");
388             AfxMessageBox(info.c_str(), MB_OK | MB_ICONWARNING);
389         }
390         return;
391     }
392 
393     version = update_helper.GetVersion();
394 #ifdef _M_X64
395     link = update_helper.GetLink64();
396 #else
397     link = update_helper.GetLink();
398 #endif
399 
400     if (version.empty() || link.empty())
401     {
402         if (message)
403         {
404             wstring info = theApp.m_str_table.LoadTextFormat(L"MSG_UPDATE_CHECK_ERROR", { L"row_data = " + std::to_wstring(update_helper.IsRowData()) });
405             theApp.m_pMainWnd->MessageBox(info.c_str(), NULL, MB_OK | MB_ICONWARNING);
406         }
407         return;
408     }
409     if (version > APP_VERSION)		//如果服务器上的版本大于本地版本
410     {
411         //根据语言设置选择对应语言版本的更新内容
412         bool is_zh_cn = theApp.m_str_table.IsSimplifiedChinese();
413         wstring contents{ is_zh_cn ? update_helper.GetContentsZhCn() : update_helper.GetContentsEn() };
414 
415         wstring info;
416         if (contents.empty())
417             info = theApp.m_str_table.LoadTextFormat(L"MSG_UPDATE_AVLIABLE", { version });
418         else
419             info = theApp.m_str_table.LoadTextFormat(L"MSG_UPDATE_AVLIABLE_2", { version, contents });
420 
421         if (theApp.m_pMainWnd->MessageBox(info.c_str(), NULL, MB_YESNO | MB_ICONQUESTION) == IDYES)
422         {
423             ShellExecute(NULL, _T("open"), link.c_str(), NULL, NULL, SW_SHOW);		//转到下载链接
424         }
425     }
426     else
427     {
428         if (message)
429         {
430             const wstring& info = theApp.m_str_table.LoadText(L"MSG_UPDATE_ALREADY");
431             theApp.m_pMainWnd->MessageBox(info.c_str(), NULL, MB_OK | MB_ICONINFORMATION);
432         }
433     }
434 }
435 
CheckUpdateInThread(bool message)436 void CMusicPlayerApp::CheckUpdateInThread(bool message)
437 {
438     AfxBeginThread(CheckUpdateThreadFunc, (LPVOID)message);
439 }
440 
LoadGlobalConfig()441 void CMusicPlayerApp::LoadGlobalConfig()
442 {
443     bool portable_mode_default{ false };
444     wstring global_cfg_path{ m_module_dir + L"global_cfg.ini" };
445     if (!CCommon::FileExist(global_cfg_path.c_str()))       //如果global_cfg.ini不存在,则根据AppData/Roaming/MusicPlayer2目录下是否存在config.ini来判断配置文件的保存位置
446     {
447         portable_mode_default = !CCommon::FileExist((m_appdata_dir + L"config.ini").c_str());
448     }
449 
450     CIniHelper ini{ global_cfg_path };
451     m_general_setting_data.portable_mode = ini.GetBool(L"config", L"portable_mode", portable_mode_default);
452 
453     //执行一次保存操作,以检查当前目录是否有写入权限
454     m_module_dir_writable = ini.Save();
455 
456     if (m_module_dir.find(CCommon::GetTemplatePath()) != wstring::npos)      //如果当前路径是在Temp目录下,则强制将数据保存到Appdata
457     {
458         m_module_dir_writable = false;
459     }
460 
461     if (!m_module_dir_writable)              //如果当前目录没有写入权限,则设置配置保存到AppData目录
462     {
463         m_general_setting_data.portable_mode = false;
464     }
465 }
466 
SaveGlobalConfig()467 void CMusicPlayerApp::SaveGlobalConfig()
468 {
469     CIniHelper ini{ m_module_dir + L"global_cfg.ini" };
470     ini.WriteBool(L"config", L"portable_mode", m_general_setting_data.portable_mode);
471 
472     //检查是否保存成功
473     if (!ini.Save())
474     {
475         //if (m_cannot_save_global_config_warning)
476         //{
477         //    CString info;
478         //    info.LoadString(IDS_CONNOT_SAVE_CONFIG_WARNING);
479         //    info.Replace(_T("<%file_path%>"), m_module_dir.c_str());
480         //    AfxMessageBox(info, MB_ICONWARNING);
481         //}
482         //m_cannot_save_global_config_warning = false;
483         //return;
484     }
485 }
486 
CheckUpdateThreadFunc(LPVOID lpParam)487 UINT CMusicPlayerApp::CheckUpdateThreadFunc(LPVOID lpParam)
488 {
489     CCommon::SetThreadLanguageList(theApp.m_str_table.GetLanguageTag());
490     theApp.CheckUpdate(lpParam);		//检查更新
491     return 0;
492 }
493 
SaveConfig()494 void CMusicPlayerApp::SaveConfig()
495 {
496     CIniHelper ini(m_config_path);
497     ini.WriteString(L"app", L"version", APP_VERSION);
498     ini.WriteBool(L"general", L"check_update_when_start", m_general_setting_data.check_update_when_start);
499     // ini.WriteInt(_T("general"), _T("language"), static_cast<int>(m_general_setting_data.language));
500     ini.WriteString(L"general", L"language_", m_general_setting_data.language_);
501     if (!CWinVersionHelper::IsWindows81OrLater())
502         ini.WriteBool(L"hot_key", L"global_multimedia_key_enable", m_hot_key_setting_data.global_multimedia_key_enable);
503     ini.Save();
504 }
505 
LoadConfig()506 void CMusicPlayerApp::LoadConfig()
507 {
508     CIniHelper ini(m_config_path);
509     wstring config_version = ini.GetString(L"app", L"version", L"");
510     m_general_setting_data.check_update_when_start = ini.GetBool(L"general", L"check_update_when_start", true);
511     // m_general_setting_data.language = static_cast<Language>(ini.GetInt(L"general", L"language", 0));
512     m_general_setting_data.language_ = ini.GetString(L"general", L"language_", L"");     // 留空表示“跟随系统”
513     if (!CWinVersionHelper::IsWindows81OrLater())
514         m_hot_key_setting_data.global_multimedia_key_enable = ini.GetBool(L"hot_key", L"global_multimedia_key_enable", false);
515 }
516 
LoadImgResource()517 void CMusicPlayerApp::LoadImgResource()
518 {
519     //加载图片资源
520     m_image_set.default_cover_img = CCommon::GetPngImageResource(IDB_DEFAULT_ALBUM_COVER);
521     m_image_set.default_cover_not_played_img = CCommon::GetPngImageResource(IDB_DEFAULT_ALBUM_COVER_NOT_PLAYED);
522 
523     m_image_set.default_cover_img_data = CCommon::GetPngImageResourceData(IDB_DEFAULT_ALBUM_COVER);
524     m_image_set.default_cover_not_played_img_data = CCommon::GetPngImageResourceData(IDB_DEFAULT_ALBUM_COVER_NOT_PLAYED);
525 }
526 
DPI(int pixel)527 int CMusicPlayerApp::DPI(int pixel)
528 {
529     return (m_dpi * (pixel) / 96);
530 }
531 
DPI(double pixel)532 int CMusicPlayerApp::DPI(double pixel)
533 {
534     return static_cast<int>(m_dpi * (pixel) / 96);
535 }
536 
DPIRound(double pixel,double round)537 int CMusicPlayerApp::DPIRound(double pixel, double round)
538 {
539     double rtn;
540     rtn = static_cast<double>(m_dpi) * pixel / 96;
541     rtn += round;
542     return static_cast<int>(rtn);
543 }
544 
GetDPIFromWindow(CWnd * pWnd)545 void CMusicPlayerApp::GetDPIFromWindow(CWnd* pWnd)
546 {
547     CWindowDC dc(pWnd);
548     HDC hDC = dc.GetSafeHdc();
549     m_dpi = GetDeviceCaps(hDC, LOGPIXELSY);
550 }
551 
GetSystemInfoString()552 wstring CMusicPlayerApp::GetSystemInfoString()
553 {
554     std::wstringstream wss;
555     wss << L"System Info:\r\n"
556         << L"Windows Version: " << CWinVersionHelper::GetMajorVersion() << L'.' << CWinVersionHelper::GetMinorVersion()
557         << L" build " << CWinVersionHelper::GetBuildNumber() << L"\r\n"
558         << L"DPI: " << GetDPI() << L"\r\n";
559 
560     wss << L"System Language List:\r\n";
561     for (const wstring& str : m_def_lang_list)
562         wss << str << L"; ";
563 
564     return std::move(wss).str();
565 }
566 
SetAutoRun(bool auto_run)567 void CMusicPlayerApp::SetAutoRun(bool auto_run)
568 {
569     CRegKey key;
570     //打开注册表项
571     if (key.Open(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\Run")) != ERROR_SUCCESS)
572     {
573         return;
574     }
575     if (auto_run)		//写入注册表项
576     {
577         if (key.SetStringValue(APP_NAME, m_module_path_reg.c_str()) != ERROR_SUCCESS)
578         {
579             return;
580         }
581     }
582     else		//删除注册表项
583     {
584         //删除前先检查注册表项是否存在,如果不存在,则直接返回
585         wchar_t buff[256];
586         ULONG size{ 256 };
587         if (key.QueryStringValue(APP_NAME, buff, &size) != ERROR_SUCCESS)
588             return;
589         if (key.DeleteValue(APP_NAME) != ERROR_SUCCESS)
590         {
591             return;
592         }
593     }
594 }
595 
GetAutoRun()596 bool CMusicPlayerApp::GetAutoRun()
597 {
598     CRegKey key;
599     if (key.Open(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\Run")) != ERROR_SUCCESS)
600     {
601         //打开注册表“Software\\Microsoft\\Windows\\CurrentVersion\\Run”失败,则返回false
602         return false;
603     }
604     wchar_t buff[256];
605     ULONG size{ 256 };
606     if (key.QueryStringValue(APP_NAME, buff, &size) == ERROR_SUCCESS)		//如果找到了APP_NAME键
607     {
608         return (m_module_path_reg == buff);	//如果APP_NAME的值是当前程序的路径,就返回true,否则返回false
609     }
610     else
611     {
612         return false;		//没有找到APP_NAME键,返回false
613     }
614 }
615 
WriteLog(const wstring & log_str,int log_type)616 void CMusicPlayerApp::WriteLog(const wstring& log_str, int log_type)
617 {
618     static std::mutex log_mutex;
619     std::lock_guard<std::mutex> lock(log_mutex);
620     if (((log_type & NonCategorizedSettingData::LT_ERROR) != 0) && ((m_nc_setting_data.debug_log & NonCategorizedSettingData::LT_ERROR) != 0))
621         CCommon::WriteLog((m_config_dir + L"error.log").c_str(), log_str);
622 #ifdef _DEBUG
623     if (((log_type & NonCategorizedSettingData::LT_NORMAL) != 0) && ((m_nc_setting_data.debug_log & NonCategorizedSettingData::LT_NORMAL) != 0))
624         CCommon::WriteLog((m_config_dir + L"debug.log").c_str(), log_str);
625 #endif
626 }
627 
StartUpdateMediaLib(bool force)628 void CMusicPlayerApp::StartUpdateMediaLib(bool force)
629 {
630     if (!m_media_lib_updating)
631     {
632         m_media_lib_updating = true;
633         m_media_update_para.num_added = 0;
634         m_media_update_para.force = force;
635         m_media_lib_update_thread = AfxBeginThread([](LPVOID lpParam)->UINT
636             {
637                 if (theApp.m_media_lib_setting_data.remove_file_not_exist_when_update)
638                 {
639                     CMusicPlayerCmdHelper::CleanUpRecentFolders();  // 虽然仍有线程安全问题不过这行在“启动时更新媒体库”时立刻进行冲突的可能性比较小
640                     CMusicPlayerCmdHelper::CleanUpSongData();
641                 }
642                 CMusicPlayerCmdHelper::UpdateMediaLib();
643                 theApp.m_media_lib_updating = false;
644                 //更新UI中我喜欢的音乐、所有曲目和媒体库项目列表
645                 CUiMyFavouriteItemMgr::Instance().UpdateMyFavourite();
646                 CUiFolderExploreMgr::Instance().UpdateFolders();
647                 CUiAllTracksMgr::Instance().UpdateAllTracks();
648                 CUiMediaLibItemMgr::Instance().Init();
649                 return 0;
650             }, nullptr);
651     }
652 }
653 
AutoSelectNotifyIcon()654 void CMusicPlayerApp::AutoSelectNotifyIcon()
655 {
656     if (CWinVersionHelper::IsWindows10OrLater())
657     {
658         bool light_mode = CWinVersionHelper::IsWindows10LightTheme();
659         if (light_mode)     //浅色模式下,如果图标是白色,则改成黑色
660         {
661             if (m_app_setting_data.notify_icon_selected == 1)
662                 m_app_setting_data.notify_icon_selected = 2;
663         }
664         else     //深色模式下,如果图标是黑色,则改成白色
665         {
666             if (m_app_setting_data.notify_icon_selected == 2)
667                 m_app_setting_data.notify_icon_selected = 1;
668         }
669     }
670 }
671 
GetNotifyIncon(int index)672 HICON CMusicPlayerApp::GetNotifyIncon(int index)
673 {
674     static HICON hIcon_color = theApp.m_icon_mgr.GetHICON(IconMgr::IconType::IT_App, IconMgr::IconStyle::IS_Color, IconMgr::IconSize::IS_DPI_16);
675     static HICON hIcon_white = theApp.m_icon_mgr.GetHICON(IconMgr::IconType::IT_App_Monochrome, IconMgr::IconStyle::IS_OutlinedLight, IconMgr::IconSize::IS_DPI_16);
676     static HICON hIcon_black = theApp.m_icon_mgr.GetHICON(IconMgr::IconType::IT_App_Monochrome, IconMgr::IconStyle::IS_OutlinedDark, IconMgr::IconSize::IS_DPI_16);
677 
678     if (index == 1)
679         return hIcon_white;
680     else if (index == 2)
681         return hIcon_black;
682     else
683         return hIcon_color;
684 }
685 
IsScintillaLoaded() const686 bool CMusicPlayerApp::IsScintillaLoaded() const
687 {
688     return m_hScintillaModule != NULL;
689 }
690 
691 //void CMusicPlayerApp::StartClassifySongData()
692 //{
693 //    AfxBeginThread(ClassifySongDataThreadFunc, NULL);
694 //}
695 
LoadSongData()696 void CMusicPlayerApp::LoadSongData()
697 {
698     CSongDataManager::GetInstance().LoadSongData(m_song_data_path);
699 }
700 
MultiMediaKeyHookProc(int nCode,WPARAM wParam,LPARAM lParam)701 LRESULT CMusicPlayerApp::MultiMediaKeyHookProc(int nCode, WPARAM wParam, LPARAM lParam)
702 {
703     //	if (wParam == HSHELL_APPCOMMAND)
704         //{
705         //	int a = 0;
706         //}
707 
708         //截获全局的多媒体按键消息
709     if (wParam == WM_KEYUP && !CPlayer::GetInstance().m_controls.IsActive())
710     {
711         KBDLLHOOKSTRUCT* pKBHook = (KBDLLHOOKSTRUCT*)lParam;
712         switch (pKBHook->vkCode)
713         {
714         case VK_MEDIA_PLAY_PAUSE:
715             SendMessage(AfxGetMainWnd()->GetSafeHwnd(), WM_COMMAND, ID_PLAY_PAUSE, 0);
716             return TRUE;
717         case VK_MEDIA_PREV_TRACK:
718             SendMessage(AfxGetMainWnd()->GetSafeHwnd(), WM_COMMAND, ID_PREVIOUS, 0);
719             return TRUE;
720         case VK_MEDIA_NEXT_TRACK:
721             SendMessage(AfxGetMainWnd()->GetSafeHwnd(), WM_COMMAND, ID_NEXT, 0);
722             return TRUE;
723         case VK_MEDIA_STOP:
724             SendMessage(AfxGetMainWnd()->GetSafeHwnd(), WM_COMMAND, ID_STOP, 0);
725             return TRUE;
726         default:
727             break;
728         }
729     }
730 
731     CallNextHookEx(theApp.m_multimedia_key_hook, nCode, wParam, lParam);
732 
733     return LRESULT();
734 }
735 
736 
737 
ExitInstance()738 int CMusicPlayerApp::ExitInstance()
739 {
740     // TODO: 在此添加专用代码和/或调用基类
741     // 卸载GDI+
742     Gdiplus::GdiplusShutdown(m_gdiplusToken);
743 
744     const auto& unknown_key = m_str_table.GetUnKnownKey();
745     if (!unknown_key.empty())
746     {
747         wstring log_str = m_str_table.LoadText(L"LOG_STRING_TABLE_UNKNOWN_KEY");
748         for (const auto& item : unknown_key)
749             log_str += item + L';';
750         WriteLog(log_str);
751     }
752     const auto& error_para_key = m_str_table.GetErrorParaKey();
753     if (!error_para_key.empty())
754     {
755         wstring log_str = m_str_table.LoadText(L"LOG_STRING_TABLE_PARA_ERROR");
756         for (const auto& item : error_para_key)
757             log_str += item + L';';
758         WriteLog(log_str);
759     }
760     // 释放ITaskbarList3
761     ReleaseTaskBarRefs();
762 
763     return CWinApp::ExitInstance();
764 }
765 
766 
OnHelpUpdateLog()767 void CMusicPlayerApp::OnHelpUpdateLog()
768 {
769     // TODO: 在此添加命令处理程序代码
770     bool is_zh_cn = theApp.m_str_table.IsSimplifiedChinese();
771     if (is_zh_cn)
772         ShellExecute(NULL, _T("open"), _T("https://github.com/zhongyang219/MusicPlayer2/blob/master/Documents/update_log.md"), NULL, NULL, SW_SHOW);
773     else
774         ShellExecute(NULL, _T("open"), _T("https://github.com/zhongyang219/MusicPlayer2/blob/master/Documents/update_log_en-us.md"), NULL, NULL, SW_SHOW);
775 
776 }
777 
778 
OnHelpCustomUi()779 void CMusicPlayerApp::OnHelpCustomUi()
780 {
781     ShellExecute(NULL, _T("open"), _T("https://github.com/zhongyang219/MusicPlayer2/wiki/%E7%94%A8%E6%88%B7%E8%87%AA%E5%AE%9A%E4%B9%89%E7%95%8C%E9%9D%A2"), NULL, NULL, SW_SHOW);
782 }
783 
784 
OnHelpFaq()785 void CMusicPlayerApp::OnHelpFaq()
786 {
787     bool is_zh_cn = theApp.m_str_table.IsSimplifiedChinese();
788     if (is_zh_cn)
789         ShellExecute(NULL, _T("open"), _T("https://github.com/zhongyang219/MusicPlayer2/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98"), NULL, NULL, SW_SHOW);
790     else
791         ShellExecute(NULL, _T("open"), _T("https://github.com/zhongyang219/MusicPlayer2/wiki/FAQ"), NULL, NULL, SW_SHOW);
792 }
793 
LoadLastFMData()794 void CMusicPlayerApp::LoadLastFMData() {
795     m_lastfm.LoadData(m_lastfm_path);
796 }
797 
SaveLastFMData()798 void CMusicPlayerApp::SaveLastFMData() {
799     m_lastfm.SaveData(m_lastfm_path);
800 }
801 
UpdateLastFMNowPlaying()802 void CMusicPlayerApp::UpdateLastFMNowPlaying() {
803     AfxBeginThread(UpdateLastFMNowPlayingFunProc, (LPVOID)NULL);
804 }
805 
UpdateLastFMNowPlayingFunProc(LPVOID lpParam)806 UINT CMusicPlayerApp::UpdateLastFMNowPlayingFunProc(LPVOID lpParam) {
807     theApp.m_lastfm.UpdateNowPlaying();
808     return 0;
809 }
810 
UpdateLastFMFavourite(bool favourite)811 void CMusicPlayerApp::UpdateLastFMFavourite(bool favourite) {
812     AfxBeginThread(UpdateLastFMFavouriteFunProc, (LPVOID)favourite);
813 }
814 
UpdateLastFMFavouriteFunProc(LPVOID lpParam)815 UINT CMusicPlayerApp::UpdateLastFMFavouriteFunProc(LPVOID lpParam) {
816     auto favourite = (bool)lpParam;
817     if (favourite) {
818         theApp.m_lastfm.Love();
819     }
820     else {
821         theApp.m_lastfm.Unlove();
822     }
823     return 0;
824 }
825 
LastFMScrobble()826 void CMusicPlayerApp::LastFMScrobble() {
827     AfxBeginThread(LastFMScrobbleFunProc, (LPVOID)NULL);
828 }
829 
LastFMScrobbleFunProc(LPVOID lpParam)830 UINT CMusicPlayerApp::LastFMScrobbleFunProc(LPVOID lpParam) {
831     theApp.m_lastfm.Scrobble();
832     return 0;
833 }
834 
UpdateUiMeidaLibItems()835 void CMusicPlayerApp::UpdateUiMeidaLibItems()
836 {
837     AfxBeginThread([](LPVOID lpParam)->UINT {
838         //如果没有设置“启动时更新媒体库”,才在这里更新Ui中所有曲目和媒体库项目列表,否则在StartUpdateMediaLib中更新
839         if (!theApp.m_media_lib_setting_data.update_media_lib_when_start_up)
840         {
841             CUiMyFavouriteItemMgr::Instance().UpdateMyFavourite();
842             CUiFolderExploreMgr::Instance().UpdateFolders();
843             CUiAllTracksMgr::Instance().UpdateAllTracks();
844             CUiMediaLibItemMgr::Instance().Init();
845         }
846         return 0;
847     }, (LPVOID)NULL);
848 }
849