OSDN Git Service

503889c0eb9dc3698e255475e25580e463aebec2
[lamexp/LameXP.git] / src / Global_Win32.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2014 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version, but always including the *additional*
9 // restrictions defined in the "License.txt" file.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License along
17 // with this program; if not, write to the Free Software Foundation, Inc.,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 //
20 // http://www.gnu.org/licenses/gpl-2.0.txt
21 ///////////////////////////////////////////////////////////////////////////////
22
23 #include "Global.h"
24
25 //Target version
26 #include "Targetver.h"
27
28 //Visual Leaks Detector
29 #include <vld.h>
30
31 //Windows includes
32 #define NOMINMAX
33 #define WIN32_LEAN_AND_MEAN
34 #include <Windows.h>
35 #include <MMSystem.h>
36 #include <ShellAPI.h>
37 #include <SensAPI.h>
38 #include <Objbase.h>
39 #include <PowrProf.h>
40 #include <Psapi.h>
41 #include <dwmapi.h>
42
43 //Qt includes
44 #include <QApplication>
45 #include <QDate>
46 #include <QDir>
47 #include <QEvent>
48 #include <QIcon>
49 #include <QImageReader>
50 #include <QLibrary>
51 #include <QLibraryInfo>
52 #include <QMap>
53 #include <QMessageBox>
54 #include <QPlastiqueStyle>
55 #include <QProcess>
56 #include <QReadWriteLock>
57 #include <QRegExp>
58 #include <QSysInfo>
59 #include <QTextCodec>
60 #include <QTimer>
61 #include <QTranslator>
62 #include <QUuid>
63 #include <QResource>
64
65 //LameXP includes
66 #define LAMEXP_INC_CONFIG
67 #include "Resource.h"
68 #include "Config.h"
69
70 //MUtils
71 #include <MUtils/Global.h>
72 #include <MUtils/OSSupport.h>
73 #include <MUtils/Terminal.h>
74
75 //CRT includes
76 #include <cstdio>
77 #include <iostream>
78 #include <fstream>
79 #include <io.h>
80 #include <fcntl.h>
81 #include <intrin.h>
82 #include <cmath>
83 #include <ctime>
84 #include <process.h>
85 #include <csignal>
86
87 //Initialize static Qt plugins
88 #ifdef QT_NODLL
89 #if QT_VERSION < QT_VERSION_CHECK(5,0,0)
90 Q_IMPORT_PLUGIN(qico)
91 Q_IMPORT_PLUGIN(qsvg)
92 #else
93 Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
94 Q_IMPORT_PLUGIN(QICOPlugin)
95 #endif
96 #endif
97
98 ///////////////////////////////////////////////////////////////////////////////
99 // HELPER MACROS
100 ///////////////////////////////////////////////////////////////////////////////
101
102 #define _LAMEXP_MAKE_STR(STR) #STR
103 #define LAMEXP_MAKE_STR(STR) _LAMEXP_MAKE_STR(STR)
104
105 ///////////////////////////////////////////////////////////////////////////////
106 // GLOBAL VARS
107 ///////////////////////////////////////////////////////////////////////////////
108
109 //Wine detection
110 static struct
111 {
112         bool bInitialized;
113         bool bIsWine;
114         QReadWriteLock lock;
115 }
116 g_lamexp_wine;
117
118 //Win32 Theme support
119 static struct
120 {
121         bool bInitialized;
122         bool bThemesEnabled;
123         QReadWriteLock lock;
124 }
125 g_lamexp_themes_enabled;
126
127 //Win32 DWM API functions
128 static struct
129 {
130         bool bInitialized;
131         HRESULT (__stdcall *dwmIsCompositionEnabled)(BOOL *bEnabled);
132         HRESULT (__stdcall *dwmExtendFrameIntoClientArea)(HWND hWnd, const MARGINS* pMarInset);
133         HRESULT (__stdcall *dwmEnableBlurBehindWindow)(HWND hWnd, const DWM_BLURBEHIND* pBlurBehind);
134         QLibrary *dwmapi_dll;
135         QReadWriteLock lock;
136 }
137 g_lamexp_dwmapi;
138
139 //Sound file cache
140 static struct
141 {
142         QHash<const QString, const unsigned char*> *sound_db;
143         QReadWriteLock lock;
144 }
145 g_lamexp_sounds;
146
147 //Main thread ID
148 static const DWORD g_main_thread_id = GetCurrentThreadId();
149
150 //Localization
151 const char* LAMEXP_DEFAULT_LANGID = "en";
152 const char* LAMEXP_DEFAULT_TRANSLATION = "LameXP_EN.qm";
153
154 ///////////////////////////////////////////////////////////////////////////////
155 // GLOBAL FUNCTIONS
156 ///////////////////////////////////////////////////////////////////////////////
157
158 /*
159  * Convert QIcon to HICON -> caller is responsible for destroying the HICON!
160  */
161 static HICON lamexp_qicon2hicon(const QIcon &icon, const int w, const int h)
162 {
163         if(!icon.isNull())
164         {
165                 QPixmap pixmap = icon.pixmap(w, h);
166                 if(!pixmap.isNull())
167                 {
168                         return pixmap.toWinHICON();
169                 }
170         }
171         return NULL;
172 }
173
174 /*
175  * Check if visual themes are enabled (WinXP and later)
176  */
177 bool lamexp_themes_enabled(void)
178 {
179         typedef int (WINAPI *IsAppThemedFun)(void);
180         
181         QReadLocker readLock(&g_lamexp_themes_enabled.lock);
182         if(g_lamexp_themes_enabled.bInitialized)
183         {
184                 return g_lamexp_themes_enabled.bThemesEnabled;
185         }
186
187         readLock.unlock();
188         QWriteLocker writeLock(&g_lamexp_themes_enabled.lock);
189
190         if(!g_lamexp_themes_enabled.bInitialized)
191         {
192                 g_lamexp_themes_enabled.bThemesEnabled = false;
193                 const MUtils::OS::Version::os_version_t &osVersion = MUtils::OS::os_version();
194                 if(osVersion >= MUtils::OS::Version::WINDOWS_WINXP)
195                 {
196                         IsAppThemedFun IsAppThemedPtr = NULL;
197                         QLibrary uxTheme(QString("%1/UxTheme.dll").arg(MUtils::OS::known_folder(MUtils::OS::FOLDER_SYSTEMFOLDER)));
198                         if(uxTheme.load())
199                         {
200                                 IsAppThemedPtr = (IsAppThemedFun) uxTheme.resolve("IsAppThemed");
201                         }
202                         if(IsAppThemedPtr)
203                         {
204                                 g_lamexp_themes_enabled.bThemesEnabled = IsAppThemedPtr();
205                                 if(!g_lamexp_themes_enabled.bThemesEnabled)
206                                 {
207                                         qWarning("Theme support is disabled for this process!");
208                                 }
209                         }
210                 }
211                 g_lamexp_themes_enabled.bInitialized = true;
212         }
213
214         return g_lamexp_themes_enabled.bThemesEnabled;
215 }
216
217 /*
218  * Block window "move" message
219  */
220 bool lamexp_block_window_move(void *message)
221 {
222         if(message)
223         {
224                 MSG *msg = reinterpret_cast<MSG*>(message);
225                 if((msg->message == WM_SYSCOMMAND) && (msg->wParam == SC_MOVE))
226                 {
227                         return true;
228                 }
229                 if((msg->message == WM_NCLBUTTONDOWN) && (msg->wParam == HTCAPTION))
230                 {
231                         return true;
232                 }
233         }
234         return false;
235 }
236
237 bool lamexp_beep(int beepType)
238 {
239         switch(beepType)
240         {
241                 case lamexp_beep_info:    return MessageBeep(MB_ICONASTERISK) == TRUE;    break;
242                 case lamexp_beep_warning: return MessageBeep(MB_ICONEXCLAMATION) == TRUE; break;
243                 case lamexp_beep_error:   return MessageBeep(MB_ICONHAND) == TRUE;        break;
244                 default: return false;
245         }
246 }
247
248 /*
249  * Play a sound (from resources)
250  */
251 bool lamexp_play_sound(const QString &name, const bool bAsync)
252 {
253         const unsigned char *data = NULL;
254         
255         //Try to look-up the sound in the cache first
256         if(!name.isEmpty())
257         {
258                 QReadLocker readLock(&g_lamexp_sounds.lock);
259                 if(g_lamexp_sounds.sound_db && g_lamexp_sounds.sound_db->contains(name))
260                 {
261                         data = g_lamexp_sounds.sound_db->value(name);
262                 }
263         }
264         
265         //If data not found in cache, try to load from resource!
266         if((!data) && (!name.isEmpty()))
267         {
268                 QResource resource(QString(":/sounds/%1.wav").arg(name));
269                 if(resource.isValid() && (data = resource.data()))
270                 {
271                         QWriteLocker writeLock(&g_lamexp_sounds.lock);
272                         if(!g_lamexp_sounds.sound_db)
273                         {
274                                 g_lamexp_sounds.sound_db = new QHash<const QString, const unsigned char*>();
275                         }
276                         g_lamexp_sounds.sound_db->insert(name, data);
277                 }
278                 else
279                 {
280                         qWarning("Sound effect \"%s\" not found!", MUTILS_UTF8(name));
281                 }
282         }
283
284         //Play the sound, if availbale
285         if(data)
286         {
287                 return PlaySound(LPCWSTR(data), NULL, (SND_MEMORY | (bAsync ? SND_ASYNC : SND_SYNC))) != FALSE;
288         }
289
290         return false;
291 }
292
293 /*
294  * Play a sound (system alias)
295  */
296 bool lamexp_play_sound_alias(const QString &alias, const bool bAsync)
297 {
298         return PlaySound(MUTILS_WCHR(alias), GetModuleHandle(NULL), (SND_ALIAS | (bAsync ? SND_ASYNC : SND_SYNC))) != FALSE;
299 }
300
301 /*
302  * Play a sound (from external DLL)
303  */
304 bool lamexp_play_sound_file(const QString &library, const unsigned short uiSoundIdx, const bool bAsync)
305 {
306         bool result = false;
307
308         QFileInfo libraryFile(library);
309         if(!libraryFile.isAbsolute())
310         {
311                 const QString &systemDir = MUtils::OS::known_folder(MUtils::OS::FOLDER_SYSTEMFOLDER);
312                 if(!systemDir.isEmpty())
313                 {
314                         libraryFile.setFile(QDir(systemDir), libraryFile.fileName());
315                 }
316         }
317
318         if(libraryFile.exists() && libraryFile.isFile())
319         {
320                 if(HMODULE module = LoadLibraryW(MUTILS_WCHR(QDir::toNativeSeparators(libraryFile.canonicalFilePath()))))
321                 {
322                         result = (PlaySound(MAKEINTRESOURCE(uiSoundIdx), module, (SND_RESOURCE | (bAsync ? SND_ASYNC : SND_SYNC))) != FALSE);
323                         FreeLibrary(module);
324                 }
325         }
326         else
327         {
328                 qWarning("PlaySound: File \"%s\" could not be found!", MUTILS_UTF8(libraryFile.absoluteFilePath()));
329         }
330
331         return result;
332 }
333
334 /*
335  * Insert entry to the window's system menu
336  */
337 bool lamexp_append_sysmenu(const QWidget *win, const unsigned int identifier, const QString &text)
338 {
339         bool ok = false;
340         
341         if(HMENU hMenu = GetSystemMenu(win->winId(), FALSE))
342         {
343                 ok = (AppendMenuW(hMenu, MF_SEPARATOR, 0, 0) == TRUE);
344                 ok = (AppendMenuW(hMenu, MF_STRING, identifier, MUTILS_WCHR(text)) == TRUE);
345         }
346
347         return ok;
348 }
349
350 /*
351  * Insert entry to the window's system menu
352  */
353 bool lamexp_check_sysmenu_msg(void *message, const unsigned int identifier)
354 {
355         return (((MSG*)message)->message == WM_SYSCOMMAND) && ((((MSG*)message)->wParam & 0xFFF0) == identifier);
356 }
357
358 /*
359  * Update system menu entry
360  */
361 bool lamexp_update_sysmenu(const QWidget *win, const unsigned int identifier, const QString &text)
362 {
363         bool ok = false;
364         
365         if(HMENU hMenu = ::GetSystemMenu(win->winId(), FALSE))
366         {
367                 ok = (ModifyMenu(hMenu, identifier, MF_STRING | MF_BYCOMMAND, identifier, MUTILS_WCHR(text)) == TRUE);
368         }
369         return ok;
370 }
371
372 /*
373  * Display the window's close button
374  */
375 bool lamexp_enable_close_button(const QWidget *win, const bool bEnable)
376 {
377         bool ok = false;
378
379         if(HMENU hMenu = GetSystemMenu(win->winId(), FALSE))
380         {
381                 ok = (EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | (bEnable ? MF_ENABLED : MF_GRAYED)) == TRUE);
382         }
383
384         return ok;
385 }
386
387 /*
388  * Check whether ESC key has been pressed since the previous call to this function
389  */
390 bool lamexp_check_escape_state(void)
391 {
392         return (GetAsyncKeyState(VK_ESCAPE) & 0x0001) != 0;
393 }
394
395 /*
396  * Bring the specifed window to the front
397  */
398 bool lamexp_bring_to_front(const QWidget *window)
399 {
400         bool ret = false;
401         
402         if(window)
403         {
404                 for(int i = 0; (i < 5) && (!ret); i++)
405                 {
406                         ret = (SetForegroundWindow(window->winId()) != FALSE);
407                         SwitchToThisWindow(window->winId(), TRUE);
408                 }
409                 LockSetForegroundWindow(LSFW_LOCK);
410         }
411
412         return ret;
413 }
414
415 /*
416  * Bring window of the specifed process to the front (callback)
417  */
418 static BOOL CALLBACK lamexp_bring_process_to_front_helper(HWND hwnd, LPARAM lParam)
419 {
420         DWORD processId = *reinterpret_cast<WORD*>(lParam);
421         DWORD windowProcessId = NULL;
422         GetWindowThreadProcessId(hwnd, &windowProcessId);
423         if(windowProcessId == processId)
424         {
425                 SwitchToThisWindow(hwnd, TRUE);
426                 SetForegroundWindow(hwnd);
427                 return FALSE;
428         }
429
430         return TRUE;
431 }
432
433 /*
434  * Bring window of the specifed process to the front
435  */
436 bool lamexp_bring_process_to_front(const unsigned long pid)
437 {
438         return EnumWindows(lamexp_bring_process_to_front_helper, reinterpret_cast<LPARAM>(&pid)) == TRUE;
439 }
440
441 static void lamexp_init_dwmapi(void)
442 {
443         QReadLocker writeLock(&g_lamexp_dwmapi.lock);
444
445         //Not initialized yet?
446         if(g_lamexp_dwmapi.bInitialized)
447         {
448                 return;
449         }
450         
451         //Reset function pointers
452         g_lamexp_dwmapi.dwmIsCompositionEnabled = NULL;
453         g_lamexp_dwmapi.dwmExtendFrameIntoClientArea = NULL;
454         g_lamexp_dwmapi.dwmEnableBlurBehindWindow = NULL;
455                         
456         //Does OS support DWM?
457         const MUtils::OS::Version::os_version_t &osVersion = MUtils::OS::os_version();
458         if(osVersion >= MUtils::OS::Version::WINDOWS_VISTA)
459         {
460                 //Load DWMAPI.DLL
461                 g_lamexp_dwmapi.dwmapi_dll = new QLibrary("dwmapi.dll");
462                 if(g_lamexp_dwmapi.dwmapi_dll->load())
463                 {
464                         //Initialize function pointers
465                         g_lamexp_dwmapi.dwmIsCompositionEnabled      = (HRESULT (__stdcall*)(BOOL*))                       g_lamexp_dwmapi.dwmapi_dll->resolve("DwmIsCompositionEnabled");
466                         g_lamexp_dwmapi.dwmExtendFrameIntoClientArea = (HRESULT (__stdcall*)(HWND, const MARGINS*))        g_lamexp_dwmapi.dwmapi_dll->resolve("DwmExtendFrameIntoClientArea");
467                         g_lamexp_dwmapi.dwmEnableBlurBehindWindow    = (HRESULT (__stdcall*)(HWND, const DWM_BLURBEHIND*)) g_lamexp_dwmapi.dwmapi_dll->resolve("DwmEnableBlurBehindWindow");
468                 }
469                 else
470                 {
471                         MUTILS_DELETE(g_lamexp_dwmapi.dwmapi_dll);
472                         qWarning("Failed to load DWMAPI.DLL on a DWM-enabled system!");
473                 }
474         }
475
476         g_lamexp_dwmapi.bInitialized = true;
477 }
478
479 /*
480  * Enable "sheet of glass" effect on the given Window
481  */
482 bool lamexp_sheet_of_glass(QWidget *window)
483 {
484         QReadLocker readLock(&g_lamexp_dwmapi.lock);
485
486         //Initialize the DWM API
487         while(!g_lamexp_dwmapi.bInitialized)
488         {
489                 readLock.unlock();
490                 lamexp_init_dwmapi();
491                 readLock.relock();
492         }
493
494         BOOL bCompositionEnabled = FALSE;
495
496         //Required functions available?
497         if((g_lamexp_dwmapi.dwmIsCompositionEnabled != NULL) && (g_lamexp_dwmapi.dwmExtendFrameIntoClientArea != NULL) && (g_lamexp_dwmapi.dwmEnableBlurBehindWindow != NULL))
498         {
499                 //Check if composition is currently enabled
500                 if(HRESULT hr = g_lamexp_dwmapi.dwmIsCompositionEnabled(&bCompositionEnabled))
501                 {
502                         qWarning("DwmIsCompositionEnabled function has failed! (error %d)", hr);
503                         return false;
504                 }
505         }
506         
507         //All functions available *and* composition enabled?
508         if(!bCompositionEnabled)
509         {
510                 return false;
511         }
512
513         //Enable the "sheet of glass" effect on this window
514         MARGINS margins = {-1, -1, -1, -1};
515         if(HRESULT hr = g_lamexp_dwmapi.dwmExtendFrameIntoClientArea(window->winId(), &margins))
516         {
517                 qWarning("DwmExtendFrameIntoClientArea function has failed! (error %d)", hr);
518                 return false;
519         }
520
521         //Create and populate the Blur Behind structure
522         DWM_BLURBEHIND bb;
523         memset(&bb, 0, sizeof(DWM_BLURBEHIND));
524         bb.fEnable = TRUE;
525         bb.dwFlags = DWM_BB_ENABLE;
526         if(HRESULT hr = g_lamexp_dwmapi.dwmEnableBlurBehindWindow(window->winId(), &bb))
527         {
528                 qWarning("DwmEnableBlurBehindWindow function has failed! (error %d)", hr);
529                 return false;
530         }
531
532         //Required for Qt
533         window->setAutoFillBackground(false);
534         window->setAttribute(Qt::WA_TranslucentBackground);
535         window->setAttribute(Qt::WA_NoSystemBackground);
536
537         return true;
538 }
539
540 /*
541  * Update "sheet of glass" effect on the given Window
542  */
543 bool lamexp_sheet_of_glass_update(QWidget *window)
544 {
545         QReadLocker readLock(&g_lamexp_dwmapi.lock);
546
547         //Initialize the DWM API
548         while(!g_lamexp_dwmapi.bInitialized)
549         {
550                 readLock.unlock();
551                 lamexp_init_dwmapi();
552                 readLock.relock();
553         }
554
555         BOOL bCompositionEnabled = FALSE;
556
557         //Required functions available?
558         if((g_lamexp_dwmapi.dwmIsCompositionEnabled != NULL) && (g_lamexp_dwmapi.dwmEnableBlurBehindWindow != NULL))
559         {
560                 //Check if composition is currently enabled
561                 if(HRESULT hr = g_lamexp_dwmapi.dwmIsCompositionEnabled(&bCompositionEnabled))
562                 {
563                         qWarning("DwmIsCompositionEnabled function has failed! (error %d)", hr);
564                         return false;
565                 }
566         }
567         
568         //All functions available *and* composition enabled?
569         if(!bCompositionEnabled)
570         {
571                 return false;
572         }
573
574         //Create and populate the Blur Behind structure
575         DWM_BLURBEHIND bb;
576         memset(&bb, 0, sizeof(DWM_BLURBEHIND));
577         bb.fEnable = TRUE;
578         bb.dwFlags = DWM_BB_ENABLE;
579         if(HRESULT hr = g_lamexp_dwmapi.dwmEnableBlurBehindWindow(window->winId(), &bb))
580         {
581                 qWarning("DwmEnableBlurBehindWindow function has failed! (error %d)", hr);
582                 return false;
583         }
584
585         return true;
586 }
587
588 /*
589  * Update the window icon
590  */
591 lamexp_icon_t *lamexp_set_window_icon(QWidget *window, const QIcon &icon, const bool bIsBigIcon)
592 {
593         if(!icon.isNull())
594         {
595                 const int extend = (bIsBigIcon ? 32 : 16);
596                 if(HICON hIcon = lamexp_qicon2hicon(icon, extend, extend))
597                 {
598                         SendMessage(window->winId(), WM_SETICON, (bIsBigIcon ? ICON_BIG : ICON_SMALL), LPARAM(hIcon));
599                         return reinterpret_cast<lamexp_icon_t*>(hIcon);
600                 }
601         }
602         return NULL;
603 }
604
605 /*
606  * Free window icon
607  */
608 void lamexp_free_window_icon(lamexp_icon_t *icon)
609 {
610         if(HICON hIcon = reinterpret_cast<HICON>(icon))
611         {
612                 DestroyIcon(hIcon);
613         }
614 }
615
616 /*
617  * Get system color info
618  */
619 QColor lamexp_system_color(const int color_id)
620 {
621         int nIndex = -1;
622
623         switch(color_id)
624         {
625         case lamexp_syscolor_text:
626                 nIndex = COLOR_WINDOWTEXT;       /*Text in windows*/
627                 break;
628         case lamexp_syscolor_background:
629                 nIndex = COLOR_WINDOW;           /*Window background*/
630                 break;
631         case lamexp_syscolor_caption:
632                 nIndex = COLOR_CAPTIONTEXT;      /*Text in caption, size box, and scroll bar arrow box*/
633                 break;
634         default:
635                 qWarning("Unknown system color id (%d) specified!", color_id);
636                 nIndex = COLOR_WINDOWTEXT;
637         }
638         
639         const DWORD rgb = GetSysColor(nIndex);
640         QColor color(GetRValue(rgb), GetGValue(rgb), GetBValue(rgb));
641         return color;
642 }
643
644 ///////////////////////////////////////////////////////////////////////////////
645 // INITIALIZATION
646 ///////////////////////////////////////////////////////////////////////////////
647
648 extern "C" void _lamexp_global_init_win32(void)
649 {
650         //Zero *before* constructors are called
651         MUTILS_ZERO_MEMORY(g_lamexp_wine);
652         MUTILS_ZERO_MEMORY(g_lamexp_themes_enabled);
653         MUTILS_ZERO_MEMORY(g_lamexp_dwmapi);
654         MUTILS_ZERO_MEMORY(g_lamexp_sounds);
655 }
656
657 ///////////////////////////////////////////////////////////////////////////////
658 // FINALIZATION
659 ///////////////////////////////////////////////////////////////////////////////
660
661 extern "C" void _lamexp_global_free_win32(void)
662 {
663         //Release DWM API
664         g_lamexp_dwmapi.dwmIsCompositionEnabled = NULL;
665         g_lamexp_dwmapi.dwmExtendFrameIntoClientArea = NULL;
666         g_lamexp_dwmapi.dwmEnableBlurBehindWindow = NULL;
667         MUTILS_DELETE(g_lamexp_dwmapi.dwmapi_dll);
668
669         //Clear sound cache
670         MUTILS_DELETE(g_lamexp_sounds.sound_db);
671 }