@@ -9,40 +9,40 @@ internal class TaskbarProgress : IDisposable
9
9
// Must be windows 7 or greater
10
10
&& Environment . OSVersion . Version >= new Version ( 6 , 1 ) ;
11
11
12
- private IntPtr consoleWindowHandle = IntPtr . Zero ;
13
- private IntPtr consoleHandle = IntPtr . Zero ;
12
+ private TaskbarProgressCom ? com ;
13
+ private TaskbarProgressTerminal ? terminal ;
14
14
15
- [ DllImport ( "kernel32.dll" ) ]
16
- private static extern IntPtr GetConsoleWindow ( ) ;
17
- [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
18
- private static extern IntPtr GetStdHandle ( int nStdHandle ) ;
15
+ private bool IsEnabled => com != null || terminal != null ;
19
16
20
- private const int STD_OUTPUT_HANDLE = - 11 ;
21
-
22
- internal TaskbarProgress ( )
17
+ internal TaskbarProgress ( TaskbarProgressState initialTaskbarState )
23
18
{
24
19
if ( OsVersionIsSupported )
25
20
{
26
- consoleWindowHandle = GetConsoleWindow ( ) ;
27
- consoleHandle = GetStdHandle ( STD_OUTPUT_HANDLE ) ;
28
- Console . CancelKeyPress += OnConsoleCancelEvent ;
21
+ com = TaskbarProgressCom . MaybeCreateInstanceAndSetInitialState ( initialTaskbarState ) ;
22
+ terminal = TaskbarProgressTerminal . MaybeCreateInstanceAndSetInitialState ( initialTaskbarState ) ;
23
+ if ( IsEnabled )
24
+ {
25
+ Console . CancelKeyPress += OnConsoleCancelEvent ;
26
+ }
29
27
}
30
28
}
31
29
32
30
internal void SetState ( TaskbarProgressState state )
33
31
{
34
- if ( OsVersionIsSupported )
35
- {
36
- TaskbarProgressCom . SetState ( consoleWindowHandle , consoleHandle , state ) ;
37
- }
32
+ com ? . SetState ( state ) ;
33
+ terminal ? . SetState ( state ) ;
38
34
}
39
35
40
36
internal void SetProgress ( float progressValue )
41
37
{
42
- if ( OsVersionIsSupported )
38
+ bool isValidRange = progressValue >= 0 & progressValue <= 1 ;
39
+ if ( ! isValidRange )
43
40
{
44
- TaskbarProgressCom . SetValue ( consoleWindowHandle , consoleHandle , progressValue ) ;
41
+ throw new ArgumentOutOfRangeException ( nameof ( progressValue ) , " progressValue must be between 0 and 1 inclusive." ) ;
45
42
}
43
+ uint value = ( uint ) ( progressValue * 100 ) ;
44
+ com ? . SetValue ( value ) ;
45
+ terminal ? . SetValue ( value ) ;
46
46
}
47
47
48
48
private void OnConsoleCancelEvent ( object sender , ConsoleCancelEventArgs e )
@@ -52,11 +52,12 @@ private void OnConsoleCancelEvent(object sender, ConsoleCancelEventArgs e)
52
52
53
53
public void Dispose ( )
54
54
{
55
- if ( OsVersionIsSupported )
55
+ if ( IsEnabled )
56
56
{
57
- TaskbarProgressCom . SetState ( consoleWindowHandle , consoleHandle , TaskbarProgressState . NoProgress ) ;
58
- consoleWindowHandle = IntPtr . Zero ;
59
- consoleHandle = IntPtr . Zero ;
57
+ com ? . SetState ( TaskbarProgressState . NoProgress ) ;
58
+ terminal ? . SetState ( TaskbarProgressState . NoProgress ) ;
59
+ com = null ;
60
+ terminal = null ;
60
61
Console . CancelKeyPress -= OnConsoleCancelEvent ;
61
62
}
62
63
}
@@ -72,33 +73,8 @@ internal enum TaskbarProgressState
72
73
Warning = Paused
73
74
}
74
75
75
- internal static class TaskbarProgressCom
76
+ internal sealed class TaskbarProgressCom
76
77
{
77
- [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
78
- private static extern bool GetConsoleMode ( IntPtr hConsoleHandle , out ConsoleModes lpMode ) ;
79
- [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
80
- private static extern bool SetConsoleMode ( IntPtr hConsoleHandle , ConsoleModes dwMode ) ;
81
-
82
- [ Flags ]
83
- private enum ConsoleModes : uint
84
- {
85
- ENABLE_PROCESSED_INPUT = 0x0001 ,
86
- ENABLE_LINE_INPUT = 0x0002 ,
87
- ENABLE_ECHO_INPUT = 0x0004 ,
88
- ENABLE_WINDOW_INPUT = 0x0008 ,
89
- ENABLE_MOUSE_INPUT = 0x0010 ,
90
- ENABLE_INSERT_MODE = 0x0020 ,
91
- ENABLE_QUICK_EDIT_MODE = 0x0040 ,
92
- ENABLE_EXTENDED_FLAGS = 0x0080 ,
93
- ENABLE_AUTO_POSITION = 0x0100 ,
94
-
95
- ENABLE_PROCESSED_OUTPUT = 0x0001 ,
96
- ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 ,
97
- ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 ,
98
- DISABLE_NEWLINE_AUTO_RETURN = 0x0008 ,
99
- ENABLE_LVB_GRID_WORLDWIDE = 0x0010
100
- }
101
-
102
78
[ ComImport ]
103
79
[ Guid ( "ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf" ) ]
104
80
[ InterfaceType ( ComInterfaceType . InterfaceIsIUnknown ) ]
@@ -134,64 +110,151 @@ private class TaskbarInstance
134
110
{
135
111
}
136
112
137
- private static readonly ITaskbarList3 s_taskbarInstance = ( ITaskbarList3 ) new TaskbarInstance ( ) ;
113
+ [ DllImport ( "kernel32.dll" ) ]
114
+ private static extern IntPtr GetConsoleWindow ( ) ;
138
115
139
- internal static void SetState ( IntPtr consoleWindowHandle , IntPtr consoleHandle , TaskbarProgressState taskbarState )
116
+ private readonly ITaskbarList3 taskbarInstance ;
117
+ private readonly IntPtr consoleWindowHandle ;
118
+
119
+ private TaskbarProgressCom ( IntPtr handle )
140
120
{
141
- if ( consoleWindowHandle != IntPtr . Zero )
142
- {
143
- s_taskbarInstance . SetProgressState ( consoleWindowHandle , taskbarState ) ;
144
- }
121
+ taskbarInstance = ( ITaskbarList3 ) new TaskbarInstance ( ) ;
122
+ consoleWindowHandle = handle ;
123
+ }
145
124
146
- if ( consoleHandle != IntPtr . Zero )
125
+ internal static TaskbarProgressCom MaybeCreateInstanceAndSetInitialState ( TaskbarProgressState initialTaskbarState )
126
+ {
127
+ try
147
128
{
148
- // Write progress state to console for Windows Terminal (https://github.com/microsoft/terminal/issues/6700).
149
- GetConsoleMode ( consoleHandle , out ConsoleModes previousConsoleMode ) ;
150
- SetConsoleMode ( consoleHandle , ConsoleModes . ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes . ENABLE_PROCESSED_OUTPUT ) ;
151
- switch ( taskbarState )
129
+ IntPtr handle = GetConsoleWindow ( ) ;
130
+ if ( handle == IntPtr . Zero )
152
131
{
153
- case TaskbarProgressState . NoProgress :
154
- Console . Write ( "\x1b ]9;4;0;0\x1b \\ " ) ;
155
- break ;
156
- case TaskbarProgressState . Indeterminate :
157
- Console . Write ( "\x1b ]9;4;3;0\x1b \\ " ) ;
158
- break ;
159
- case TaskbarProgressState . Normal :
160
- // Do nothing, this is set automatically when SetValue is called (and WT has no documented way to set this).
161
- break ;
162
- case TaskbarProgressState . Error :
163
- Console . Write ( "\x1b ]9;4;2;0\x1b \\ " ) ;
164
- break ;
165
- case TaskbarProgressState . Warning :
166
- Console . Write ( "\x1b ]9;4;4;0\x1b \\ " ) ;
167
- break ;
168
- } ;
169
- SetConsoleMode ( consoleHandle , previousConsoleMode ) ;
132
+ return null ;
133
+ }
134
+ var com = new TaskbarProgressCom ( handle ) ;
135
+ com . SetState ( initialTaskbarState ) ;
136
+ return com ;
137
+ }
138
+ // COM may be disabled, in which case this will throw (#2253).
139
+ catch ( NotSupportedException )
140
+ {
141
+ return null ;
170
142
}
171
143
}
172
144
173
- internal static void SetValue ( IntPtr consoleWindowHandle , IntPtr consoleHandle , float progressValue )
145
+ internal void SetState ( TaskbarProgressState taskbarState )
174
146
{
175
- bool isValidRange = progressValue >= 0 & progressValue <= 1 ;
176
- if ( ! isValidRange )
147
+ taskbarInstance . SetProgressState ( consoleWindowHandle , taskbarState ) ;
148
+ }
149
+
150
+ /// <summary>
151
+ /// Sets the progress value out of 100.
152
+ /// </summary>
153
+ internal void SetValue ( uint progressValue )
154
+ {
155
+ taskbarInstance . SetProgressValue ( consoleWindowHandle , progressValue , 100 ) ;
156
+ }
157
+ }
158
+
159
+ internal sealed class TaskbarProgressTerminal
160
+ {
161
+ [ Flags ]
162
+ private enum ConsoleModes : uint
163
+ {
164
+ ENABLE_PROCESSED_INPUT = 0x0001 ,
165
+ ENABLE_LINE_INPUT = 0x0002 ,
166
+ ENABLE_ECHO_INPUT = 0x0004 ,
167
+ ENABLE_WINDOW_INPUT = 0x0008 ,
168
+ ENABLE_MOUSE_INPUT = 0x0010 ,
169
+ ENABLE_INSERT_MODE = 0x0020 ,
170
+ ENABLE_QUICK_EDIT_MODE = 0x0040 ,
171
+ ENABLE_EXTENDED_FLAGS = 0x0080 ,
172
+ ENABLE_AUTO_POSITION = 0x0100 ,
173
+
174
+ ENABLE_PROCESSED_OUTPUT = 0x0001 ,
175
+ ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 ,
176
+ ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 ,
177
+ DISABLE_NEWLINE_AUTO_RETURN = 0x0008 ,
178
+ ENABLE_LVB_GRID_WORLDWIDE = 0x0010
179
+ }
180
+
181
+ [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
182
+ private static extern bool GetConsoleMode ( IntPtr hConsoleHandle , out ConsoleModes lpMode ) ;
183
+ [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
184
+ private static extern bool SetConsoleMode ( IntPtr hConsoleHandle , ConsoleModes dwMode ) ;
185
+ [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
186
+ private static extern IntPtr GetStdHandle ( int nStdHandle ) ;
187
+ private const int STD_OUTPUT_HANDLE = - 11 ;
188
+
189
+ private readonly IntPtr consoleHandle = IntPtr . Zero ;
190
+
191
+ private TaskbarProgressTerminal ( IntPtr handle )
192
+ {
193
+ consoleHandle = handle ;
194
+ }
195
+
196
+ internal static TaskbarProgressTerminal MaybeCreateInstanceAndSetInitialState ( TaskbarProgressState initialTaskbarState )
197
+ {
198
+ IntPtr handle = GetStdHandle ( STD_OUTPUT_HANDLE ) ;
199
+ if ( handle == IntPtr . Zero )
177
200
{
178
- throw new ArgumentOutOfRangeException ( nameof ( progressValue ) , "progressValue must be between 0 and 1 inclusive." ) ;
201
+ return null ;
179
202
}
180
- uint value = ( uint ) ( progressValue * 100 ) ;
181
-
182
- if ( consoleWindowHandle != IntPtr . Zero )
203
+ if ( ! GetConsoleMode ( handle , out ConsoleModes previousConsoleMode )
204
+ || ! SetConsoleMode ( handle , ConsoleModes . ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes . ENABLE_PROCESSED_OUTPUT ) )
183
205
{
184
- s_taskbarInstance . SetProgressValue ( consoleWindowHandle , value , 100 ) ;
206
+ // If we failed to set virtual terminal processing mode, it is likely due to an older Windows version that does not support it,
207
+ // or legacy console. In either case the TaskbarProgressCom will take care of the progress. See https://stackoverflow.com/a/44574463/5703407.
208
+ // If we try to write without VT mode, the sequence will be printed for the user to see, which clutters the output.
209
+ return null ;
185
210
}
211
+ SetStateThenRevertConsoleMode ( handle , initialTaskbarState , previousConsoleMode ) ;
212
+ var terminal = new TaskbarProgressTerminal ( handle ) ;
213
+ terminal . SetState ( initialTaskbarState ) ;
214
+ return terminal ;
215
+ }
186
216
187
- if ( consoleHandle != IntPtr . Zero )
217
+ internal void SetState ( TaskbarProgressState taskbarState )
218
+ {
219
+ // Write progress state to console for Windows Terminal (https://github.com/microsoft/terminal/issues/6700).
220
+ GetConsoleMode ( consoleHandle , out ConsoleModes previousConsoleMode ) ;
221
+ SetConsoleMode ( consoleHandle , ConsoleModes . ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes . ENABLE_PROCESSED_OUTPUT ) ;
222
+ SetStateThenRevertConsoleMode ( consoleHandle , taskbarState , previousConsoleMode ) ;
223
+ }
224
+
225
+ private static void SetStateThenRevertConsoleMode ( IntPtr handle , TaskbarProgressState taskbarState , ConsoleModes previousConsoleMode )
226
+ {
227
+ switch ( taskbarState )
188
228
{
189
- // Write progress sequence to console for Windows Terminal (https://github.com/microsoft/terminal/discussions/14268).
190
- GetConsoleMode ( consoleHandle , out ConsoleModes previousConsoleMode ) ;
191
- SetConsoleMode ( consoleHandle , ConsoleModes . ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes . ENABLE_PROCESSED_OUTPUT ) ;
192
- Console . Write ( $ "\x1b ]9;4;1;{ value } \x1b \\ ") ;
193
- SetConsoleMode ( consoleHandle , previousConsoleMode ) ;
194
- }
229
+ case TaskbarProgressState . NoProgress :
230
+ Console . Write ( "\x1b ]9;4;0;0\x1b \\ " ) ;
231
+ break ;
232
+ case TaskbarProgressState . Indeterminate :
233
+ Console . Write ( "\x1b ]9;4;3;0\x1b \\ " ) ;
234
+ break ;
235
+ case TaskbarProgressState . Normal :
236
+ // Do nothing, this is set automatically when SetValue is called (and WT has no documented way to set this).
237
+ break ;
238
+ case TaskbarProgressState . Error :
239
+ Console . Write ( "\x1b ]9;4;2;0\x1b \\ " ) ;
240
+ break ;
241
+ case TaskbarProgressState . Warning :
242
+ Console . Write ( "\x1b ]9;4;4;0\x1b \\ " ) ;
243
+ break ;
244
+ } ;
245
+ SetConsoleMode ( handle , previousConsoleMode ) ;
246
+ }
247
+
248
+ /// <summary>
249
+ /// Sets the progress value out of 100.
250
+ /// </summary>
251
+ internal void SetValue ( uint progressValue )
252
+ {
253
+ // Write progress sequence to console for Windows Terminal (https://github.com/microsoft/terminal/discussions/14268).
254
+ GetConsoleMode ( consoleHandle , out ConsoleModes previousConsoleMode ) ;
255
+ SetConsoleMode ( consoleHandle , ConsoleModes . ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes . ENABLE_PROCESSED_OUTPUT ) ;
256
+ Console . Write ( $ "\x1b ]9;4;1;{ progressValue } \x1b \\ ") ;
257
+ SetConsoleMode ( consoleHandle , previousConsoleMode ) ;
195
258
}
196
259
}
197
260
}
0 commit comments