PoshCode Logo PowerShell Code Repository

PowerShell CMatrix by Oisin Grehan 6 years ago
View followups from Gowrish Gates and Claudia | embed code: <script type="text/javascript" src="http://PoshCode.org/embed/2412"></script>download | new post

A pure console screen saver in the vein of the popular CMatrix x-term screensaver. PowerShell 2.0 module, see blog post: http://goo.gl/5QkI5

  1. Set-StrictMode -off
  2.  
  3. #
  4. # Module: PowerShell Console ScreenSaver Version 0.1
  5. # Author: Oisin Grehan ( http://www.nivot.org )
  6. #
  7. # A PowerShell CMatrix-style screen saver for true-console hosts.
  8. #
  9. # This will not work in Micrisoft's ISE, Quest's PowerGUI or other graphical hosts.
  10. # It should work fine in PowerShell+ from Idera which is a true console.
  11. #
  12.  
  13. if ($host.ui.rawui.windowsize -eq $null) {
  14.     write-warning "Sorry, I only work in a true console host like powershell.exe."
  15.     throw
  16. }
  17.  
  18. #
  19. # Console Utility Functions
  20. #
  21.  
  22. function New-Size {
  23.     param([int]$width, [int]$height)
  24.    
  25.     new-object System.Management.Automation.Host.Size $width,$height
  26. }
  27.  
  28. function New-Rectangle {
  29.     param(
  30.         [int]$left,
  31.         [int]$top,
  32.         [int]$right,
  33.         [int]$bottom
  34.     )
  35.    
  36.     $rect = new-object System.Management.Automation.Host.Rectangle
  37.     $rect.left= $left
  38.     $rect.top = $top
  39.     $rect.right =$right
  40.     $rect.bottom = $bottom
  41.    
  42.     $rect
  43. }
  44.  
  45. function New-Coordinate {
  46.     param([int]$x, [int]$y)
  47.    
  48.     new-object System.Management.Automation.Host.Coordinates $x, $y
  49. }
  50.  
  51. function Get-BufferCell {
  52.     param([int]$x, [int]$y)
  53.    
  54.     $rect = new-rectangle $x $y $x $y
  55.    
  56.     [System.Management.Automation.Host.buffercell[,]]$cells = $host.ui.RawUI.GetBufferContents($rect)    
  57.    
  58.     $cells[0,0]
  59. }
  60.  
  61. function Set-BufferCell {
  62.     [outputtype([System.Management.Automation.Host.buffercell])]
  63.     param(
  64.         [int]$x,
  65.         [int]$y,
  66.         [System.Management.Automation.Host.buffercell]$cell
  67.     )
  68.    
  69.     $rect = new-rectangle $x $y $x $y
  70.        
  71.     # return previous
  72.     get-buffercell $x $y
  73.  
  74.     # use "fill" overload with single cell rect    
  75.     $host.ui.rawui.SetBufferContents($rect, $cell)
  76. }
  77.  
  78. function New-BufferCell {
  79.     param(
  80.         [string]$Character,
  81.         [consolecolor]$ForeGroundColor = $(get-buffercell 0 0).foregroundcolor,
  82.         [consolecolor]$BackGroundColor = $(get-buffercell 0 0).backgroundcolor,
  83.         [System.Management.Automation.Host.BufferCellType]$BufferCellType = "Complete"
  84.     )
  85.    
  86.     $cell = new-object System.Management.Automation.Host.BufferCell
  87.     $cell.Character = $Character
  88.     $cell.ForegroundColor = $foregroundcolor
  89.     $cell.BackgroundColor = $backgroundcolor
  90.     $cell.BufferCellType = $buffercelltype
  91.    
  92.     $cell
  93. }
  94.  
  95. function log {
  96.     param($message)
  97.     [diagnostics.debug]::WriteLine($message, "PS ScreenSaver")
  98. }
  99.  
  100. #
  101. # Main entry point for starting the animation
  102. #
  103.  
  104. function Start-CMatrix {
  105.     param(
  106.         [int]$maxcolumns = 8,
  107.         [int]$frameWait = 100
  108.     )
  109.  
  110.     $script:winsize = $host.ui.rawui.WindowSize
  111.     $script:columns = @{} # key: xpos; value; column
  112.     $script:framenum = 0
  113.        
  114.     $prevbg = $host.ui.rawui.BackgroundColor
  115.     $host.ui.rawui.BackgroundColor = "black"
  116.     cls
  117.    
  118.     $done = $false        
  119.    
  120.     while (-not $done) {
  121.  
  122.         Write-FrameBuffer -maxcolumns $maxcolumns
  123.  
  124.         Show-FrameBuffer
  125.        
  126.         sleep -milli $frameWait
  127.        
  128.         $done = $host.ui.rawui.KeyAvailable        
  129.     }
  130.    
  131.     $host.ui.rawui.BackgroundColor = $prevbg
  132.     cls
  133. }
  134.  
  135. # TODO: actually write into buffercell[,] framebuffer
  136. function Write-FrameBuffer {
  137.     param($maxColumns)
  138.  
  139.     # do we need a new column?
  140.     if ($columns.count -lt $maxcolumns) {
  141.        
  142.         # incur staggering of columns with get-random
  143.         # by only adding a new one 50% of the time
  144.         if ((get-random -min 0 -max 10) -lt 5) {
  145.            
  146.             # search for a column not current animating
  147.             do {
  148.                 $x = get-random -min 0 -max ($winsize.width - 1)
  149.             } while ($columns.containskey($x))
  150.            
  151.             $columns.add($x, (new-column $x))
  152.            
  153.         }
  154.     }
  155.    
  156.     $script:framenum++
  157. }
  158.  
  159. # TODO: setbuffercontent with buffercell[,] framebuffer
  160. function Show-FrameBuffer {
  161.     param($frame)
  162.    
  163.     $completed=@()
  164.    
  165.     # loop through each active column and animate a single step/frame
  166.     foreach ($entry in $columns.getenumerator()) {
  167.        
  168.         $column = $entry.value
  169.    
  170.         # if column has finished animating, add to the "remove" pile
  171.         if (-not $column.step()) {            
  172.             $completed += $entry.key
  173.         }
  174.     }
  175.    
  176.     # cannot remove from collection while enumerating, so do it here
  177.     foreach ($key in $completed) {
  178.         $columns.remove($key)
  179.     }    
  180. }
  181.  
  182. function New-Column {
  183.     param($x)
  184.    
  185.     # return a new module that represents the column of letters and its state
  186.     # we also pass in a reference to the main screensaver module to be able to
  187.     # access our console framebuffer functions.
  188.    
  189.     new-module -ascustomobject -name "col_$x" -script {
  190.         param(
  191.             [int]$startx,
  192.             [PSModuleInfo]$parentModule
  193.          )
  194.        
  195.         $script:xpos = $startx
  196.         $script:ylimit = $host.ui.rawui.WindowSize.Height
  197.  
  198.         [int]$script:head = 1
  199.         [int]$script:fade = 0
  200.         [int]$script:fadelen = [math]::Abs($ylimit / 3)
  201.        
  202.         $script:fadelen += (get-random -min 0 -max $fadelen)
  203.        
  204.         function Step {
  205.            
  206.             # reached the bottom yet?
  207.             if ($head -lt $ylimit) {
  208.  
  209.                 & $parentModule Set-BufferCell $xpos $head (
  210.                     & $parentModule New-BufferCell -Character `
  211.                         ([char](get-random -min 65 -max 122)) -Fore white) > $null
  212.                
  213.                 & $parentModule Set-BufferCell $xpos ($head - 1) (
  214.                     & $parentModule New-BufferCell -Character `
  215.                         ([char](get-random -min 65 -max 122)) -Fore green) > $null
  216.                
  217.                 $script:head++
  218.             }
  219.            
  220.             # time to start rendering the darker green "tail?"
  221.             if ($head -gt $fadelen) {
  222.  
  223.                 & $parentModule Set-BufferCell $xpos $fade (
  224.                     & $parentModule New-BufferCell -Character `
  225.                         ([char](get-random -min 65 -max 122)) -Fore darkgreen) > $null
  226.                    
  227.                 $script:fade++
  228.             }
  229.            
  230.             # are we done animating?
  231.             if ($fade -lt $ylimit) {
  232.                 return $true
  233.             }
  234.                        
  235.             $false            
  236.         }
  237.                
  238.         Export-ModuleMember -function Step
  239.        
  240.     } -args $x, $executioncontext.sessionstate.module
  241. }
  242.  
  243. function Start-ScreenSaver {
  244.    
  245.     # feel free to tweak maxcolumns and frame delay
  246.     # currently 8 columns with 50ms wait
  247.    
  248.     Start-CMatrix -max 8 -frame 50
  249. }
  250.  
  251. function Register-Timer {
  252.  
  253.     # prevent prompt from reregistering if explicit disable
  254.     if ($_ssdisabled) {
  255.         return
  256.     }
  257.    
  258.     if (-not (Test-Path variable:global:_ssjob)) {
  259.        
  260.         # register our counter job
  261.         $global:_ssjob = Register-ObjectEvent $_sstimer elapsed -action {
  262.            
  263.             $global:_sscount++;
  264.             $global:_sssrcid = $event.sourceidentifier
  265.                
  266.             # hit timeout yet?
  267.             if ($_sscount -eq $_sstimeout) {
  268.                
  269.                 # disable this event (prevent choppiness)
  270.                 Unregister-Event -sourceidentifier $_sssrcid
  271.                 Remove-Variable _ssjob -scope Global
  272.                            
  273.                 sleep -seconds 1
  274.                      
  275.                 # start ss
  276.                 Start-ScreenSaver
  277.             }
  278.  
  279.         }
  280.     }
  281. }
  282.  
  283. function Enable-ScreenSaver {
  284.    
  285.     if (-not $_ssdisabled) {
  286.         write-warning "Screensaver is not disabled."
  287.         return
  288.     }
  289.    
  290.     $global:_ssdisabled = $false    
  291. }
  292.  
  293. function Disable-ScreenSaver {
  294.  
  295.     if ((Test-Path variable:global:_ssjob)) {
  296.  
  297.         $global:_ssdisabled = $true
  298.         Unregister-Event -SourceIdentifier $_sssrcid        
  299.         Remove-Variable _ssjob -Scope global        
  300.  
  301.     } else {
  302.         write-warning "Screen saver is not enabled."
  303.     }
  304. }
  305.  
  306. function Get-ScreenSaverTimeout {
  307.     new-timespan -seconds $global:_sstimeout
  308. }
  309.  
  310. function Set-ScreenSaverTimeout {
  311.     [cmdletbinding(defaultparametersetname="int")]
  312.     param(
  313.         [parameter(position=0, mandatory=$true, parametersetname="int")]
  314.         [int]$Seconds,
  315.        
  316.         [parameter(position=0, mandatory=$true, parametersetname="timespan")]
  317.         [Timespan]$Timespan
  318.     )
  319.    
  320.     if ($pscmdlet.parametersetname -eq "int") {
  321.         $timespan = new-timespan -seconds $Seconds
  322.     }
  323.    
  324.     if ($timespan.totalseconds -lt 1) {
  325.         throw "Timeout must be greater than 0 seconds."
  326.     }
  327.    
  328.     $global:_sstimeout = $timespan.totalseconds
  329. }
  330.  
  331. #
  332. # Eventing / Timer Hooks, clean up and Prompt injection
  333. #
  334.  
  335. # timeout
  336. [int]$global:_sstimeout = 180 # default 3 minutes
  337.  
  338. # tick count
  339. [int]$global:_sscount = 0
  340.  
  341. # modify current prompt function to reset ticks counter to 0 and
  342. # to reregister timer, while saving for later on module onload
  343.  
  344. $self = $ExecutionContext.SessionState.Module
  345. $function:global:prompt = $self.NewBoundScriptBlock(
  346.     [scriptblock]::create(
  347.         ("{0}`n`$global:_sscount = 0`nRegister-Timer" `
  348.             -f ($global:_ssprompt = gc function:prompt))))
  349.  
  350. # configure our timer
  351. $global:_sstimer = new-object system.timers.timer
  352. $_sstimer.Interval = 1000 # tick once a second
  353. $_sstimer.AutoReset = $true
  354. $_sstimer.start()
  355.  
  356. # we start out disabled - use enable-screensaver
  357. $global:_ssdisabled = $true
  358.  
  359. # arrange clean up on module remove
  360. $ExecutionContext.SessionState.Module.OnRemove = {
  361.    
  362.     # restore prompt
  363.     $function:global:prompt = [scriptblock]::Create($_ssprompt)
  364.    
  365.     # kill off eventing subscriber, if one exists
  366.     if ($_sssrcid) {
  367.         Unregister-Event -SourceIdentifier $_sssrcid
  368.     }
  369.    
  370.     # clean up timer
  371.     $_sstimer.Dispose()
  372.    
  373.     # clear out globals
  374.     remove-variable _ss* -scope global
  375. }
  376.  
  377. Export-ModuleMember -function Start-ScreenSaver, Get-ScreenSaverTimeout, `
  378.     Set-ScreenSaverTimeout, Enable-ScreenSaver, Disable-ScreenSaver

Submit a correction or amendment below (
click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:


Remember me