oc-tweaks 0.5.2 → 0.6.0

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.
Files changed (3) hide show
  1. package/README.md +10 -0
  2. package/dist/index.js +156 -26
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -89,6 +89,11 @@ Customize the appearance of the WPF notification window.
89
89
  | `shadow` | `true` | Enable or disable the drop shadow effect. |
90
90
  | `idleColor` | `"#4ADE80"` | Accent color for idle (success) notifications. |
91
91
  | `errorColor` | `"#EF4444"` | Accent color for error notifications. |
92
+ | `fadeOut` | `true` | Animate opacity from `initialOpacity` to `finalOpacity` over `duration`. |
93
+ | `initialOpacity` | `0.85` | Window opacity at start (0–1). |
94
+ | `finalOpacity` | `0.05` | Window opacity at end before auto-close (0–1). |
95
+ | `clickThrough` | `true` | Mouse clicks pass through the window (WS_EX_TRANSPARENT). Hover over the ✕ zone to dismiss. |
96
+ | `hoverDismissMs` | `400` | Milliseconds the cursor must dwell on the ✕ zone to dismiss the notification. |
92
97
 
93
98
  #### `notify.toolCall` (Windows WPF)
94
99
 
@@ -305,6 +310,11 @@ bunx oc-tweaks init
305
310
  | `shadow` | `true` | 启用或禁用下拉阴影效果。 |
306
311
  | `idleColor` | `"#4ADE80"` | 空闲 (成功) 通知的强调色。 |
307
312
  | `errorColor` | `"#EF4444"` | 错误通知的强调色。 |
313
+ | `fadeOut` | `true` | 在 `duration` 时间内从 `initialOpacity` 渐隐到 `finalOpacity`。 |
314
+ | `initialOpacity` | `0.85` | 窗口初始不透明度 (0–1)。 |
315
+ | `finalOpacity` | `0.05` | 自动关闭前的最终不透明度 (0–1)。 |
316
+ | `clickThrough` | `true` | 鼠标点击穿透窗口 (WS_EX_TRANSPARENT)。将鼠标悬停在 ✕ 区域即可关闭。 |
317
+ | `hoverDismissMs` | `400` | 光标在 ✕ 区域停留多少毫秒后关闭通知。 |
308
318
 
309
319
  #### `notify.toolCall` (Windows WPF)
310
320
 
package/dist/index.js CHANGED
@@ -662,6 +662,11 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
662
662
  const shadow = style?.shadow !== false;
663
663
  const idleColor = style?.idleColor ?? "#4ADE80";
664
664
  const errorColor = style?.errorColor ?? "#EF4444";
665
+ const fadeOut = style?.fadeOut !== false;
666
+ const initialOpacity = style?.initialOpacity ?? 0.85;
667
+ const finalOpacity = style?.finalOpacity ?? 0.05;
668
+ const clickThrough = style?.clickThrough !== false;
669
+ const hoverDismissMs = style?.hoverDismissMs ?? 400;
665
670
  const contentMaxWidth = Math.max(160, width - 120);
666
671
  const accentColor = renderOptions?.accentColor ?? (tag === "Error" ? errorColor : idleColor);
667
672
  const icon = renderOptions?.icon ?? (tag === "Error" ? "❌" : "✅");
@@ -677,8 +682,19 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
677
682
  const messageLimit = typeof renderOptions?.maxMessageLength === "number" && renderOptions.maxMessageLength > 0 ? renderOptions.maxMessageLength : 400;
678
683
  const normalizedMessage = renderOptions?.preserveMessageFormatting ? message : cleanMarkdown(message);
679
684
  const textB64 = escapeForPowerShell(truncateText(normalizedMessage, messageLimit));
680
- const shadowXaml = shadow ? '<Border.Effect><DropShadowEffect BlurRadius="20" ShadowDepth="2" Opacity="0.7" Color="Black"/></Border.Effect>' : "";
681
- const dismissHintXaml = showDismissHint ? ` <TextBlock Text="Click to dismiss" Foreground="#555555" FontSize="9" Margin="0,4,0,0"/>` : "";
685
+ const shadowXaml = shadow ? '<Border.Effect><DropShadowEffect BlurRadius="16" ShadowDepth="0" Opacity="0.4" Color="Black"/></Border.Effect>' : "";
686
+ const closeBtnXaml = clickThrough ? [
687
+ ` <Border Name="CloseBg" CornerRadius="4" Width="22" Height="22"`,
688
+ ` HorizontalAlignment="Right" VerticalAlignment="Top"`,
689
+ ` Margin="0,6,6,0" Background="Transparent">`,
690
+ ` <TextBlock Name="CloseBtn" Text="&#x2715;" FontSize="13"`,
691
+ ` Foreground="#2A2A3A"`,
692
+ ` HorizontalAlignment="Center" VerticalAlignment="Center"`,
693
+ ` Margin="0,-1,0,0"/>`,
694
+ ` </Border>`
695
+ ].join(`
696
+ `) : "";
697
+ const dismissHintXaml = showDismissHint && !clickThrough ? ` <TextBlock Text="Click to dismiss" Foreground="#555555" FontSize="9" Margin="0,4,0,0"/>` : "";
682
698
  const xaml = [
683
699
  `<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"`,
684
700
  ` xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"`,
@@ -693,7 +709,7 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
693
709
  ` ${shadowXaml}`,
694
710
  ` <Grid>`,
695
711
  ` <Border CornerRadius="${borderRadius},0,0,${borderRadius}" Width="${colorBarWidth}" HorizontalAlignment="Left" Name="ColorBar"/>`,
696
- ` <StackPanel Orientation="Horizontal" Margin="22,12,16,12" VerticalAlignment="Center">`,
712
+ ` <StackPanel Orientation="Horizontal" Margin="22,12,${clickThrough ? 50 : 16},12" VerticalAlignment="Center">`,
697
713
  ` <TextBlock Name="IconText" FontSize="${iconFontSize}" VerticalAlignment="Center" Margin="0,0,15,0" Foreground="White"/>`,
698
714
  ` <StackPanel VerticalAlignment="Center" MaxWidth="${contentMaxWidth}">`,
699
715
  ` <TextBlock Name="TitleText" FontSize="${titleFontSize}" FontWeight="SemiBold" TextWrapping="Wrap"/>`,
@@ -701,6 +717,7 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
701
717
  dismissHintXaml,
702
718
  ` </StackPanel>`,
703
719
  ` </StackPanel>`,
720
+ closeBtnXaml,
704
721
  ` </Grid>`,
705
722
  ` </Border>`,
706
723
  `</Window>`
@@ -711,13 +728,55 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
711
728
  `$window.Left = ${resolvedPosition.leftExpr}`,
712
729
  `$window.Top = ${resolvedPosition.topExpr}`
713
730
  ] : [];
714
- const psScript = [
715
- "Add-Type -AssemblyName PresentationFramework",
716
- "Add-Type -AssemblyName PresentationCore",
717
- "Add-Type -AssemblyName WindowsBase",
718
- "Add-Type -AssemblyName System.Windows.Forms",
719
- "$targetScreen = [System.Windows.Forms.Screen]::FromPoint([System.Windows.Forms.Cursor]::Position).WorkingArea",
731
+ const win32TypeDef = clickThrough ? [
732
+ "Add-Type -TypeDefinition @'",
733
+ "using System;",
734
+ "using System.Runtime.InteropServices;",
735
+ "",
736
+ "[StructLayout(LayoutKind.Sequential)]",
737
+ "public struct RECT { public int Left, Top, Right, Bottom; }",
738
+ "",
739
+ "[StructLayout(LayoutKind.Sequential)]",
740
+ "public struct POINT { public int X, Y; }",
741
+ "",
742
+ "public static class VDesktop {",
743
+ ' [DllImport("user32.dll", SetLastError = true)]',
744
+ " public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);",
745
+ "",
746
+ ' [DllImport("user32.dll", SetLastError = true)]',
747
+ " public static extern int GetWindowLong(IntPtr hWnd, int nIndex);",
720
748
  "",
749
+ ' [DllImport("user32.dll")]',
750
+ " public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);",
751
+ "",
752
+ ' [DllImport("user32.dll")]',
753
+ " public static extern bool GetCursorPos(out POINT lpPoint);",
754
+ "",
755
+ " public const int GWL_EXSTYLE = -20;",
756
+ " public const int WS_EX_TOOLWINDOW = 0x00000080;",
757
+ " public const int WS_EX_NOACTIVATE = 0x08000000;",
758
+ " public const int WS_EX_APPWINDOW = 0x00040000;",
759
+ " public const int WS_EX_TRANSPARENT = 0x00000020;",
760
+ "",
761
+ " public static void MakeGlobalWindow(IntPtr hwnd, bool transparent) {",
762
+ " int style = GetWindowLong(hwnd, GWL_EXSTYLE);",
763
+ " style = style | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE;",
764
+ " style = style & ~WS_EX_APPWINDOW;",
765
+ " if (transparent) { style = style | WS_EX_TRANSPARENT; }",
766
+ " else { style = style & ~WS_EX_TRANSPARENT; }",
767
+ " SetWindowLong(hwnd, GWL_EXSTYLE, style);",
768
+ " }",
769
+ "",
770
+ " public static bool IsCursorInTopRight(IntPtr hwnd, int zoneW, int zoneH) {",
771
+ " RECT r; POINT p;",
772
+ " if (!GetWindowRect(hwnd, out r)) return false;",
773
+ " if (!GetCursorPos(out p)) return false;",
774
+ " return (p.X >= r.Right - zoneW && p.X <= r.Right &&",
775
+ " p.Y >= r.Top && p.Y <= r.Top + zoneH);",
776
+ " }",
777
+ "}",
778
+ "'@ -ErrorAction SilentlyContinue"
779
+ ] : [
721
780
  "Add-Type -TypeDefinition @'",
722
781
  "using System;",
723
782
  "using System.Runtime.InteropServices;",
@@ -734,14 +793,95 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
734
793
  " public const int WS_EX_NOACTIVATE = 0x08000000;",
735
794
  " public const int WS_EX_APPWINDOW = 0x00040000;",
736
795
  "",
737
- " public static void MakeGlobalWindow(IntPtr hwnd) {",
796
+ " public static void MakeGlobalWindow(IntPtr hwnd, bool transparent) {",
738
797
  " int style = GetWindowLong(hwnd, GWL_EXSTYLE);",
739
798
  " style = style | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE;",
740
799
  " style = style & ~WS_EX_APPWINDOW;",
741
800
  " SetWindowLong(hwnd, GWL_EXSTYLE, style);",
742
801
  " }",
743
802
  "}",
744
- "'@ -ErrorAction SilentlyContinue",
803
+ "'@ -ErrorAction SilentlyContinue"
804
+ ];
805
+ const hoverThreshold = Math.max(1, Math.round(hoverDismissMs / 100));
806
+ const hoverDismissLines = clickThrough ? [
807
+ "",
808
+ "$script:hoverTicks = 0",
809
+ `$hoverThreshold = ${hoverThreshold}`,
810
+ "$brushConv = [System.Windows.Media.BrushConverter]::new()",
811
+ "$closeBtnEl = $window.FindName('CloseBtn')",
812
+ "$closeBgEl = $window.FindName('CloseBg')",
813
+ "",
814
+ "$hitTimer = New-Object System.Windows.Threading.DispatcherTimer",
815
+ "$hitTimer.Interval = [TimeSpan]::FromMilliseconds(100)",
816
+ "$hitTimer.Add_Tick({",
817
+ " if ($script:hwnd -eq [IntPtr]::Zero) { return }",
818
+ " $inZone = [VDesktop]::IsCursorInTopRight($script:hwnd, 80, 50)",
819
+ " if ($inZone) {",
820
+ " $script:hoverTicks++",
821
+ " $closeBtnEl.Foreground = $brushConv.ConvertFromString('#FFFFFF')",
822
+ " $closeBgEl.Background = $brushConv.ConvertFromString('#CC3344')",
823
+ " if ($script:hoverTicks -ge $hoverThreshold) {",
824
+ " $hitTimer.Stop()",
825
+ " $window.Close()",
826
+ " }",
827
+ " } else {",
828
+ " $script:hoverTicks = 0",
829
+ " $closeBtnEl.Foreground = $brushConv.ConvertFromString('#2A2A3A')",
830
+ " $closeBgEl.Background = $brushConv.ConvertFromString('Transparent')",
831
+ " }",
832
+ "})"
833
+ ] : [];
834
+ const loadedLines = clickThrough ? [
835
+ "$window.Add_Loaded({",
836
+ " $script:hwnd = (New-Object System.Windows.Interop.WindowInteropHelper($window)).Handle",
837
+ " [VDesktop]::MakeGlobalWindow($script:hwnd, $true)",
838
+ ...fadeOut ? [
839
+ ` $fadeAnim = New-Object System.Windows.Media.Animation.DoubleAnimation`,
840
+ ` $fadeAnim.From = ${initialOpacity}`,
841
+ ` $fadeAnim.To = ${finalOpacity}`,
842
+ ` $fadeAnim.Duration = [System.Windows.Duration]::new([TimeSpan]::FromMilliseconds($duration))`,
843
+ ` $fadeAnim.Add_Completed({ $window.Close() })`,
844
+ ` $window.BeginAnimation([System.Windows.UIElement]::OpacityProperty, $fadeAnim)`
845
+ ] : [],
846
+ " $hitTimer.Start()",
847
+ "})",
848
+ "",
849
+ "$window.Add_Closed({ $hitTimer.Stop() })"
850
+ ] : [
851
+ "$window.Add_Loaded({",
852
+ " $script:hwnd = (New-Object System.Windows.Interop.WindowInteropHelper($window)).Handle",
853
+ " [VDesktop]::MakeGlobalWindow($script:hwnd, $false)",
854
+ ...fadeOut ? [
855
+ ` $fadeAnim = New-Object System.Windows.Media.Animation.DoubleAnimation`,
856
+ ` $fadeAnim.From = ${initialOpacity}`,
857
+ ` $fadeAnim.To = ${finalOpacity}`,
858
+ ` $fadeAnim.Duration = [System.Windows.Duration]::new([TimeSpan]::FromMilliseconds($duration))`,
859
+ ` $fadeAnim.Add_Completed({ $window.Close() })`,
860
+ ` $window.BeginAnimation([System.Windows.UIElement]::OpacityProperty, $fadeAnim)`
861
+ ] : [],
862
+ "})"
863
+ ];
864
+ const timerLines = !fadeOut ? [
865
+ "",
866
+ "if ($duration -gt 0) {",
867
+ " $timer = New-Object System.Windows.Threading.DispatcherTimer",
868
+ " $timer.Interval = [TimeSpan]::FromMilliseconds($duration)",
869
+ " $timer.Add_Tick({",
870
+ " $window.Close()",
871
+ " $timer.Stop()",
872
+ " })",
873
+ " $timer.Start()",
874
+ "}"
875
+ ] : [];
876
+ const clickDismissLine = !clickThrough ? "$window.Add_MouseLeftButtonDown({ $window.Close() })" : "";
877
+ const psScript = [
878
+ "Add-Type -AssemblyName PresentationFramework",
879
+ "Add-Type -AssemblyName PresentationCore",
880
+ "Add-Type -AssemblyName WindowsBase",
881
+ "Add-Type -AssemblyName System.Windows.Forms",
882
+ "$targetScreen = [System.Windows.Forms.Screen]::FromPoint([System.Windows.Forms.Cursor]::Position).WorkingArea",
883
+ "",
884
+ ...win32TypeDef,
745
885
  "",
746
886
  `$title = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${titleB64}'))`,
747
887
  `$text = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${textB64}'))`,
@@ -756,6 +896,7 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
756
896
  "$reader = New-Object System.Xml.XmlNodeReader $xaml",
757
897
  "$window = [Windows.Markup.XamlReader]::Load($reader)",
758
898
  ...manualPositionLines,
899
+ ...fadeOut ? [`$window.Opacity = ${initialOpacity}`] : [],
759
900
  "",
760
901
  "$colorBar = $window.FindName('ColorBar')",
761
902
  "$iconText = $window.FindName('IconText')",
@@ -768,22 +909,11 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
768
909
  "$titleText.Foreground = [System.Windows.Media.BrushConverter]::new().ConvertFromString($accentColor)",
769
910
  "$contentText.Text = $text",
770
911
  "",
771
- "$window.Add_MouseLeftButtonDown({ $window.Close() })",
772
- "",
773
- "$window.Add_Loaded({",
774
- " $hwnd = (New-Object System.Windows.Interop.WindowInteropHelper($window)).Handle",
775
- " [VDesktop]::MakeGlobalWindow($hwnd)",
776
- "})",
912
+ clickDismissLine,
913
+ ...hoverDismissLines,
777
914
  "",
778
- "if ($duration -gt 0) {",
779
- " $timer = New-Object System.Windows.Threading.DispatcherTimer",
780
- " $timer.Interval = [TimeSpan]::FromMilliseconds($duration)",
781
- " $timer.Add_Tick({",
782
- " $window.Close()",
783
- " $timer.Stop()",
784
- " })",
785
- " $timer.Start()",
786
- "}",
915
+ ...loadedLines,
916
+ ...timerLines,
787
917
  "",
788
918
  "$window.ShowActivated = $false",
789
919
  "$window.Show()",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-tweaks",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/index.js"