/
Util.cs
1533 lines (1461 loc) · 76.4 KB
/
Util.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using Microsoft.Win32;
using Power8.DataProviders;
using Power8.Helpers;
using Power8.Properties;
using Power8.Views;
using Application = System.Windows.Forms.Application;
using MessageBox = System.Windows.MessageBox;
using ThreadState = System.Threading.ThreadState;
namespace Power8
{
/// <summary>
/// Contains helping methods
/// </summary>
static class Util
{
/// <summary>
/// The Main UI thread dispatcher. Public since many stuff is related to program shutdown.
/// </summary>
public static Dispatcher MainDisp;
//2-kilobyte string buffer for string operations
private static readonly StringBuilder Buffer = new StringBuilder(1024);
//Singleton window manager
private static readonly Dictionary<Type, IComponent> Instances = new Dictionary<Type, IComponent>();
#region Cross-thread operations
/// <summary>
/// Invokes the delegate on the main UI thread. Execution is immediate, but control is not
/// returned to the caller until the callee is done. The execution priority is set to Render.
/// </summary>
/// <param name="method">Any parameterless action, lambda or delegate</param>
public static void Send(Action method)
{
MainDisp.Invoke(DispatcherPriority.Render, method);
}
/// <summary>
/// Invokes the delegate on the main UI thread. Execution may be delayed, but control is
/// immediately returned to the caller. The execution priority is set to Background.
/// </summary>
/// <param name="method">Any parameterless action, lambda or delegate</param>
public static void Post(Action method)
{
MainDisp.BeginInvoke(DispatcherPriority.Background, method);
}
/// <summary>
/// Executes an Action with almost lowest priority (AppIdle). Use this to free
/// unmanaged resources.
/// </summary>
/// <param name="method">Any parameterless action, lambda or delegate</param>
private static void PostBackgroundReleaseResourceCall(Action method)
{
#if DEBUG
var mName = method.Method.Name; //In debug, we log the resource release usage
method = (Action) Delegate.Combine(method, new Action(() => Log.Raw("...invoked", mName)));
#endif
MainDisp.BeginInvoke(DispatcherPriority.SystemIdle, method);
}
/// <summary>
/// Initiates the procedure of unloading the unmanaged dll, and immediately returns.
/// The resource will be freed likely later, when the Application becomes idle.
/// </summary>
/// <param name="hModule">A handle to the DLL obtained by LoadLibrary() call.</param>
public static void PostBackgroundDllUnload(IntPtr hModule)
{
if (!SettingsManager.Instance.DontFreeLibs)
PostBackgroundReleaseResourceCall(() => API.FreeLibrary(hModule));
}
/// <summary>
/// Initiates the procedure of unloading the unmanaged icon, and immediately returns.
/// The resource will be freed likely later, when the Application becomes idle.
/// </summary>
/// <param name="hIcon">A handle to the icon obtained by LoadIcon() or similar call.</param>
public static void PostBackgroundIconDestroy(IntPtr hIcon)
{
PostBackgroundReleaseResourceCall(() => API.DestroyIcon(hIcon));
}
/// <summary>
/// Executes the function on the main thread with the almost highest priority.
/// The caling thread is blocked until the callee returns the result.
/// </summary>
/// <typeparam name="T">The type of return result.</typeparam>
/// <param name="method">Any parameterless Func, including lambda and the returning delegate</param>
/// <returns>The value returned by the method invoked</returns>
public static T Eval<T>(Func<T> method)
{
return (T) MainDisp.Invoke(DispatcherPriority.DataBind, method);
}
/// <summary>
/// Creates a Thread from a delegate, with the given thread name, wrapping it in the
/// try-catch block, without starting it.
/// The catch calls <code>DispatchUnhandledException()</code>
/// </summary>
/// <param name="method">The delegate which can be used as non-parametrized thread start</param>
/// <param name="name">Optional. Managed name of a Thread being created, default is "P8 forked"</param>
/// <returns>The Thread class instance created, ready to be started</returns>
public static Thread Fork(ThreadStart method, string name = "P8 forked")
{
Log.Raw("Thread forked: " + name);
return new Thread(() =>
{
try
{
method();
}
catch (Exception ex)
{
DispatchUnhandledException(ex);
}
})
{
Name = name,
#if DEBUG
CurrentCulture = Thread.CurrentThread.CurrentCulture,
CurrentUICulture = Thread.CurrentThread.CurrentUICulture
#endif
};
}
/// <summary>
/// A shortcut fo Fork(something).Start()
/// </summary>
/// <param name="method">The delegate which can be used as non-parametrized thread start</param>
/// <param name="name">Optional. Managed name of a Thread being created, default is "P8 forked"</param>
public static void ForkStart(ThreadStart method, string name = "unnamed")
{
Fork(method, name).Start();
}
/// <summary>
/// Unkile ForkStart() or simple Fork(), uses thread pool to lower system load when multiple threads are initialized.
/// </summary>
/// <param name="method">The delegate which can be used as non-parametrized thread start</param>
/// <param name="name">Optional. Managed name of a Thread being created, default is "P8 forked"</param>
public static void ForkPool(ThreadStart method, string name = "unnamed")
{
Log.Raw("Thread forked to pool: " + name);
#if DEBUG
var cc = Thread.CurrentThread.CurrentCulture;
var cuic = Thread.CurrentThread.CurrentUICulture;
ThreadPool.QueueUserWorkItem(state =>
{
try
{
Thread.CurrentThread.CurrentCulture = cc;
Thread.CurrentThread.CurrentUICulture = cuic;
#else
ThreadPool.QueueUserWorkItem(state =>
{
try
{
#endif
Log.Raw("Started execution of task " + name);
method();
}
catch (Exception ex)
{
DispatchUnhandledException(ex);
}
Log.Raw("Done execution of task " + name);
});
}
/// <summary>
/// Forks background thread with given name and starts it, but only in case referenced thread
/// doesn't exist or is stopped already. If the referenced thread is not started - simply starts it.
/// Ignores running threads.
/// </summary>
/// <param name="thread">Thread variable which holds or will hold the thread reference</param>
/// <param name="pFunc">Thread delegate. Not required when <paramref name="thread"/> is already
/// created byt not started.</param>
/// <param name="threadName">Name of newly created thread.</param>
public static void BgrThreadInit(ref Thread thread, ThreadStart pFunc, string threadName)
{
if (thread == null || (thread.ThreadState & ThreadState.Stopped) > 0)
{
thread = Fork(pFunc, threadName);
thread.IsBackground = true;
}
if ((thread.ThreadState & ThreadState.Unstarted) > 0)
{
thread.Start();
}
}
#endregion
#region WPF-User32 interactions
/// <summary>
/// Extension. Returns unmanaged handle for the User32 window that hosts
/// current WPF Window.
/// </summary>
/// <param name="w">The Window to get handle for</param>
/// <returns>IntPtr which represents the HWND of the caller</returns>
public static IntPtr GetHandle(this Window w)
{
return new WindowInteropHelper(w).Handle;
}
/// <summary>
/// Extension. Returns HwndSource object for current WPF Window.
/// </summary>
/// <param name="w">Window to get HwndSource for.</param>
public static HwndSource GetHwndSource(this Window w)
{
return HwndSource.FromHwnd(w.GetHandle());
}
/// <summary>
/// Extension. Makes the Window glassed in the environment which support such action.
/// In case this is not supported, ensures that the Window will have proper background.
/// </summary>
/// <param name="w">The Window that must be glassified</param>
/// <returns>Unmanaged HWND of the glassified window, in the form of IntPtr.
/// Returns NULL (IntPtr.Zero) in case Window has not yet initialized completely.</returns>
public static IntPtr MakeGlassWpfWindow(this Window w)
{
if (!w.IsLoaded)
return IntPtr.Zero;
var source = w.GetHwndSource();
if (OsIs.SevenOrMore && API.DwmIsCompositionEnabled())
{
if (source.CompositionTarget != null)
{
w.Background = Brushes.Transparent;
source.CompositionTarget.BackgroundColor = Colors.Transparent;
}
MakeGlass(source.Handle);
}
else
{
w.Background = w is MainWindow ? SystemColors.ActiveCaptionBrush : SystemColors.ControlBrush;
}
return source.Handle;
}
/// <summary>
/// Calls the DWM API to make the target window, represented by a handle, the glass one.
/// </summary>
/// <param name="hWnd">HWND of a window, in the form of IntPtr</param>
public static void MakeGlass(IntPtr hWnd)
{
var bbhOff = new API.DwmBlurbehind
{
dwFlags = API.DwmBlurbehind.DWM_BB_ENABLE | API.DwmBlurbehind.DWM_BB_BLURREGION,
fEnable = false,
hRegionBlur = IntPtr.Zero
};
API.DwmEnableBlurBehindWindow(hWnd, bbhOff); //vvvHere goes special "MARGIN{-1}" structure;
API.DwmExtendFrameIntoClientArea(hWnd, new API.Margins { cxLeftWidth = -1, cxRightWidth = 0, cyTopHeight = 0, cyBottomHeight = 0 });
}
/// <summary>
/// Extension. For the given Window attaches Message Filter hook to the
/// native WndProc sink of a host native window.
/// </summary>
/// <param name="w">Window whose messages you need to filter</param>
/// <param name="hook"><code>HwndSourceHook</code> instance.</param>
public static void RegisterHook(this Window w, HwndSourceHook hook)
{
w.GetHwndSource().AddHook(hook);
}
/// <summary>
/// Scans the VisualTree of a Window or Control, etc. to find the Visual that complies
/// to the passed parameters. When <code>content</code> parameter is used, the child must
/// be derived from ContentControl to be tested for the value of content.
/// Something like GetWindow()/FindWindowEx() but bit smarter.
/// </summary>
/// <param name="o">The parent of the hierarchy searched.</param>
/// <param name="shortTypeName">Optional. The <code>child.GetType().Name</code> of the
/// required child.</param>
/// <param name="content">The object that shall be reference-equal to the desired
/// Content of the control being searched.</param>
/// <returns>Returns first child available if no parameters specified.
/// When single paramemeter is specified returns first Visual child that
/// satisfies the parameter passed.
/// If no children are available, or noone satisfies the conditions passed,
/// returns null.</returns>
public static DependencyObject GetFirstVisualChildOfTypeByContent
(this DependencyObject o, string shortTypeName = null, object content = null)
{
for (var i = 0; o != null && i < VisualTreeHelper.GetChildrenCount(o); i++)
{
var child = VisualTreeHelper.GetChild(o, i);
if (shortTypeName == null || child.GetType().Name == shortTypeName)
if (content == null || (child is ContentControl && ((ContentControl)child).Content == content))
return child;
}
return null;
}
#endregion
#region Shell items resolution
/// <summary>
/// Returns the target and outs an argument of a shell shortcut, using COM component to get data.
/// </summary>
/// <param name="link">Full path to *.LNK file</param>
/// <param name="argument">The arguments to thew target of link</param>
public static string ResolveLink(string link, out string argument)
{
var shLink = new API.ShellLink();
((API.IPersistFile) shLink).Load(link, 0);
var res = ResolveLink(((API.IShellLink) shLink));
Marshal.FinalReleaseComObject(shLink);
argument = res.Item2;
return res.Item1;
}
/// <summary>
/// Returns the target of a shell shortcut, using COM component to get data.
/// </summary>
/// <param name="link">Full path to *.LNK file</param>
public static string ResolveLink(string link)
{
string dummy;
return ResolveLink(link, out dummy);//The target of Link
}
/// <summary>
/// Extracts data from IShellLink instance, in Unicode format. Does NOT automatically
/// release the COM instance.
/// </summary>
/// <param name="shellLink">Initialized instance of IShellLink, with the data already loaded.</param>
/// <returns>The tuple of 2 strings:
/// - the target of the shortcut (512 characters max);
/// - the command line to the target, except 0th argument (the target itself), also
/// limited to 512 chars max</returns>
public static Tuple<string, string> ResolveLink(API.IShellLink shellLink)
{
lock (Buffer.Clear())
{
API.WIN32_FIND_DATAW sd;
shellLink.GetPath(Buffer, 512, out sd, API.SLGP_FLAGS.SLGP_UNCPRIORITY);
var i1 = Buffer.ToString();
shellLink.GetArguments(Buffer, 512);
return new Tuple<string, string>(i1, Buffer.ToString());
}
}
/// <summary>
/// Cals ResolveLink() swallowing all exceptions inside. Use when you don't care
/// abour the results of such resolution.
/// </summary>
/// <param name="link">Full path to *.LNK file</param>
/// <returns>The target of a shell shortcul on success and null if failed</returns>
public static string ResolveLinkSafe(string link)
{
string res;
try
{
res = ResolveLink(link);
}
catch
#if DEBUG
(Exception ex)
#endif
{
#if DEBUG
Log.Raw("failed safely.\r\n" + ex, link);
#endif
res = null;
}
return res;
}
/// <summary> Gets the path of a known folder (Win7 or higher) </summary>
/// <param name="apiKnFldr">One of GUIDs in <code>API.KnFldrIds</code></param>
/// <returns>FQ-Path of the FS folder for existing initialized Known Folders, or null</returns>
public static string ResolveKnownFolder(string apiKnFldr)
{
IntPtr pwstr;
API.SHGetKnownFolderPath(new Guid(apiKnFldr), API.KFF.NORMAL, IntPtr.Zero, out pwstr);
var res = Marshal.PtrToStringUni(pwstr);
Marshal.FreeCoTaskMem(pwstr);
return res;
}
/// <summary> Gets the path of a special folder </summary>
/// <param name="id">Corresponding folder ID</param>
/// <returns>FQ-Path of the FS folder for existing available Special Folder, or null</returns>
public static string ResolveSpecialFolder(API.Csidl id)
{
lock (Buffer.Clear())
{
API.SHGetSpecialFolderPath(IntPtr.Zero, Buffer, id, false);
return Buffer.ToString();
}
}
/// <summary>
/// Gets the display name of the Special folder, using the desktop.ini configuration
/// and in system locale. For Win7 and upper OS, tries fast shell function first, and
/// if it fails or it is XP, uses general <code>SHGetFileInfo()</code>.
/// </summary>
/// <param name="id">The ID for the folder whose display name is needed.</param>
/// <returns>String, equal to how the folder will be displayed in Explorer,
/// or null if such information is not available.</returns>
public static string ResolveSpecialFolderName(API.Csidl id)
{
var ppIdl = IntPtr.Zero; //Sinse some of special folders are virtual, we'll use PIDLs
var hRes = API.SHGetSpecialFolderLocation(IntPtr.Zero, id, ref ppIdl); //Obtain PIDL to folder
Log.Fmt("SHGetSp.F.Loc. returned result code "+ hRes, id.ToString());
var pwstr = IntPtr.Zero;
var info = new API.ShfileinfoW();
var zeroFails = new IntPtr(1);
//Fast shell call for Win7+
if (hRes == 0 && OsIs.SevenOrMore && API.SHGetNameFromIDList(ppIdl, API.SIGDN.NORMALDISPLAY, ref pwstr) == 0)
{
info.szDisplayName = Marshal.PtrToStringUni(pwstr);
Marshal.FreeCoTaskMem(pwstr);
}
else //If failed or XP
{
zeroFails = (hRes != 0 //if(!SUCCEEDED(SHGetSp.F.Loc.)) return NULL;
? IntPtr.Zero
: API.SHGetFileInfo(ppIdl, 0, ref info, (uint) Marshal.SizeOf(info),
API.Shgfi.DISPLAYNAME | API.Shgfi.PIDL | API.Shgfi.USEFILEATTRIBUTES));
}
Marshal.FreeCoTaskMem(ppIdl);
Log.Raw("ShGetFileInfo returned " + zeroFails, id.ToString());
return zeroFails == IntPtr.Zero ? null : info.szDisplayName;
}
/// <summary>
/// Converts a path with 8.3 styled file- or foldernames to a long one.
/// Or, gets the diplay name for shell namespace guids.
/// </summary>
/// <param name="path">Path to file or folder, either normal one (the function won't
/// do anything), or containing 8.3 styled names, like "C:\Users\MYUSER~1\",
/// or a shell namespage guid(s), one from <code>API.ShNs</code>.</param>
/// <returns>If succeeds, returns expanded FQ-FS-path of a file or folder that was
/// passed containing 8.3-styled names, or returns the display name for shell guid.
/// If fails, returns either original passed data (if no changes were done, and the
/// parameter is simply non-applicable), or null, or empty string in case something
/// gone wrong.</returns>
public static string ResolveLongPathOrDisplayName(string path)
{
if (path.Contains("~") || path.StartsWith("::"))
{
IntPtr ppidl;
lock (Buffer.Clear())
{
API.SFGAO nul;
API.SHParseDisplayName(path, IntPtr.Zero, out ppidl, API.SFGAO.NULL, out nul);
API.SHGetPathFromIDList(ppidl, Buffer);//for 8.3-styled
path = Buffer.ToString();
}
if (string.IsNullOrEmpty(path))//if the 8.3-conversion failed
{
var info = new API.ShfileinfoW();
API.SHGetFileInfo(ppidl, 0, ref info, (uint) Marshal.SizeOf(info),
API.Shgfi.DISPLAYNAME | API.Shgfi.PIDL | API.Shgfi.USEFILEATTRIBUTES);
path = info.szDisplayName;
}
}
return path;
}
/// <summary>
/// Gets Display Name for shell object identified by parsable filesystem path.
/// </summary>
/// <param name="realPath">The physical path of object identifying drive (C:\),
/// folder (C:\folder\) of a file (C:\folder\file.ext)</param>
/// <returns>The disply name of object within Windows Shell. This function does not respect
/// the Explorer settings, such as "Hide drive letters" and similar ones.</returns>
public static string ResolveDisplayName(string realPath)
{
IntPtr pidl;
API.SFGAO dummy;
if (API.SHParseDisplayName(realPath, IntPtr.Zero, out pidl, API.SFGAO.NULL, out dummy) == 0)
{
if (OsIs.SevenOrMore)
{
string name;
if (API.SHGetNameFromIDList(pidl, API.SIGDN.PARENTRELATIVEEDITING, out name) == 0
&& name != null)
{
return name;
}
}
else if (OsIs.XPSp123OrSrv03)
{
var item = API.SHCreateShellItem(IntPtr.Zero, null, pidl);
if (item != null)
{
var name = item.GetDisplayName(API.SIGDN.PARENTRELATIVEEDITING);
if (name != null)
{
Marshal.ReleaseComObject(item);
return name;
}
}
}
}
return null;
}
#endregion
#region Shell items visualizing
/// <summary>
/// Initializes the background thread to display the required special folder,
/// and returns immediately
/// </summary>
public static void DisplaySpecialFolder(API.Csidl id)
{
ForkStart(() => DisplaySpecialFolderSync(id), "Async DisplaySpecialFolder executor");
}
/// <summary>
/// Displays the special folder specified in Explorer window, blocking calling thread until
/// the coresponding window has not <b>begun</b> the displaying the special folder.
/// Uses different strategies for XP and other OSs. For XP, it needs some Explorer window to exist
/// and to be available for shell automation to work seamlessly. In case no such window is available,
/// the new Explorer window is created, with something default inside, and then it is pointed to the
/// desired location. This can cause short blinking of the created window.
/// For Win7 and later, the ExplorerBrowser instance is used, so no blinking and other side effects
/// will be visible.
/// </summary>
/// <param name="id"><code>API.Csidl</code> representing the desired special folder</param>
public static void DisplaySpecialFolderSync(object id)
{
var pidl = IntPtr.Zero;
var res = API.SHGetSpecialFolderLocation(IntPtr.Zero, (API.Csidl)id, ref pidl);
if (res != 0)
{
Log.Raw("Can't SHget folder PIDL, error " + res);
return;
}
if (OsIs.XPOrLess) //Use old awfull shell COM hell from Raymond Chen...
{
API.IShellWindows shWndList = null; //Opened Explorer windows, excl. Desktop, ProgMan...
API.IServiceProvider provider = null; //Browser accessor for Explorer window
API.IShellBrowser browser = null; //The browser that makes possible to kick Explorer
//whereever you need
try
{
shWndList = (API.IShellWindows)new API.ShellWindows();
var wndCount = shWndList.Count; //How many are opened at the moment?
var launchNew = true; //To open our target in new window or in existing one?
if (wndCount == 0) //Will create a window and use it
{
launchNew = false; //Use newly created window
StartExplorer("/N"); //Create a window
while (shWndList.Count == 0) //Wait until dispatch stops snorring...
Thread.Sleep(40);
}
provider = (API.IServiceProvider)shWndList.Item(0); //Get browser from any available wnd
var sidBrowser = new Guid(API.Sys.IdSTopLevelBrowser);
var iidBrowser = new Guid(API.Sys.IdIShellBrowser);
provider.QueryService(ref sidBrowser, ref iidBrowser, out browser);
//Use browser to navigate where needed
browser.BrowseObject(pidl, launchNew ? API.SBSP.NEWBROWSER : API.SBSP.SAMEBROWSER);
}
catch
#if DEBUG
(Exception e)
#endif
{
#if DEBUG
Log.Raw(e.ToString());
#endif
}
finally //Cleanup
{
if(shWndList != null)
Marshal.ReleaseComObject(shWndList);
if(provider != null)
Marshal.ReleaseComObject(provider);
if(browser != null)
Marshal.ReleaseComObject(browser);
Marshal.FreeCoTaskMem(pidl);
}
}
else //Modern approach
{
API.IExplorerBrowser browser = null;
try
{ //Create a browser directly and go...
browser = (API.IExplorerBrowser)new API.ExplorerBrowser();
browser.BrowseToIDList(pidl, API.SBSP.NEWBROWSER);
}
catch
#if DEBUG
(Exception e)
#endif
{
#if DEBUG
Log.Raw(e.ToString());
#endif
}
finally //Cleanup
{
if (browser != null)
Marshal.ReleaseComObject(browser);
Marshal.FreeCoTaskMem(pidl);
}
}
}
/// <summary>
/// Opens Explorer window for the folder that can be represented by a path,
/// or a default folder if no command specified,
/// or simply starts Explorer if it's dead and no command specified
/// </summary>
/// <param name="command">Optional. FQ-path of a FS folder to display.</param>
public static void StartExplorer(string command = null)
{
const string explorerString = "explorer.exe";
CreateProcess(explorerString, command);
}
/// <summary>
/// Displays in Explorer the folder that is a container for the FS element
/// specified as parameter, and makes explorer select the mentioned FS element.
/// </summary>
/// <param name="objectToSelect">FQ-path to the file or a folder on FS</param>
public static void StartExplorerSelect(string objectToSelect)
{
StartExplorer("/select,\"" + objectToSelect + "\"");
}
/// <summary>
/// Creates an instance of the Power8 class according t the parameters passed. If the class
/// is a WPF Window or a WinForms form, displays it by calling <code>Show()</code>. The class
/// must implement the <code>IComponent</code> interface to be created via this method. Only
/// one instance of the class may exist at the same time. Use this method with classes inherited
/// from <code>DisposableWindow</code> to create singleton WPF windows, which will be activated
/// when user clicks again on their related button or menu item.
/// </summary>
/// <param name="className">Half-optional. Either this parameter or <code>t</code> must be
/// specified. The FQ-class name, such that can be passed as parameter to
/// <code>Type.GetType()</code>, for example, "Power8.Views.UltraWnd". Null by default.</param>
/// <param name="t">Half-optional. Either this parameter or a valid <code>className</code>
/// must be specified. The type of the class being created. Null by default.</param>
/// <param name="ctor">Optional. The function that will return the instance of the class. If
/// not specified, the <code>Activator.CreateInstance(t)</code> will be executed. Passing only
/// this parameter is not enough even if the delegate is able to produce a valid instance.</param>
public static void InstanciateClass(string className = null, Type t = null, Func<IComponent> ctor = null)
{
try
{ //Testing parameters
if (t == null && !string.IsNullOrEmpty(className))
t = Type.GetType(className);
if (t == null)
throw new Exception(NoLoc.Err_GotNoTypeObject);
if (!t.GetInterfaces().Contains(typeof(IComponent)))
throw new Exception(NoLoc.Err_TypeIsNotIComponent);
IComponent inst;
if (Instances.ContainsKey(t)) //If object of this type already exists, just use it
{
inst = Instances[t];
}
else //Create it
{
inst = ctor != null ? ctor() : (IComponent)Activator.CreateInstance(t);
//We'll automaticaly remove the object when it's not needed anymore
inst.Disposed += (sender, args) => Instances.Remove(sender.GetType());
Instances.Add(t, inst);
}
var wnd = inst as Window;
if (wnd != null) //Show the Window
{
if (wnd.IsVisible) wnd.Hide(); //XP hack
wnd.Show();
if (wnd.WindowState == WindowState.Minimized)
wnd.WindowState = WindowState.Normal;
wnd.Activate();
return;
}
var frm = inst as Form;
if (frm != null) //Show the Form
{
if (frm.Visible) frm.Hide(); //XP hack
frm.Show();
if (frm.WindowState == FormWindowState.Minimized)
frm.WindowState = FormWindowState.Normal;
frm.Activate();
}
}
catch (Exception ex) //User won't ever see this I believe... so we can use not the Dispatch... methods
{
MessageBox.Show(string.Format(Resources.Err_CantInstanciateClassFormatString, className, ex.Message));
}
}
/// <summary>
/// Creates a process, from just command (e.g. exe to launch or url to open or file to load, etc.) or from command
/// (application) and command line, or from complete startup information. Command or startInfo must be set, but only
/// one of them must be.
/// Before creating the process, updates own envioronment to comply with Explorer. This means whenever you change
/// environment variables in system properties dialog, new processes launched will have them already updated/added.
/// </summary>
/// <param name="command">Application to launch, documanet to open, etc.
/// See <code>Process.Start(string)</code> for details.</param>
/// <param name="args">Argiments passed to application launched when <code>command</code> points to one.
/// See <code>Process.Start(string, string)</code> for details.</param>
/// <param name="startInfo">Complete startup information for process.
/// See <code>Process.Start(StartupInfo)</code> for details.</param>
public static void CreateProcess(string command = null, string args = null, ProcessStartInfo startInfo = null)
{
//validate
if (command == null && startInfo == null)
throw new Exception("CreateProcess: both process start info and command are null!");
if (command != null && startInfo != null)
throw new Exception("CreateProcess: launch mode undefined: both start info and command are set!");
//update variables
UpdateEnvironment();
if (OsIs.EightOrMore && TryLaunchMetroApp(command ?? startInfo.FileName))
{//check if the app is Metro-style one and launch it correspondingly.
return;
}
//run
if (command != null)
{
if (args != null)
Process.Start(command, args);
else
Process.Start(command);
}
else
{
Process.Start(startInfo);
}
}
/// <summary>
/// This method tries launching ModernUI application using it's exe, htm or link to one as a source.
/// It automatically resolves it's identity, AppId and finally queries AppUserModelId to activate one.
/// If any data is unavailable the launch doesn't occur.
/// </summary>
/// <param name="targ">Path to exe, htm or lnk file pointing to exe or htm</param>
/// <returns>True if OS ActivationManager reported that app was launched successfully,
/// false if any required information was not found or ActivationManager failed.</returns>
public static bool TryLaunchMetroApp(string targ)
{
string appUserModelId;
try
{
// ReSharper disable AssignNullToNotNullAttribute
//First, try locating the manifest that contains identity and ID
const string mxml = ImmersiveAppsProvider.APPXMANIFEST_XML;
const StringComparison caseless = StringComparison.OrdinalIgnoreCase;
Log.Raw("Searching for manifest", targ);
var manifest = Path.Combine(Path.GetDirectoryName(targ), mxml);
if (!File.Exists(manifest) && targ.EndsWith(".lnk", caseless))
{
targ = ResolveLinkSafe(targ);
manifest = Path.Combine(Path.GetDirectoryName(targ), mxml);
}
// ReSharper restore AssignNullToNotNullAttribute
if (!File.Exists(manifest))
{
Log.Raw("No manifest found!", targ);
return false;
}
var app = ImmersiveAppsProvider.GetAppsCache()
.Where(a => Path.Combine(a.ApplicationPath, mxml).Equals(manifest, caseless))
.SingleOrDefault(a => targ.EndsWith(a.File, caseless));
if (app == null)
{
Log.Raw("Target app wasn't discovered, defaulting to regular shell launch", targ);
return false;
}
appUserModelId = app.AppUserModelID;
}
catch (Exception ex)
{
DispatchCaughtException(ex);
return false;
}
if (appUserModelId == null)
{//no Windows registry entries found describing the app or it's not activatable
Log.Raw("Failed to retrieve AppUserModelId", targ);
return false;
}
return TryInvokeAppUserModelId(appUserModelId, targ);
}
public static bool TryInvokeAppUserModelId(string aumid, string targ = null)
{
Log.Raw("Creating ActivationManager", targ);
var muiActivator = (API.IApplicationActivationManager)new API.ApplicationActivationManager();
try //activate = launch; separate try because we need finally here
{
Log.Raw("Launching AppX for AUMID " + aumid, targ);
/*var pid = */
muiActivator.ActivateApplication(aumid, string.Empty, API.ACTIVATEOPTIONS.AO_NONE);
Log.Raw("Done OK ", targ);
return true;
}
catch (Exception ex)
{
DispatchCaughtException(ex);
return false;
}
finally
{
Marshal.FinalReleaseComObject(muiActivator);
}
}
/// <summary>
/// Updates environment variables from those stored by Explorer in registry, so Power8 has same environment
/// variables as Explorer does. Call this method before direct or indirect creation of new child processes.
/// </summary>
public static void UpdateEnvironment()
{
var keys = new[]
{
new {Key = Registry.LocalMachine, Path = @"SYSTEM\CurrentControlSet\Control\Session Manager\"},
new {Key = Registry.CurrentUser, Path = string.Empty}
};
Log.Raw("Starting process");
foreach (var key in keys)
{
using (var k = key.Key.OpenSubKey(key.Path + "Environment"))
{
Log.Fmt("For key={0}, k = {1}", key.Key, (k == null ? "" : "not ") + "null");
if (k == null) continue;
foreach (var valueName in k.GetValueNames().Where(v => !string.IsNullOrWhiteSpace(v)))
{
var value = k.GetValue(valueName);
if (value == null)
continue;
var sValue = value.ToString();
if (Environment.GetEnvironmentVariable(valueName) == sValue)
continue;
Environment.SetEnvironmentVariable(valueName, sValue);
Log.Fmt("Updated variable '{0}' to '{1}'", valueName, value);
}
}
}
Log.Raw("Done");
}
/// <summary>
/// Launches a system-default browser opening the Power8 web site
/// </summary>
public static void OpenPower8WebSite()
{
CreateProcess(NoLoc.Stg_Power8URI);
}
#endregion
#region Registry data resolution
/// <summary>
/// Gets a resource id string representing a kind of description for object
/// represented by a guid, either directly in form of a CLSID or in a form
/// of a shell namespace description.
/// </summary>
/// <param name="clsidOrApiShNs">Guid, guid with braces, shell namespace from
/// <code>API.ShNs</code>. The path or file extension won't work, because the
/// description for them is stored a bit differently; the function may be
/// enhanced in future to support files/extensions as well.</param>
/// <param name="fallbackToInfoTip">If set to true, the function tries to get
/// also "InfoTip" value, if "LocalizedString" is not available.
/// If in this case the InfoTip is also unavailable, function checks if the
/// default value (fallback description for class) contains any reasonable data
/// and returns it if so. Such data may be directly put to FriendlyName, or
/// may be stored to ResourceIdString to be transferrd to FriendlyName automatically
/// when required. Util supports plane text stored as ResourceId and puts it
/// to FriendlyName.</param>
/// <returns>Resource Id that, when resolved, will contain the description
/// for the class or shell namespace represented by passed parameter, or
/// plane resource text that should be directly (or indirectly - by storing
/// in ResourceIdString) transferred to FriendlyName.</returns>
public static string GetLocalizedStringResourceIdForClass(string clsidOrApiShNs, bool fallbackToInfoTip = false)
{
var ls = GetResourceIdForClassCommon(clsidOrApiShNs, "", "LocalizedString");
if(ls == null && fallbackToInfoTip)
{
ls = GetResourceIdForClassCommon(clsidOrApiShNs, "", "InfoTip") ??
GetResourceIdForClassCommon(clsidOrApiShNs, "", ""); //Fallback description
if(string.IsNullOrWhiteSpace(ls)) //don't need empty or space resId
ls = null;
}
return ls;
}
/// <summary>
/// Gets a resource id string pointing to a icon for object
/// represented by a guid, either directly in form of a CLSID or in a form
/// of a shell namespace description, or by a file name with extension.
/// </summary>
/// <param name="clsidOrApiShNs">Guid, guid with braces, shell namespace from
/// <code>API.ShNs</code>, filename with extension, either with path or without.</param>
/// <returns>Resource Id that, when resolved, will return the icon
/// for the class or shell namespace represented by passed parameter</returns>
public static string GetDefaultIconResourceIdForClass(string clsidOrApiShNs)
{
return GetResourceIdForClassCommon(clsidOrApiShNs, "\\DefaultIcon", "");
}
/// <summary>
/// Gets a pair of strings that work as open command for object
/// represented by a guid, either directly in form of a CLSID or in a form
/// of a shell namespace description, or by a file name with extension.
/// In case of file, the command may contain enumerated parameters, like "%1".
/// </summary>
/// <param name="clsidOrApiShNs">Guid, guid with braces, shell namespace from
/// <code>API.ShNs</code>, filename with extension, either with path or without.</param>
/// <returns>Tuple, where Item1 is an executable application, and Item2 is a
/// comand for this application passed to launch the given object.</returns>
public static Tuple<string, string> GetOpenCommandForClass(string clsidOrApiShNs)
{
var command = GetResourceIdForClassCommon(clsidOrApiShNs, "\\Shell\\Open\\Command", "");
return CommandToFilenameAndArgs(command);
}
/// <summary>
/// Properly registered CPLs contain "system name", a string that, when passed to
/// "control.exe /name {sysname}" launches this CPL item. This function returns
/// such name for a CPL represented by a guid or a shell namespace poiner.
/// </summary>
/// <param name="clsidOrApiShNs">Guid, guid with braces, shell namespace from
/// <code>API.ShNs</code> with a guid appended. Parameter must represent the
/// Control Panel Item.</param>
/// <returns>String that is a system regisered name for a cpl.</returns>
public static string GetCplAppletSysNameForClass(string clsidOrApiShNs)
{
return GetResourceIdForClassCommon(clsidOrApiShNs, "", "System.ApplicationName");
}
/// <summary>
/// Helper function that finds a proper registry key for an object passed
/// and gets the string placed under that key, described by other parameters.
/// </summary>
/// <param name="clsidOrApiShNs">Guid, guid with braces, shell namespace from
/// <code>API.ShNs</code>, filename with extension, either with path or without.</param>
/// <param name="subkey">Subkey name where to find information, relative to the
/// location in the registry where the class description for specified object
/// resides. May be null or empty. If not, shall start with a backslash,
/// e.g. "\DefaultIcon".</param>
/// <param name="valueName">Name of the registry value that contains the desired
/// data. May be empty, which means default value ("@"), but must not be null.</param>
/// <returns>String contained in the registry value located as escribed by
/// parameters passed, without any conversion done on it. If fails, returns null.</returns>
private static string GetResourceIdForClassCommon(string clsidOrApiShNs, string subkey, string valueName)
{//That's a simple routine: get container-open key-open value-return
// ReSharper disable EmptyGeneralCatchClause
if (!string.IsNullOrEmpty(clsidOrApiShNs))
{
try
{
var key = GetRegistryContainer(clsidOrApiShNs);
if (key != null)
{
using (var k = Registry.ClassesRoot.OpenSubKey(key + subkey, false))
{
if (k != null)
return ((string) k.GetValue(valueName, null));
}
}
}
#if DEBUG
catch (Exception ex)
{
Log.Raw(ex.Message, clsidOrApiShNs);
}
#else
catch (Exception){}
#endif
}
// ReSharper restore EmptyGeneralCatchClause
return null;
}
/// <summary>
/// Returns the registry key located under HKCR, but withour explicit "HKCR"
/// mentioning, which contains descriptive data for a class represented by
/// a guid, shell namespace pointer, or a file with extension.
/// </summary>
/// <param name="pathClsidGuidOrApishnamespace">Guid, or guid with braces, shell
/// namespace from <code>API.ShNs</code>, shell namespace with additional \guid
/// appended, with 2 colons ("::") or without, a filename with extension, either
/// with path or without.</param>
/// <returns>See Summary on what is rerturned on success. Null is returned on
/// failure.</returns>
private static string GetRegistryContainer(string pathClsidGuidOrApishnamespace)
{ //file?
var i = pathClsidGuidOrApishnamespace.LastIndexOf(".", StringComparison.Ordinal);
if (i < 0) //not file
{ //vvvThis will parse guid
return "CLSID\\" + NameSpaceToGuidWithBraces(pathClsidGuidOrApishnamespace);
}
//file