pi-win-notify 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -51
- package/README.zh.md +51 -51
- package/extensions/desktop-notify.ts +562 -562
- package/extensions/host.ps1 +290 -289
- package/package.json +34 -34
package/extensions/host.ps1
CHANGED
|
@@ -1,289 +1,290 @@
|
|
|
1
|
-
$ErrorActionPreference = 'Stop'
|
|
2
|
-
[Console]::InputEncoding = [Text.Encoding]::UTF8
|
|
3
|
-
[Console]::OutputEncoding = [Text.Encoding]::UTF8
|
|
4
|
-
$log = "$env:TEMP\pi-notify-debug.log"
|
|
5
|
-
function l($m) { try { "$m" | Out-File -Append -Encoding utf8 $log } catch {} }
|
|
6
|
-
|
|
7
|
-
l '[host-start]'
|
|
8
|
-
|
|
9
|
-
Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase,System.Windows.Forms
|
|
10
|
-
l '[host-assemblies-ok]'
|
|
11
|
-
|
|
12
|
-
# PF: 切回终端
|
|
13
|
-
Add-Type -TypeDefinition @"
|
|
14
|
-
using System;
|
|
15
|
-
using System.Runtime.InteropServices;
|
|
16
|
-
using System.Text;
|
|
17
|
-
public class PF {
|
|
18
|
-
[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
|
|
19
|
-
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
|
|
20
|
-
[DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
|
|
21
|
-
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumCb cb, IntPtr l);
|
|
22
|
-
[DllImport("user32.dll")] public static extern int GetWindowText(IntPtr h, StringBuilder s, int n);
|
|
23
|
-
[DllImport("user32.dll")] public static extern int GetWindowTextLength(IntPtr h);
|
|
24
|
-
public delegate bool EnumCb(IntPtr h, IntPtr l);
|
|
25
|
-
public static string Search;
|
|
26
|
-
public static IntPtr Found = IntPtr.Zero;
|
|
27
|
-
public static string FoundTitle = "";
|
|
28
|
-
public static bool Callback(IntPtr h, IntPtr l) {
|
|
29
|
-
int len = GetWindowTextLength(h);
|
|
30
|
-
if (len > 0) {
|
|
31
|
-
var sb = new StringBuilder(len + 1);
|
|
32
|
-
GetWindowText(h, sb, sb.Capacity);
|
|
33
|
-
string t = sb.ToString();
|
|
34
|
-
if (t.Contains(Search)) { Found = h; FoundTitle = t; return false; }
|
|
35
|
-
}
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
"@
|
|
40
|
-
|
|
41
|
-
# HotkeyHelper: 热键注册 + WPF 窗口绑定
|
|
42
|
-
Add-Type -TypeDefinition @"
|
|
43
|
-
using System;
|
|
44
|
-
using System.Runtime.InteropServices;
|
|
45
|
-
using System.Windows;
|
|
46
|
-
using System.Windows.Interop;
|
|
47
|
-
public class HotkeyHelper {
|
|
48
|
-
[DllImport("user32.dll")] public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
|
|
49
|
-
[DllImport("user32.dll")] public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
|
50
|
-
|
|
51
|
-
public static void Setup(Window win, Action dismiss, Action focus, int idDismiss, int idFocus) {
|
|
52
|
-
var source = PresentationSource.FromVisual(win) as HwndSource;
|
|
53
|
-
if (source == null) return;
|
|
54
|
-
IntPtr hwnd = source.Handle;
|
|
55
|
-
uint MOD_ALT = 0x0001;
|
|
56
|
-
uint VK_OEM_4 = 0xDB;
|
|
57
|
-
uint VK_OEM_6 = 0xDD;
|
|
58
|
-
RegisterHotKey(hwnd, idDismiss, MOD_ALT, VK_OEM_4);
|
|
59
|
-
RegisterHotKey(hwnd, idFocus, MOD_ALT, VK_OEM_6);
|
|
60
|
-
source.AddHook((IntPtr h, int msg, IntPtr wp, IntPtr lp, ref bool handled) => {
|
|
61
|
-
if (msg == 0x0312) {
|
|
62
|
-
int id = wp.ToInt32();
|
|
63
|
-
if (id == idDismiss) { handled = true; win.Dispatcher.Invoke(dismiss); }
|
|
64
|
-
else if (id == idFocus) { handled = true; win.Dispatcher.Invoke(focus); }
|
|
65
|
-
}
|
|
66
|
-
return IntPtr.Zero;
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
"@ -ReferencedAssemblies "WindowsBase","PresentationCore","PresentationFramework","System.Xaml"
|
|
71
|
-
|
|
72
|
-
l '[host-compiled]'
|
|
73
|
-
|
|
74
|
-
function Build-Xaml($opacity, $dismissLabel, $continueLabel) {
|
|
75
|
-
return @"
|
|
76
|
-
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
77
|
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
78
|
-
WindowStyle="None" AllowsTransparency="True" Background="Transparent"
|
|
79
|
-
Topmost="True" ShowActivated="False" ShowInTaskbar="False" ResizeMode="NoResize"
|
|
80
|
-
Opacity="$opacity" Width="380" Height="115">
|
|
81
|
-
<Window.Resources>
|
|
82
|
-
<DropShadowEffect x:Key="shadow" BlurRadius="20" ShadowDepth="2" Opacity="0.3"/>
|
|
83
|
-
</Window.Resources>
|
|
84
|
-
<Border CornerRadius="10" Background="#2B2B2B" BorderBrush="#444" BorderThickness="1"
|
|
85
|
-
Effect="{StaticResource shadow}">
|
|
86
|
-
<Grid Margin="12">
|
|
87
|
-
<Grid.RowDefinitions>
|
|
88
|
-
<RowDefinition Height="Auto"/>
|
|
89
|
-
<RowDefinition Height="*"/>
|
|
90
|
-
<RowDefinition Height="Auto"/>
|
|
91
|
-
</Grid.RowDefinitions>
|
|
92
|
-
<TextBlock Grid.Row="0" x:Name="Title" FontWeight="SemiBold" Foreground="#E0E0E0" FontSize="13"/>
|
|
93
|
-
<TextBlock Grid.Row="1" x:Name="Body" Foreground="#999" FontSize="12" Margin="0,4,0,6" TextWrapping="Wrap" VerticalAlignment="Top" TextTrimming="CharacterEllipsis"/>
|
|
94
|
-
<Grid Grid.Row="2">
|
|
95
|
-
<Grid.ColumnDefinitions>
|
|
96
|
-
<ColumnDefinition Width="*"/>
|
|
97
|
-
<ColumnDefinition Width="Auto"/>
|
|
98
|
-
</Grid.ColumnDefinitions>
|
|
99
|
-
<TextBlock x:Name="Timer" Foreground="#666" FontSize="11" VerticalAlignment="Bottom"/>
|
|
100
|
-
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
|
101
|
-
<Button x:Name="MuteBtn" Width="32" Height="30" Margin="0,0,6,0" Cursor="Hand"
|
|
102
|
-
ToolTip="勿扰">
|
|
103
|
-
<Button.Template>
|
|
104
|
-
<ControlTemplate TargetType="Button">
|
|
105
|
-
<Border CornerRadius="4" Background="#3A3A3A">
|
|
106
|
-
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
|
|
107
|
-
Text="🔕" FontSize="13" Foreground="#AAA"/>
|
|
108
|
-
</Border>
|
|
109
|
-
</ControlTemplate>
|
|
110
|
-
</Button.Template>
|
|
111
|
-
</Button>
|
|
112
|
-
<Button x:Name="DismissBtn" Content="$dismissLabel" Width="78" Height="30" Margin="0,0,10,0" Cursor="Hand">
|
|
113
|
-
<Button.Template>
|
|
114
|
-
<ControlTemplate TargetType="Button">
|
|
115
|
-
<Border CornerRadius="4" Background="#3A3A3A">
|
|
116
|
-
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"
|
|
117
|
-
TextElement.Foreground="#CCC" TextElement.FontSize="12"/>
|
|
118
|
-
</Border>
|
|
119
|
-
</ControlTemplate>
|
|
120
|
-
</Button.Template>
|
|
121
|
-
</Button>
|
|
122
|
-
<Button x:Name="FocusBtn" Content="$continueLabel" Width="78" Height="30" Cursor="Hand">
|
|
123
|
-
<Button.Template>
|
|
124
|
-
<ControlTemplate TargetType="Button">
|
|
125
|
-
<Border CornerRadius="4" Background="#0E639C">
|
|
126
|
-
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"
|
|
127
|
-
TextElement.Foreground="White" TextElement.FontSize="12"/>
|
|
128
|
-
</Border>
|
|
129
|
-
</ControlTemplate>
|
|
130
|
-
</Button.Template>
|
|
131
|
-
</Button>
|
|
132
|
-
</StackPanel>
|
|
133
|
-
</Grid>
|
|
134
|
-
</Grid>
|
|
135
|
-
</Border>
|
|
136
|
-
</Window>
|
|
137
|
-
"@
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function Focus-PiTerminal($hwndNum, $winTitle) {
|
|
141
|
-
l '[focus-called]'
|
|
142
|
-
$h = [IntPtr]::new($hwndNum)
|
|
143
|
-
if ([PF]::IsIconic($h)) { [PF]::ShowWindow($h, 9) }
|
|
144
|
-
$r = [PF]::SetForegroundWindow($h)
|
|
145
|
-
l "[focus-cached-sfg] result=$r"
|
|
146
|
-
if ($r) { return }
|
|
147
|
-
|
|
148
|
-
l "[focus-fallback] searching for '$winTitle'"
|
|
149
|
-
[PF]::Search = $winTitle
|
|
150
|
-
$cb = [PF+EnumCb]{ param($h2,$l2) [PF]::Callback($h2,$l2) }
|
|
151
|
-
[PF]::EnumWindows($cb, [IntPtr]::Zero)
|
|
152
|
-
if ([PF]::Found -ne [IntPtr]::Zero) {
|
|
153
|
-
l "[focus-found] title='$([PF]::FoundTitle)' hwnd=$([PF]::Found)"
|
|
154
|
-
if ([PF]::IsIconic([PF]::Found)) { [PF]::ShowWindow([PF]::Found, 9) }
|
|
155
|
-
[PF]::SetForegroundWindow([PF]::Found) | Out-Null
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
$stackFile = "$env:TEMP\pi-notify-stack.txt"
|
|
160
|
-
|
|
161
|
-
function inc-stack {
|
|
162
|
-
$count = 0
|
|
163
|
-
if (Test-Path $stackFile) { try { $count = [int](Get-Content $stackFile -Raw).Trim() } catch {} }
|
|
164
|
-
$count++
|
|
165
|
-
$count | Out-File -Encoding ascii $stackFile
|
|
166
|
-
return $count
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function dec-stack {
|
|
170
|
-
if (-not (Test-Path $stackFile)) { return }
|
|
171
|
-
try {
|
|
172
|
-
$c = [int](Get-Content $stackFile -Raw).Trim()
|
|
173
|
-
$c--
|
|
174
|
-
if ($c -le 0) { Remove-Item $stackFile -Force } else { $c | Out-File -Encoding ascii $stackFile }
|
|
175
|
-
} catch {}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function Show-Notify($data) {
|
|
179
|
-
$title = $data.title
|
|
180
|
-
$body = $data.body
|
|
181
|
-
$hwndNum = [long]$data.hwnd
|
|
182
|
-
$winTitle= $data.winTitle
|
|
183
|
-
$dismiss = $data.dismissLabel
|
|
184
|
-
$continue= $data.continueLabel
|
|
185
|
-
$timeout = [int]$data.timeoutSec
|
|
186
|
-
$opacity = $data.opacityVal
|
|
187
|
-
$elapsed = $data.elapsedLabel
|
|
188
|
-
|
|
189
|
-
$MOD_ALT = 0x0001; $VK_OEM_4 = 0xDB; $VK_OEM_6 = 0xDD
|
|
190
|
-
$HOTKEY_DISMISS = 1; $HOTKEY_FOCUS = 2
|
|
191
|
-
|
|
192
|
-
$xamlText = Build-Xaml $opacity $dismiss $continue
|
|
193
|
-
$reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]$xamlText)
|
|
194
|
-
$win = [System.Windows.Markup.XamlReader]::Load($reader)
|
|
195
|
-
$reader.Close()
|
|
196
|
-
|
|
197
|
-
$win.FindName('Title').Text = $title
|
|
198
|
-
$win.FindName('Body').Text = $body
|
|
199
|
-
$win.FindName('Timer').Text = $elapsed
|
|
200
|
-
$win.FindName('DismissBtn').Add_Click({ $win.Close() })
|
|
201
|
-
$win.FindName('FocusBtn').Add_Click({ $win.Close(); Focus-PiTerminal $hwndNum $winTitle })
|
|
202
|
-
|
|
203
|
-
# 勿扰菜单
|
|
204
|
-
$popup = New-Object System.Windows.Controls.Primitives.Popup
|
|
205
|
-
$popup.PlacementTarget = $win.FindName('MuteBtn')
|
|
206
|
-
$popup.Placement = [System.Windows.Controls.Primitives.PlacementMode]::Bottom
|
|
207
|
-
$popup.StaysOpen = $false
|
|
208
|
-
$popup.AllowsTransparency = $true
|
|
209
|
-
|
|
210
|
-
$panel = New-Object System.Windows.Controls.StackPanel
|
|
211
|
-
$panel.Background = "#333"
|
|
212
|
-
$panel.MinWidth = 100
|
|
213
|
-
|
|
214
|
-
$opts = @(
|
|
215
|
-
@{Header=$data.mute3Label; Val=3},
|
|
216
|
-
@{Header=$data.mute30Label; Val=30},
|
|
217
|
-
@{Header=$data.mute60Label; Val=60},
|
|
218
|
-
@{Header=$data.muteOffLabel; Val=0}
|
|
219
|
-
)
|
|
220
|
-
foreach ($o in $opts) {
|
|
221
|
-
$btn = New-Object System.Windows.Controls.Button
|
|
222
|
-
$btn.Content = $o.Header
|
|
223
|
-
$btn.Background = "Transparent"
|
|
224
|
-
$btn.Foreground = "#CCC"
|
|
225
|
-
$btn.BorderThickness = 0
|
|
226
|
-
$btn.FontSize = 12
|
|
227
|
-
$btn.HorizontalContentAlignment = "Left"
|
|
228
|
-
$btn.Padding = "10,6"
|
|
229
|
-
$btn.Cursor = "Hand"
|
|
230
|
-
$btn.Tag = $o.Val
|
|
231
|
-
$btn.Add_Click({
|
|
232
|
-
$popup.IsOpen = $false
|
|
233
|
-
$win.Close()
|
|
234
|
-
|
|
235
|
-
}.GetNewClosure())
|
|
236
|
-
$btn.Add_MouseEnter({ $this.Background = "#444" })
|
|
237
|
-
$btn.Add_MouseLeave({ $this.Background = "Transparent" })
|
|
238
|
-
$panel.Children.Add($btn) | Out-Null
|
|
239
|
-
}
|
|
240
|
-
$popup.Child = $panel
|
|
241
|
-
$win.FindName('MuteBtn').Add_Click({ $popup.IsOpen = $true })
|
|
242
|
-
|
|
243
|
-
$cursor = [System.Windows.Forms.Cursor]::Position
|
|
244
|
-
$
|
|
245
|
-
$
|
|
246
|
-
$
|
|
247
|
-
$
|
|
248
|
-
$win.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
$timer
|
|
253
|
-
$timer.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
try { [HotkeyHelper]::UnregisterHotKey((New-Object System.Windows.Interop.WindowInteropHelper($win)).Handle, $
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
$
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
[System.Windows.Threading.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
1
|
+
$ErrorActionPreference = 'Stop'
|
|
2
|
+
[Console]::InputEncoding = [Text.Encoding]::UTF8
|
|
3
|
+
[Console]::OutputEncoding = [Text.Encoding]::UTF8
|
|
4
|
+
$log = "$env:TEMP\pi-notify-debug.log"
|
|
5
|
+
function l($m) { try { "$m" | Out-File -Append -Encoding utf8 $log } catch {} }
|
|
6
|
+
|
|
7
|
+
l '[host-start]'
|
|
8
|
+
|
|
9
|
+
Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase,System.Windows.Forms
|
|
10
|
+
l '[host-assemblies-ok]'
|
|
11
|
+
|
|
12
|
+
# PF: 切回终端
|
|
13
|
+
Add-Type -TypeDefinition @"
|
|
14
|
+
using System;
|
|
15
|
+
using System.Runtime.InteropServices;
|
|
16
|
+
using System.Text;
|
|
17
|
+
public class PF {
|
|
18
|
+
[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
|
|
19
|
+
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
|
|
20
|
+
[DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
|
|
21
|
+
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumCb cb, IntPtr l);
|
|
22
|
+
[DllImport("user32.dll")] public static extern int GetWindowText(IntPtr h, StringBuilder s, int n);
|
|
23
|
+
[DllImport("user32.dll")] public static extern int GetWindowTextLength(IntPtr h);
|
|
24
|
+
public delegate bool EnumCb(IntPtr h, IntPtr l);
|
|
25
|
+
public static string Search;
|
|
26
|
+
public static IntPtr Found = IntPtr.Zero;
|
|
27
|
+
public static string FoundTitle = "";
|
|
28
|
+
public static bool Callback(IntPtr h, IntPtr l) {
|
|
29
|
+
int len = GetWindowTextLength(h);
|
|
30
|
+
if (len > 0) {
|
|
31
|
+
var sb = new StringBuilder(len + 1);
|
|
32
|
+
GetWindowText(h, sb, sb.Capacity);
|
|
33
|
+
string t = sb.ToString();
|
|
34
|
+
if (t.Contains(Search)) { Found = h; FoundTitle = t; return false; }
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
"@
|
|
40
|
+
|
|
41
|
+
# HotkeyHelper: 热键注册 + WPF 窗口绑定
|
|
42
|
+
Add-Type -TypeDefinition @"
|
|
43
|
+
using System;
|
|
44
|
+
using System.Runtime.InteropServices;
|
|
45
|
+
using System.Windows;
|
|
46
|
+
using System.Windows.Interop;
|
|
47
|
+
public class HotkeyHelper {
|
|
48
|
+
[DllImport("user32.dll")] public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
|
|
49
|
+
[DllImport("user32.dll")] public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
|
50
|
+
|
|
51
|
+
public static void Setup(Window win, Action dismiss, Action focus, int idDismiss, int idFocus) {
|
|
52
|
+
var source = PresentationSource.FromVisual(win) as HwndSource;
|
|
53
|
+
if (source == null) return;
|
|
54
|
+
IntPtr hwnd = source.Handle;
|
|
55
|
+
uint MOD_ALT = 0x0001;
|
|
56
|
+
uint VK_OEM_4 = 0xDB;
|
|
57
|
+
uint VK_OEM_6 = 0xDD;
|
|
58
|
+
RegisterHotKey(hwnd, idDismiss, MOD_ALT, VK_OEM_4);
|
|
59
|
+
RegisterHotKey(hwnd, idFocus, MOD_ALT, VK_OEM_6);
|
|
60
|
+
source.AddHook((IntPtr h, int msg, IntPtr wp, IntPtr lp, ref bool handled) => {
|
|
61
|
+
if (msg == 0x0312) {
|
|
62
|
+
int id = wp.ToInt32();
|
|
63
|
+
if (id == idDismiss) { handled = true; win.Dispatcher.Invoke(dismiss); }
|
|
64
|
+
else if (id == idFocus) { handled = true; win.Dispatcher.Invoke(focus); }
|
|
65
|
+
}
|
|
66
|
+
return IntPtr.Zero;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
"@ -ReferencedAssemblies "WindowsBase","PresentationCore","PresentationFramework","System.Xaml"
|
|
71
|
+
|
|
72
|
+
l '[host-compiled]'
|
|
73
|
+
|
|
74
|
+
function Build-Xaml($opacity, $dismissLabel, $continueLabel) {
|
|
75
|
+
return @"
|
|
76
|
+
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
77
|
+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
78
|
+
WindowStyle="None" AllowsTransparency="True" Background="Transparent"
|
|
79
|
+
Topmost="True" ShowActivated="False" ShowInTaskbar="False" ResizeMode="NoResize"
|
|
80
|
+
Opacity="$opacity" Width="380" Height="115">
|
|
81
|
+
<Window.Resources>
|
|
82
|
+
<DropShadowEffect x:Key="shadow" BlurRadius="20" ShadowDepth="2" Opacity="0.3"/>
|
|
83
|
+
</Window.Resources>
|
|
84
|
+
<Border CornerRadius="10" Background="#2B2B2B" BorderBrush="#444" BorderThickness="1"
|
|
85
|
+
Effect="{StaticResource shadow}">
|
|
86
|
+
<Grid Margin="12">
|
|
87
|
+
<Grid.RowDefinitions>
|
|
88
|
+
<RowDefinition Height="Auto"/>
|
|
89
|
+
<RowDefinition Height="*"/>
|
|
90
|
+
<RowDefinition Height="Auto"/>
|
|
91
|
+
</Grid.RowDefinitions>
|
|
92
|
+
<TextBlock Grid.Row="0" x:Name="Title" FontWeight="SemiBold" Foreground="#E0E0E0" FontSize="13"/>
|
|
93
|
+
<TextBlock Grid.Row="1" x:Name="Body" Foreground="#999" FontSize="12" Margin="0,4,0,6" TextWrapping="Wrap" VerticalAlignment="Top" TextTrimming="CharacterEllipsis"/>
|
|
94
|
+
<Grid Grid.Row="2">
|
|
95
|
+
<Grid.ColumnDefinitions>
|
|
96
|
+
<ColumnDefinition Width="*"/>
|
|
97
|
+
<ColumnDefinition Width="Auto"/>
|
|
98
|
+
</Grid.ColumnDefinitions>
|
|
99
|
+
<TextBlock x:Name="Timer" Foreground="#666" FontSize="11" VerticalAlignment="Bottom"/>
|
|
100
|
+
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
|
101
|
+
<Button x:Name="MuteBtn" Width="32" Height="30" Margin="0,0,6,0" Cursor="Hand"
|
|
102
|
+
ToolTip="勿扰">
|
|
103
|
+
<Button.Template>
|
|
104
|
+
<ControlTemplate TargetType="Button">
|
|
105
|
+
<Border CornerRadius="4" Background="#3A3A3A">
|
|
106
|
+
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
|
|
107
|
+
Text="🔕" FontSize="13" Foreground="#AAA"/>
|
|
108
|
+
</Border>
|
|
109
|
+
</ControlTemplate>
|
|
110
|
+
</Button.Template>
|
|
111
|
+
</Button>
|
|
112
|
+
<Button x:Name="DismissBtn" Content="$dismissLabel" Width="78" Height="30" Margin="0,0,10,0" Cursor="Hand">
|
|
113
|
+
<Button.Template>
|
|
114
|
+
<ControlTemplate TargetType="Button">
|
|
115
|
+
<Border CornerRadius="4" Background="#3A3A3A">
|
|
116
|
+
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"
|
|
117
|
+
TextElement.Foreground="#CCC" TextElement.FontSize="12"/>
|
|
118
|
+
</Border>
|
|
119
|
+
</ControlTemplate>
|
|
120
|
+
</Button.Template>
|
|
121
|
+
</Button>
|
|
122
|
+
<Button x:Name="FocusBtn" Content="$continueLabel" Width="78" Height="30" Cursor="Hand">
|
|
123
|
+
<Button.Template>
|
|
124
|
+
<ControlTemplate TargetType="Button">
|
|
125
|
+
<Border CornerRadius="4" Background="#0E639C">
|
|
126
|
+
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"
|
|
127
|
+
TextElement.Foreground="White" TextElement.FontSize="12"/>
|
|
128
|
+
</Border>
|
|
129
|
+
</ControlTemplate>
|
|
130
|
+
</Button.Template>
|
|
131
|
+
</Button>
|
|
132
|
+
</StackPanel>
|
|
133
|
+
</Grid>
|
|
134
|
+
</Grid>
|
|
135
|
+
</Border>
|
|
136
|
+
</Window>
|
|
137
|
+
"@
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function Focus-PiTerminal($hwndNum, $winTitle) {
|
|
141
|
+
l '[focus-called]'
|
|
142
|
+
$h = [IntPtr]::new($hwndNum)
|
|
143
|
+
if ([PF]::IsIconic($h)) { [PF]::ShowWindow($h, 9) }
|
|
144
|
+
$r = [PF]::SetForegroundWindow($h)
|
|
145
|
+
l "[focus-cached-sfg] result=$r"
|
|
146
|
+
if ($r) { return }
|
|
147
|
+
|
|
148
|
+
l "[focus-fallback] searching for '$winTitle'"
|
|
149
|
+
[PF]::Search = $winTitle
|
|
150
|
+
$cb = [PF+EnumCb]{ param($h2,$l2) [PF]::Callback($h2,$l2) }
|
|
151
|
+
[PF]::EnumWindows($cb, [IntPtr]::Zero)
|
|
152
|
+
if ([PF]::Found -ne [IntPtr]::Zero) {
|
|
153
|
+
l "[focus-found] title='$([PF]::FoundTitle)' hwnd=$([PF]::Found)"
|
|
154
|
+
if ([PF]::IsIconic([PF]::Found)) { [PF]::ShowWindow([PF]::Found, 9) }
|
|
155
|
+
[PF]::SetForegroundWindow([PF]::Found) | Out-Null
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
$stackFile = "$env:TEMP\pi-notify-stack.txt"
|
|
160
|
+
|
|
161
|
+
function inc-stack {
|
|
162
|
+
$count = 0
|
|
163
|
+
if (Test-Path $stackFile) { try { $count = [int](Get-Content $stackFile -Raw).Trim() } catch {} }
|
|
164
|
+
$count++
|
|
165
|
+
$count | Out-File -Encoding ascii $stackFile
|
|
166
|
+
return $count
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function dec-stack {
|
|
170
|
+
if (-not (Test-Path $stackFile)) { return }
|
|
171
|
+
try {
|
|
172
|
+
$c = [int](Get-Content $stackFile -Raw).Trim()
|
|
173
|
+
$c--
|
|
174
|
+
if ($c -le 0) { Remove-Item $stackFile -Force } else { $c | Out-File -Encoding ascii $stackFile }
|
|
175
|
+
} catch {}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function Show-Notify($data) {
|
|
179
|
+
$title = $data.title
|
|
180
|
+
$body = $data.body
|
|
181
|
+
$hwndNum = [long]$data.hwnd
|
|
182
|
+
$winTitle= $data.winTitle
|
|
183
|
+
$dismiss = $data.dismissLabel
|
|
184
|
+
$continue= $data.continueLabel
|
|
185
|
+
$timeout = [int]$data.timeoutSec
|
|
186
|
+
$opacity = $data.opacityVal
|
|
187
|
+
$elapsed = $data.elapsedLabel
|
|
188
|
+
|
|
189
|
+
$MOD_ALT = 0x0001; $VK_OEM_4 = 0xDB; $VK_OEM_6 = 0xDD
|
|
190
|
+
$HOTKEY_DISMISS = 1; $HOTKEY_FOCUS = 2
|
|
191
|
+
|
|
192
|
+
$xamlText = Build-Xaml $opacity $dismiss $continue
|
|
193
|
+
$reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]$xamlText)
|
|
194
|
+
$win = [System.Windows.Markup.XamlReader]::Load($reader)
|
|
195
|
+
$reader.Close()
|
|
196
|
+
|
|
197
|
+
$win.FindName('Title').Text = $title
|
|
198
|
+
$win.FindName('Body').Text = $body
|
|
199
|
+
$win.FindName('Timer').Text = $elapsed
|
|
200
|
+
$win.FindName('DismissBtn').Add_Click({ $win.Close() })
|
|
201
|
+
$win.FindName('FocusBtn').Add_Click({ $win.Close(); Focus-PiTerminal $hwndNum $winTitle })
|
|
202
|
+
|
|
203
|
+
# 勿扰菜单
|
|
204
|
+
$popup = New-Object System.Windows.Controls.Primitives.Popup
|
|
205
|
+
$popup.PlacementTarget = $win.FindName('MuteBtn')
|
|
206
|
+
$popup.Placement = [System.Windows.Controls.Primitives.PlacementMode]::Bottom
|
|
207
|
+
$popup.StaysOpen = $false
|
|
208
|
+
$popup.AllowsTransparency = $true
|
|
209
|
+
|
|
210
|
+
$panel = New-Object System.Windows.Controls.StackPanel
|
|
211
|
+
$panel.Background = "#333"
|
|
212
|
+
$panel.MinWidth = 100
|
|
213
|
+
|
|
214
|
+
$opts = @(
|
|
215
|
+
@{Header=$data.mute3Label; Val=3},
|
|
216
|
+
@{Header=$data.mute30Label; Val=30},
|
|
217
|
+
@{Header=$data.mute60Label; Val=60},
|
|
218
|
+
@{Header=$data.muteOffLabel; Val=0}
|
|
219
|
+
)
|
|
220
|
+
foreach ($o in $opts) {
|
|
221
|
+
$btn = New-Object System.Windows.Controls.Button
|
|
222
|
+
$btn.Content = $o.Header
|
|
223
|
+
$btn.Background = "Transparent"
|
|
224
|
+
$btn.Foreground = "#CCC"
|
|
225
|
+
$btn.BorderThickness = 0
|
|
226
|
+
$btn.FontSize = 12
|
|
227
|
+
$btn.HorizontalContentAlignment = "Left"
|
|
228
|
+
$btn.Padding = "10,6"
|
|
229
|
+
$btn.Cursor = "Hand"
|
|
230
|
+
$btn.Tag = $o.Val
|
|
231
|
+
$btn.Add_Click({
|
|
232
|
+
$popup.IsOpen = $false
|
|
233
|
+
$win.Close()
|
|
234
|
+
[Console]::WriteLine("MUTE:" + $this.Tag)
|
|
235
|
+
}.GetNewClosure())
|
|
236
|
+
$btn.Add_MouseEnter({ $this.Background = "#444" })
|
|
237
|
+
$btn.Add_MouseLeave({ $this.Background = "Transparent" })
|
|
238
|
+
$panel.Children.Add($btn) | Out-Null
|
|
239
|
+
}
|
|
240
|
+
$popup.Child = $panel
|
|
241
|
+
$win.FindName('MuteBtn').Add_Click({ $popup.IsOpen = $true })
|
|
242
|
+
|
|
243
|
+
$cursor = [System.Windows.Forms.Cursor]::Position
|
|
244
|
+
$waW = [System.Windows.SystemParameters]::WorkArea.Width
|
|
245
|
+
$waH = [System.Windows.SystemParameters]::WorkArea.Height
|
|
246
|
+
$count = inc-stack
|
|
247
|
+
$offset = ($count - 1) * 128
|
|
248
|
+
$win.Left = $waW - $win.Width - 20
|
|
249
|
+
$win.Top = $waH - $win.Height - 10 - $offset
|
|
250
|
+
l "[ps-stack] count=$count offset=$offset"
|
|
251
|
+
|
|
252
|
+
$timer = New-Object System.Windows.Threading.DispatcherTimer
|
|
253
|
+
$timer.Interval = [TimeSpan]::FromSeconds($timeout)
|
|
254
|
+
$timer.Add_Tick({ $win.Close(); $timer.Stop() })
|
|
255
|
+
|
|
256
|
+
$win.Add_Closed({
|
|
257
|
+
$timer.Stop()
|
|
258
|
+
try { [HotkeyHelper]::UnregisterHotKey((New-Object System.Windows.Interop.WindowInteropHelper($win)).Handle, $HOTKEY_DISMISS) } catch {}
|
|
259
|
+
try { [HotkeyHelper]::UnregisterHotKey((New-Object System.Windows.Interop.WindowInteropHelper($win)).Handle, $HOTKEY_FOCUS) } catch {}
|
|
260
|
+
dec-stack
|
|
261
|
+
$frame.Continue = $false
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
$timer.Start()
|
|
265
|
+
$win.Show()
|
|
266
|
+
l '[ps-shown]'
|
|
267
|
+
|
|
268
|
+
[HotkeyHelper]::Setup($win, { $win.Close() }, { $win.Close(); Focus-PiTerminal $hwndNum $winTitle }, $HOTKEY_DISMISS, $HOTKEY_FOCUS)
|
|
269
|
+
l '[ps-hotkeys-setup]'
|
|
270
|
+
|
|
271
|
+
$frame = [System.Windows.Threading.DispatcherFrame]::new($true)
|
|
272
|
+
[System.Windows.Threading.Dispatcher]::PushFrame($frame)
|
|
273
|
+
l '[ps-done]'
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
l '[host-ready]'
|
|
277
|
+
Write-Output "READY"
|
|
278
|
+
|
|
279
|
+
while (($line = [Console]::In.ReadLine()) -ne $null) {
|
|
280
|
+
try {
|
|
281
|
+
$data = $line | ConvertFrom-Json
|
|
282
|
+
Show-Notify $data
|
|
283
|
+
Write-Output "OK"
|
|
284
|
+
} catch {
|
|
285
|
+
l "[host-error] $_"
|
|
286
|
+
Write-Output "ERROR:$_"
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
l '[host-exit]'
|
package/package.json
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pi-win-notify",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "A Windows notification tool for pi — fast focus, response preview, elapsed time, mute, multi-language",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"pi-package",
|
|
8
|
-
"pi-extension",
|
|
9
|
-
"wpf",
|
|
10
|
-
"notification",
|
|
11
|
-
"windows"
|
|
12
|
-
],
|
|
13
|
-
"author": "ryanchan720",
|
|
14
|
-
"license": "MIT",
|
|
15
|
-
"repository": {
|
|
16
|
-
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/ryanchan720/pi-desktop-notify.git"
|
|
18
|
-
},
|
|
19
|
-
"pi": {
|
|
20
|
-
"extensions": [
|
|
21
|
-
"./extensions"
|
|
22
|
-
]
|
|
23
|
-
},
|
|
24
|
-
"files": [
|
|
25
|
-
"extensions/desktop-notify.ts",
|
|
26
|
-
"extensions/host.ps1",
|
|
27
|
-
"README.md",
|
|
28
|
-
"README.zh.md",
|
|
29
|
-
"package.json"
|
|
30
|
-
],
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"koffi": "^3.0.2"
|
|
33
|
-
}
|
|
34
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-win-notify",
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A Windows notification tool for pi — fast focus, response preview, elapsed time, mute, multi-language",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi-extension",
|
|
9
|
+
"wpf",
|
|
10
|
+
"notification",
|
|
11
|
+
"windows"
|
|
12
|
+
],
|
|
13
|
+
"author": "ryanchan720",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/ryanchan720/pi-desktop-notify.git"
|
|
18
|
+
},
|
|
19
|
+
"pi": {
|
|
20
|
+
"extensions": [
|
|
21
|
+
"./extensions"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"extensions/desktop-notify.ts",
|
|
26
|
+
"extensions/host.ps1",
|
|
27
|
+
"README.md",
|
|
28
|
+
"README.zh.md",
|
|
29
|
+
"package.json"
|
|
30
|
+
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"koffi": "^3.0.2"
|
|
33
|
+
}
|
|
34
|
+
}
|