pi-win-notify 1.0.2 → 1.0.6

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.
@@ -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
- Write-Output ("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
- $wa = [System.Windows.Forms.Screen]::FromPoint($cursor).WorkingArea
245
- $count = inc-stack
246
- $offset = ($count - 1) * 128
247
- $win.Left = $wa.Right - $win.Width - 20
248
- $win.Top = $wa.Bottom - $win.Height - 10 - $offset
249
- l "[ps-stack] count=$count offset=$offset"
250
-
251
- $timer = New-Object System.Windows.Threading.DispatcherTimer
252
- $timer.Interval = [TimeSpan]::FromSeconds($timeout)
253
- $timer.Add_Tick({ $win.Close(); $timer.Stop() })
254
-
255
- $win.Add_Closed({
256
- $timer.Stop()
257
- try { [HotkeyHelper]::UnregisterHotKey((New-Object System.Windows.Interop.WindowInteropHelper($win)).Handle, $HOTKEY_DISMISS) } catch {}
258
- try { [HotkeyHelper]::UnregisterHotKey((New-Object System.Windows.Interop.WindowInteropHelper($win)).Handle, $HOTKEY_FOCUS) } catch {}
259
- dec-stack
260
- $frame.Continue = $false
261
- })
262
-
263
- $timer.Start()
264
- $win.Show()
265
- l '[ps-shown]'
266
-
267
- [HotkeyHelper]::Setup($win, { $win.Close() }, { $win.Close(); Focus-PiTerminal $hwndNum $winTitle }, $HOTKEY_DISMISS, $HOTKEY_FOCUS)
268
- l '[ps-hotkeys-setup]'
269
-
270
- $frame = [System.Windows.Threading.DispatcherFrame]::new($true)
271
- [System.Windows.Threading.Dispatcher]::PushFrame($frame)
272
- l '[ps-done]'
273
- }
274
-
275
- l '[host-ready]'
276
- Write-Output "READY"
277
-
278
- while (($line = [Console]::In.ReadLine()) -ne $null) {
279
- try {
280
- $data = $line | ConvertFrom-Json
281
- Show-Notify $data
282
- Write-Output "OK"
283
- } catch {
284
- l "[host-error] $_"
285
- Write-Output "ERROR:$_"
286
- }
287
- }
288
-
289
- l '[host-exit]'
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
+ Write-Output ("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.2",
4
- "type": "module",
5
- "description": "WPF desktop notification for pi coding agent rich popup with mute, multi-monitor, i18n",
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.6",
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
+ }