├── README.md ├── cmatrix.psm1 └── demo.gif /README.md: -------------------------------------------------------------------------------- 1 | # cmatrix Effect 2 | 3 | The matrix effect on Windows through `PowerShell` 4 | 5 | ![Matrix](demo.gif "Matrix effect") 6 | 7 | ## How to 8 | 9 | 1) Open `Windows Powershell` (in some cases Admin privilege might be needed) 10 | 11 | 2) Either download and save [cmatrix.psm1](https://raw.githubusercontent.com/matriex/cmatrix/master/cmatrix.psm1) or just clonethis repo using `git clone https://github.com/matriex/cmatrix` 12 | 13 | 3) Import the module, set the timeout and enable it: 14 | ````powershell 15 | Set-Executionpolicy remotesigned 16 | Import-Module .\cmatrix 17 | Set-ScreenSaverTimeout -Seconds 5 18 | Enable-ScreenSaver 19 | ```` 20 | 21 | If you are happy with it, then you can put it also in one of your module folders that you can get via `$env:PSModulePath`. Just create a folder named `cmatrix` and put `cmatrix.psm1` in it and then the next time you can just import it as `Import-Module cmatrix` without having to specify the full path. 22 | 23 | _Author: Oisin Grehan_ 24 | 25 | _Contributor: Christoph Bergmeister_ 26 | -------------------------------------------------------------------------------- /cmatrix.psm1: -------------------------------------------------------------------------------- 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 | $randomLengthVariation = (1 + (get-random -min -30 -max 50)/100) 201 | [int]$script:fadelen = [math]::Abs($ylimit / 3 * $randomLengthVariation) 202 | 203 | $script:fadelen += (get-random -min 0 -max $fadelen) 204 | 205 | function Step { 206 | 207 | # reached the bottom yet? 208 | if ($head -lt $ylimit) { 209 | 210 | & $parentModule Set-BufferCell $xpos $head ( 211 | & $parentModule New-BufferCell -Character ` 212 | ([char](get-random -min 65 -max 122)) -Fore white) > $null 213 | 214 | & $parentModule Set-BufferCell $xpos ($head - 1) ( 215 | & $parentModule New-BufferCell -Character ` 216 | ([char](get-random -min 65 -max 122)) -Fore green) > $null 217 | 218 | $script:head++ 219 | } 220 | 221 | # time to start rendering the darker green "tail?" 222 | if ($head -gt $fadelen) { 223 | 224 | & $parentModule Set-BufferCell $xpos $fade ( 225 | & $parentModule New-BufferCell -Character ` 226 | ([char](get-random -min 65 -max 122)) -Fore darkgreen) > $null 227 | 228 | # tail end 229 | $tail = $fade-1 230 | if ($tail -lt $ylimit) { 231 | & $parentModule Set-BufferCell $xpos ($fade-1) ( 232 | & $parentModule New-BufferCell -Character ` 233 | ([char](get-random -min 65 -max 122)) -Fore black) > $null 234 | } 235 | 236 | $script:fade++ 237 | } 238 | 239 | 240 | # are we done animating? 241 | if ($fade -lt $ylimit) { 242 | return $true 243 | } 244 | 245 | # remove last row from tail end 246 | if (($fade - 1) -lt $ylimit) { 247 | & $parentModule Set-BufferCell $xpos ($fade - 1) ( 248 | & $parentModule New-BufferCell -Character ` 249 | ([char]' ') -Fore black) > $null 250 | } 251 | 252 | $false 253 | } 254 | 255 | Export-ModuleMember -function Step 256 | 257 | } -args $x, $executioncontext.sessionstate.module 258 | } 259 | 260 | function Start-ScreenSaver { 261 | 262 | # feel free to tweak maxcolumns and frame delay 263 | # currently 20 columns with 30ms wait 264 | 265 | Start-CMatrix -max 20 -frame 30 266 | } 267 | 268 | function Register-Timer { 269 | 270 | # prevent prompt from reregistering if explicit disable 271 | if ($_ssdisabled) { 272 | return 273 | } 274 | 275 | if (-not (Test-Path variable:global:_ssjob)) { 276 | 277 | # register our counter job 278 | $global:_ssjob = Register-ObjectEvent $_sstimer elapsed -action { 279 | 280 | $global:_sscount++; 281 | $global:_sssrcid = $event.sourceidentifier 282 | 283 | # hit timeout yet? 284 | if ($_sscount -eq $_sstimeout) { 285 | 286 | # disable this event (prevent choppiness) 287 | Unregister-Event -sourceidentifier $_sssrcid 288 | Remove-Variable _ssjob -scope Global 289 | 290 | sleep -seconds 1 291 | 292 | # start ss 293 | Start-ScreenSaver 294 | } 295 | 296 | } 297 | } 298 | } 299 | 300 | function Enable-ScreenSaver { 301 | 302 | if (-not $_ssdisabled) { 303 | write-warning "Screensaver is not disabled." 304 | return 305 | } 306 | 307 | $global:_ssdisabled = $false 308 | } 309 | 310 | function Disable-ScreenSaver { 311 | 312 | if ((Test-Path variable:global:_ssjob)) { 313 | 314 | $global:_ssdisabled = $true 315 | Unregister-Event -SourceIdentifier $_sssrcid 316 | Remove-Variable _ssjob -Scope global 317 | 318 | } else { 319 | write-warning "Screen saver is not enabled." 320 | } 321 | } 322 | 323 | function Get-ScreenSaverTimeout { 324 | new-timespan -seconds $global:_sstimeout 325 | } 326 | 327 | function Set-ScreenSaverTimeout { 328 | [cmdletbinding(defaultparametersetname="int")] 329 | param( 330 | [parameter(position=0, mandatory=$true, parametersetname="int")] 331 | [int]$Seconds, 332 | 333 | [parameter(position=0, mandatory=$true, parametersetname="timespan")] 334 | [Timespan]$Timespan 335 | ) 336 | 337 | if ($pscmdlet.parametersetname -eq "int") { 338 | $timespan = new-timespan -seconds $Seconds 339 | } 340 | 341 | if ($timespan.totalseconds -lt 1) { 342 | throw "Timeout must be greater than 0 seconds." 343 | } 344 | 345 | $global:_sstimeout = $timespan.totalseconds 346 | } 347 | 348 | # 349 | # Eventing / Timer Hooks, clean up and Prompt injection 350 | # 351 | 352 | # timeout 353 | [int]$global:_sstimeout = 180 # default 3 minutes 354 | 355 | # tick count 356 | [int]$global:_sscount = 0 357 | 358 | # modify current prompt function to reset ticks counter to 0 and 359 | # to reregister timer, while saving for later on module onload 360 | 361 | $self = $ExecutionContext.SessionState.Module 362 | $function:global:prompt = $self.NewBoundScriptBlock( 363 | [scriptblock]::create( 364 | ("{0}`n`$global:_sscount = 0`nRegister-Timer" ` 365 | -f ($global:_ssprompt = gc function:prompt)))) 366 | 367 | # configure our timer 368 | $global:_sstimer = new-object system.timers.timer 369 | $_sstimer.Interval = 1000 # tick once a second 370 | $_sstimer.AutoReset = $true 371 | $_sstimer.start() 372 | 373 | # we start out disabled - use enable-screensaver 374 | $global:_ssdisabled = $true 375 | 376 | # arrange clean up on module remove 377 | $ExecutionContext.SessionState.Module.OnRemove = { 378 | 379 | # restore prompt 380 | $function:global:prompt = [scriptblock]::Create($_ssprompt) 381 | 382 | # kill off eventing subscriber, if one exists 383 | if ($_sssrcid) { 384 | Unregister-Event -SourceIdentifier $_sssrcid 385 | } 386 | 387 | # clean up timer 388 | $_sstimer.Dispose() 389 | 390 | # clear out globals 391 | remove-variable _ss* -scope global 392 | } 393 | 394 | Export-ModuleMember -function Start-ScreenSaver, Get-ScreenSaverTimeout, ` 395 | Set-ScreenSaverTimeout, Enable-ScreenSaver, Disable-ScreenSaver 396 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matriex/cmatrix/a8f454893d6093166b56d1356b7a4b1a4da5f801/demo.gif --------------------------------------------------------------------------------