Class: WinToaster::Notifier

Inherits:
Object
  • Object
show all
Defined in:
lib/win_toaster/notifier.rb

Overview

Builds a Windows toast notification and shows it by invoking Windows PowerShell (powershell.exe) from WSL. The toast XML and the PowerShell script are Base64-encoded so that arbitrary text (including Japanese and shell metacharacters) survives the WSL -> Windows boundary without any quoting or injection concerns.

Constant Summary collapse

DEFAULT_APP_ID =

Default AppUserModelId. This is Windows PowerShell’s registered id, taken from the reference article. Toasts shown under it appear as coming from “Windows PowerShell”. Override via app_id to use your own.

'{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe'
POWERSHELL =

The Windows PowerShell executable, resolved from PATH inside WSL.

"powershell.exe"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(title:, message:, detail: nil, image: nil, hero: nil, app_id: DEFAULT_APP_ID) ⇒ Notifier

image is shown as the small icon (appLogoOverride) and hero as the large banner image. Both take a file path; from WSL a Linux/WSL path (e.g. /mnt/c/… or /home/…) is accepted and converted to a Windows path automatically.



26
27
28
29
30
31
32
33
# File 'lib/win_toaster/notifier.rb', line 26

def initialize(title:, message:, detail: nil, image: nil, hero: nil, app_id: DEFAULT_APP_ID)
  @title = title
  @message = message
  @detail = detail
  @image = image
  @hero = hero
  @app_id = app_id
end

Instance Attribute Details

#app_idObject (readonly)

Returns the value of attribute app_id.



20
21
22
# File 'lib/win_toaster/notifier.rb', line 20

def app_id
  @app_id
end

#detailObject (readonly)

Returns the value of attribute detail.



20
21
22
# File 'lib/win_toaster/notifier.rb', line 20

def detail
  @detail
end

#heroObject (readonly)

Returns the value of attribute hero.



20
21
22
# File 'lib/win_toaster/notifier.rb', line 20

def hero
  @hero
end

#imageObject (readonly)

Returns the value of attribute image.



20
21
22
# File 'lib/win_toaster/notifier.rb', line 20

def image
  @image
end

#messageObject (readonly)

Returns the value of attribute message.



20
21
22
# File 'lib/win_toaster/notifier.rb', line 20

def message
  @message
end

#titleObject (readonly)

Returns the value of attribute title.



20
21
22
# File 'lib/win_toaster/notifier.rb', line 20

def title
  @title
end

Instance Method Details

#build_xmlObject

Builds the toast XML using the ToastGeneric template. Text nodes are XML-escaped. The detail line and image nodes are omitted when nil.



37
38
39
40
41
42
# File 'lib/win_toaster/notifier.rb', line 37

def build_xml
  nodes = [title, message, detail].compact.map { |t| "<text>#{escape_xml(t)}</text>" }
  nodes << image_node("appLogoOverride", image) if image
  nodes << image_node("hero", hero) if hero
  %(<toast><visual><binding template="ToastGeneric">#{nodes.join}</binding></visual></toast>)
end

#deliverObject

Builds and shows the toast. Returns true on success and raises WinToaster::Error on any failure.



71
72
73
74
75
76
77
78
79
80
81
# File 'lib/win_toaster/notifier.rb', line 71

def deliver
  _stdout, stderr, status = Open3.capture3(*powershell_command)
  unless status.success?
    raise Error, "failed to show toast (exit #{status.exitstatus}): #{stderr.strip}"
  end

  true
rescue Errno::ENOENT
  raise Error, "#{POWERSHELL} not found. win_toaster requires Windows PowerShell, " \
               "so run it from WSL or from Ruby on Windows."
end

#powershell_commandObject

The argv used to launch PowerShell with the script as an EncodedCommand. Using -EncodedCommand avoids shell quoting issues and the UNC-path warning that -File triggers when run from a WSL working directory.



64
65
66
67
# File 'lib/win_toaster/notifier.rb', line 64

def powershell_command
  encoded = [powershell_script.encode("UTF-16LE")].pack("m0")
  [POWERSHELL, "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded]
end

#powershell_scriptObject

The PowerShell script that decodes the XML and shows the toast.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/win_toaster/notifier.rb', line 45

def powershell_script
  xml_b64 = [build_xml.encode("UTF-8")].pack("m0")
  app_id_literal = app_id.gsub("'", "''")
  <<~PS
    $ErrorActionPreference = 'Stop'
    [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
    [Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
    [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType = WindowsRuntime] | Out-Null
    $xmlText = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('#{xml_b64}'))
    $xml = New-Object Windows.Data.Xml.Dom.XmlDocument
    $xml.LoadXml($xmlText)
    $toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
    [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('#{app_id_literal}').Show($toast)
  PS
end