[Question]
  [
You may or may not remember Xeyes, a demo program that came with (and, as far as I know, still comes with) the X window system. Its purpose was to draw a pair of eyes that followed your mouse cursor:
[![Xeyes](https://i.stack.imgur.com/bmanm.png)](https://i.stack.imgur.com/bmanm.png)
Your challenge is to recreate Xeyes with ASCII art. Write a program or function that draws two ASCII art eyes (specified below) wherever the user clicks and then moves their pupils to point in the direction of the cursor.
[![Terminal Eyes GIF](https://i.stack.imgur.com/2xnal.gif)](https://i.stack.imgur.com/2xnal.gif)
The above GIF is a recording of [this non-golfed Ruby implementation](https://gist.github.com/jrunning/b2fb863555c9e35c426d2fec25d9ac27), which can be run with any recent version of Ruby. You may also find it useful as reference for Xterm control sequences.
## Specifications
This is [code-golf](/questions/tagged/code-golf "show questions tagged 'code-golf'"), so the solution with the fewest bytes wins.
This is an [ascii-art](/questions/tagged/ascii-art "show questions tagged 'ascii-art'") challenge, so your program must draw using ASCII characters‚Äîspecifically, the characters `-`, `.`, `|`, `'`, `0`, space, and newline.1 2
This is an [interactive](/questions/tagged/interactive "show questions tagged 'interactive'") challenge, so your program must accept input and draw its output in realtime.3
Before your program begins accepting input, it should initialize a blank canvas of at least 20 rows and 20 columns. It should not draw anything until the user clicks on the canvas.
Any time the user clicks4 on the canvas, the program should clear any previous output and then draw these ASCII eyes on the canvas, centered on the character nearest the location of the mouse cursor.5 6 (Below, `‚úß` represents the mouse cursor and should not be drawn.)
```
.---. .---.
|   | |   |
|  0|‚úß|0  |
|   | |   |
'---' '---'
```
Note how the pupils "point" toward the cursor.
Any time the mouse cursor moves on the canvas, the program should re-draw the pupils so that they continue pointing toward the cursor,7 e.g.:
```
             ‚úß
.---. .---.
|  0| |  0|
|   | |   |
|   | |   |
'---' '---'
```
## Pupil pointing
Suppose we enumerated the positions of the inner nine characters of each eye like so:
```
.---.
|678|
|591|
|432|
'---'
```
The pupil will be drawn at one of the locations `1`-`9`. To decide which one, pretend the characters are square and that the canvas is a Cartesian grid, with the center of the `9` character at (0, 0), the center of `1` at (1, 0), and so on. When the program receives input‚Äîa click or movement‚Äîit should map the input location to the nearest grid coordinate ùëÄ. If ùëÄ is (0, 0), the pupil should be drawn at (0, 0), i.e. the location of the `9` above. Otherwise, it should be drawn as described below.
Imagine a Cartesian plane superimposed on the grid and divided into octants numbered **1**‚Äì**8**:
[![](https://i.stack.imgur.com/4SSLk.png)](https://i.stack.imgur.com/4SSLk.png)
If ùëÄ lies within octant **1**, then the pupil should be drawn at the location of `1` above, i.e. at (1, 0). If ùëÄ is in octant **2** it should be drawn at `2`‚Äîand so on. To illustrate, the below image shows part of the grid color-coded according to where the pupil should be drawn when the mouse cursor is at a particular location. When, for example, the cursor is at any of the green coordinates (keeping in mind that the grid coordinates lie at the squares' centers, not their corners), the pupil should be drawn at `4`.
[![](https://i.stack.imgur.com/WdKO7.png)](https://i.stack.imgur.com/WdKO7.png)
The two eyes' pupils move independently, so for each eye repeat the process with ùëÄ relative to that eye's center.
## Notes
1. This isn't a [graphical-output](/questions/tagged/graphical-output "show questions tagged 'graphical-output'") challenge. The output must be a grid of characters. You may, of course, use graphics routines to draw a grid of characters.
2. Whitespace may be drawn (or, rather, not drawn) however is convenient. An empty spot in the grid looks the same as a space character and will be considered equivalent.
3. "Real-time" is defined here as fewer than 200ms between input and the corresponding output being drawn.
4. It is at your discretion which mouse button(s) are observed for input, and whether a press or release constitutes a "click."
5. The canvas must be cleared, or the visual equivalent must be achieved. With a terminal-based solution, for example, printing a new canvas below the previous canvas is not considered equivalent.
6. When the user clicks near the edge of the canvas such that some of the eye characters would be drawn beyond its edge, the behavior is undefined. However, the program must continue running normally upon subsequent clicks.
7. When the mouse cursor leaves the "canvas," the behavior is undefined, but the program must continue running normally when the cursor re-enters the canvas.
8. A text cursor may appear on the canvas, as long as it does not obscure the output.
Standard loopholes are forbidden.
  
[Answer]
# Excel VBA 32-Bit, ~~630~~ 519 Bytes
A `Worksheet` level `Sub`routine that activates on every click. Draws eyes in a \$5\times 11\$ range centered at the clicked cell.
This is calibrated to run at the default zoom of 100%. It breaks if when scrolling, zooming, or selecting multiple cells.
```
Sub Worksheet_SelectionChange(ByVal t As Range)
With Cells
.Clear
.ColumnWidth=1.3
.Font.Name="Courier"
End With
Dim l As p,p As p
Q l
On Error Resume Next
Do
DoEvents
Q p
For i=0To 1
v=l.x-p.x+56*(-1)^i
w=l.y-p.y
n=IIf(Abs(v)>6Or Abs(w)>9,(4*Application.Atan2(v,w)\[Pi()]+4)Mod 8,8)
For j=0To 24
m=Mid(".---.|567||480||321|'---'",j+1,1)
t.Offset(j\5-2,j Mod 5+1-6*i)=Mid("0 '"&m,3+(m Like"[0-9]")+(m=Val(n)))
Next j,i
Loop
End Sub
```
***Note:** terminal `"` on line 5 is included for Prettify.JS/Markdown syntax highlighting only, and does not contribute to byte count*
### Helper Function and Type Declaration
The below declarations allow for querying the cursor position using the Windows management function [GetCursorPos](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getcursorpos).
These must be declared in an accessible `Module` level context.
```
Declare Sub Q Lib"user32"Alias"GetCursorPos"(l As p)
Type p
x As Long
y As Long
End Type
```
The above is only compatible with 32-bit installations of Excel, but may be adjusted to run in a 64-bit installs by modifying the the declaration to
```
Declare PtrSafe Sub Q Lib"user32"Alias"GetCursorPos"(l As p)
```
# Output
[![Moving_Eyes](https://i.stack.imgur.com/5JSpq.gif)](https://i.stack.imgur.com/5JSpq.gif)
# Commented
```
''  execute the following every time the user changes selection
Sub Worksheet_SelectionChange(ByVal t As Range)
    ''  Tell VBA to keep running on errors
    ''    ( Necessary for specification Note #6 )
    On Error Resume Next
    ''  Clear All Cell, Make them Square, Use Monospace Font
    With Cells
        .Clear
        .ColumnWidth = 1.3
        .Font.Name = "Courier"
    End With
    ''  Dim two p (point) objects, defined below
    Dim l As p, p As p
    ''  Use Q (alias for User32 function GetCursoPos) to get initial mouse position (l)
    Q l
    ''  Implicitly loop until the next selection change event
    Do
    ''  Allow events
    DoEvents
    ''  Use User32 function to get current mouse position (p)
    Q p
    '' iterate over the two eyes (left: i=1, right: i=0)
    For i = 0 To 0 '1
        ''  define var (v) with horiontal distance between cursor and center of eye i
        v = l.x - p.x + 56 * (-1) ^ i
        ''  define var (w) with vertical distance between cursor and center of eyes
        w = l.y - p.y
        ''  Based off of (v) and (w), calculate which of the numbers in the below
        ''    should be the pupil for each eye
        ''
        ''  .---.
        ''  |567|
        ''  |480|
        ''  |321|
        ''  '---'
        ''
        n = IIf(Abs(v) > 6 Or Abs(w) > 9, (4 * Application.Atan2(v, w) \ [Pi()] + 4) Mod 8, 8)
        ''  Iterate over the 25 cells to be updated, per eye
        For j = 0 To 24
            ''  find character at current cell in template
            m = Mid(".---.|567||480||321|'---'", j + 1, 1)
            ''  stuff cell with the first *k* letters of  "0 '" & template char
            ''    where k defaults to 3,
            ''      is decremented by 1 if template char is a number  AND
            ''      is decremented by 1 if template char is n
            ''
            ''  Cases:
            ''    Border Characters (`.`,`-`,`|`,`'`):
            ''      start at 3rd character, cell holds `'` and template char
            ''      Note: `'` literal at beginning of cell coerces excel to display
            ''      any and all values as text, allowing `-` to be printed
            ''    Sclera Characters (` `):
            ''      start at 2nd character, cells hold ` '` and template char
            ''      but due to cell sizing and monospace font, only ` ` is visible
            ''    Iris / Pupil Characters (`0`):
            ''      start at 1st character, cells hold `0 '` and template char
            ''      but only leading `0` is visible
            ''      Note: use of `Val` here forces implicit type 
            ''          coercion, and is generally *cursed*
            t.Offset(j \ 5 - 2, j Mod 5 + 1 - 6 * i) = Mid("0 '" & m, 3 + (m Like "[0-9]") + (m = Val(n)))
            
    ''  End For loops
    Next j, i
    
    ''  End Do loop
    Loop
End Sub
```
[Answer]
# HTML + CSS + JavaScript (ES6), 93 + 19 + ~~278~~ 276 = 388 bytes
```
w=7.8125
h=15
with(Math)r=round,
(onclick=e=>F.style=`margin:-3.5em -6.5ch;left:${x=r(e.x/w)*w}px;top:${y=r(e.y/h)*h}px`)({y:-40}),onmousemove=e=>(s=($,o)=>$.style=`left:${a=atan2(Y=r((e.y-y)/h),X=r((e.x-x)/w+o)),X|Y?w*r(cos(a)):0}px;top:${X|Y?h*r(sin(a)):0}px`)(L,3)&&s(R,-3)
```
```
*{position:relative
```
```
<pre id=F>.---. .---.
|   | |   |
| <a id=L>0</a> | | <a id=R>0</a> |
|   | |   |
'---' '---'
```
[Answer]
# [6502 machine code](https://en.wikibooks.org/wiki/6502_Assembly) (C64 + [1351 mouse](https://en.wikipedia.org/wiki/Commodore_1351)), 630 bytes
```
00 C0 20 44 E5 A9 FF 85 5E A2 3F A9 00 8D 10 D0 8D 1B D0 9D C0 02 CA 10 FA A0
0A A2 1E B9 5A C2 9D C0 02 CA CA CA 88 10 F4 A9 0B 8D F8 07 A9 18 8D 00 D0 A9
32 8D 01 D0 A9 0D 8D 27 D0 A9 01 8D 15 D0 78 A9 60 8D 14 03 A9 C1 8D 15 03 58
D0 FE 84 FD 85 FE A8 38 E5 FD 29 7F C9 40 B0 04 4A F0 0A 60 09 C0 C9 FF F0 03
38 6A 60 A9 00 60 20 44 E5 A5 69 38 E9 05 B0 02 A9 00 C9 1E 90 02 A9 1D 85 FD
18 69 02 85 5C 69 06 85 5D A5 6A 38 E9 02 B0 02 A9 00 C9 15 90 02 A9 14 85 FE
18 69 02 85 5E A9 65 8D BB C0 A9 C2 8D BC C0 A9 04 85 02 A6 FE 20 F0 E9 A9 02
85 5F A4 FD A2 00 BD FF FF 91 D1 C8 E8 E0 05 D0 F5 C8 C6 5F D0 EE E6 FE A9 6A
8D BB C0 A9 C2 8D BC C0 C6 02 30 0E D0 D1 A9 6F 8D BB C0 A9 C2 8D BC C0 D0 C5
60 C5 69 90 0A F0 5D E5 69 85 5F A9 C6 D0 09 49 FF 38 65 69 85 5F A9 E6 8D 1C
C1 8D 23 C1 8D 3E C1 A5 6A C5 5E 90 21 F0 12 E5 5E C5 5F 90 12 4A C5 5F B0 02
C6 FD A6 5E E8 D0 33 C6 FD A6 5E D0 2D 0A C5 5F B0 EE 90 F3 49 FF 38 65 5E C5
5F 90 0C 4A C5 5F B0 02 C6 FD A6 5E CA D0 11 0A C5 5F B0 F4 90 D7 A5 6A C5 5E
90 EE F0 D1 B0 C8 20 F0 E9 A9 30 A4 FD 91 D1 60 AD 19 D4 A4 FB 20 4E C0 84 FB
85 5F 18 6D 00 D0 8D 00 D0 6A 45 5F 10 08 A9 01 4D 10 D0 8D 10 D0 AD 10 D0 4A
AD 00 D0 B0 08 C9 18 B0 16 A9 18 D0 0F C9 58 90 0E 24 5F 10 05 CE 10 D0 B0 EF
A9 57 8D 00 D0 AD 1A D4 A4 FC 20 4E C0 84 FC 49 FF 85 5F 38 6D 01 D0 8D 01 D0
6A 45 5F 10 06 24 5F 10 11 30 07 AD 01 D0 C9 32 B0 04 A9 32 D0 06 C9 FA 90 05
A9 F9 8D 01 D0 A5 69 85 6B A5 6A 85 6C AD 10 D0 4A AD 00 D0 6A 38 E9 0C 4A 4A
85 69 AD 01 D0 38 E9 32 4A 4A 4A 85 6A AD 01 DC 29 10 C5 6D F0 0B 85 6D 29 10
D0 05 20 6C C0 30 10 A5 5E 30 46 A5 69 C5 6B D0 06 A5 6A C5 6C F0 3A A6 5E CA
86 5F A9 03 85 02 A6 5F 20 F0 E9 A9 20 A2 03 A4 5C 88 91 D1 C8 CA D0 FA A2 03
A4 5D 88 91 D1 C8 CA D0 FA E6 5F C6 02 D0 DD A5 5C 85 FD 20 E9 C0 A5 5D 85 FD
20 E9 C0 4C 31 EA 80 C0 E0 F0 F8 FC F0 D8 18 0C 0C 2E 2D 2D 2D 2E 5D 20 20 20
5D 27 2D 2D 2D 27
```
In action:
[![demo](https://i.stack.imgur.com/Kla09.gif)](https://i.stack.imgur.com/Kla09.gif)
**No online demo**, sorry, because there's AFAIK no js C64 emulator supporting a mouse. If you want to try it yourself, grab [VICE](http://vice-emu.sourceforge.net/), download [the binary executable](https://github.com/Zirias/c64xeyes/blob/8a910681df6f99e81bdb6d370579c00e402130f5/xeyes.prg?raw=true) and start it in the C64 emulator:
```
x64sc -autoload xeyes.prg -controlport1device 3 -keybuf 'sys49152\n'
```
To grab/ungrab the mouse input in the running emulator, use `ctrl+m` on Unix/Linux and `ctrl+q` on windows.
---
Yes, this had to be done ;) After all, there **is** an original Commodore mouse for the C64, but of course, the builtin operating system doesn't support it, so I first needed a mouse driver, which already took 230 bytes (including a mouse-cursor shaped hardware sprite and bounds checking code for the screen area, but without translating the pointer coordinates to text screen coordinates).
* To safe some bytes, I decided to keep the OS' IRQ working and use a few Kernal routines where possible (clearing the screen and getting a base pointer for a text screen row).
* The code also puts all variables in zeropage, which saves some more bytes, but destroys floating point values used by BASIC. As the program never exits anyways, this doesn't matter.
* The third trick to reduce size is self modification: There's only code to check for putting the pupil on the *left* side of the eye. The same code is reused after patching some decrement instructions to increment instructions for the right side.
If you're interested, you can read the code [as *assembly source* here](https://github.com/Zirias/c64xeyes/blob/8a910681df6f99e81bdb6d370579c00e402130f5/src/main.s) :)
[Answer]
# QBasic ([QB64](http://qb64.net)), ~~361~~ 305 bytes
```
DO
WHILE _MOUSEINPUT
x=CINT(_MOUSEX)
y=CINT(_MOUSEY)
IF _MOUSEBUTTON(1)THEN l=x-3:k=y
IF(2<l)*(73>l)*(2<k)*(22>k)THEN CLS:FOR i=0TO 1:h=l+6*i:LOCATE k-2,h-2:?".---.":FOR j=1TO 3:LOCATE,h-2:?"|   |":NEXT:LOCATE,h-2:?"'---'":d=x-h:e=y-k:m=ABS(e/d):LOCATE k-SGN(e)*(m>=.5),h-SGN(d)*(m<=2):?"0":NEXT
WEND
LOOP
```
Left-click places the eyes. If placement of eyes would result in part of the eyes being out of bounds, the program "freezes" until a valid placement is made.
The main tricky part is placing the pupils. Most of the time, the coordinates of the pupil are just the center of the eye plus (sign(Œîx), sign(Œîy)), except that in octants 1 and 5, the y-coordinate equals y-center, and in octants 3 and 7, the x-coordinate equals x-center. Octant boundaries can be calculated using the slope `m` of the line from the center of the eye to the mouse coordinates. Conveniently, dividing by zero when calculating the slope gives floating-point infinity (+/-) rather than an error.
[![Visual eyes in QB64](https://i.stack.imgur.com/7BJQc.gif)](https://i.stack.imgur.com/7BJQc.gif)
### Ungolfed
```
' Loop forever
DO
    ' Do stuff if there is new mouse data (movement or click)
    IF _MOUSEINPUT THEN
        ' Store the mouse coords rounded to the nearest integer
        mouse_x = CINT(_MOUSEX)
        mouse_y = CINT(_MOUSEY)
        ' If left mouse button was clicked, change location of eyes
        IF _MOUSEBUTTON(1) THEN
            ' Store center coordinates of left eye
            left_center_x = mouse_x - 3
            center_y = mouse_y
        END IF
        ' If eye location is in bounds, print the eyes and pupils
        x_in_bounds = left_center_x > 2 AND left_center_x < 73
        y_in_bounds = center_y > 2 AND center_y < 22
        IF x_in_bounds AND y_in_bounds THEN
            CLS
            FOR eye = 1 TO 2
                ' eye = 1 for left eye, eye = 2 for right eye
                IF eye = 1 THEN center_x = left_center_x
                IF eye = 2 THEN center_x = left_center_x + 6
                ' Print eye borders
                LOCATE center_y - 2, center_x - 2
                PRINT ".---."
                FOR row = 1 TO 3
                    LOCATE , center_x - 2
                    PRINT "|   |"
                NEXT row
                LOCATE , center_x - 2
                PRINT "'---'"
                ' Calculate coordinates of pupil
                xdiff = mouse_x - center_x
                ydiff = mouse_y - center_y
                slope = ydiff / xdiff
                ' For most cases, adding the sign of the diff to the center
                ' coordinate is sufficient
                pupil_x = center_x + SGN(xdiff)
                pupil_y = center_y + SGN(ydiff)
                ' But in octants 3 and 7, the x-coordinate is centered
                IF ABS(slope) > 2 THEN pupil_x = center_x
                ' And in octants 1 and 5, the y-coordinate is centered
                IF ABS(slope) < 0.5 THEN pupil_y = center_y
                LOCATE pupil_y, pupil_x
                PRINT "0"
            NEXT eye
        END IF   ' in bounds
    END IF   ' mouse data
LOOP   ' forever
```
[Answer]
# [Clean](https://clean.cs.ru.nl), ~~1014~~ ~~904~~ ~~892~~ ~~884~~ ~~840~~ ~~814~~ ~~782~~ ~~772~~ 769 bytes
***-6 bytes if the eyes don't need to snap to a grid***
This wasn't easy. UIs in functional languages rarely are.
```
import StdEnv,StdIO,osfont,ostoolbox
a=toReal
c=1>0
Start w#(d,w)=openId w
#(t,w)=worldGetToolbox w
#(_,f,_)=osSelectfont("Courier",[],9)t
=let$p#(s,p)=accPIO getProcessWindowSize p
    =snd(openWindow NilLS(Window""NilLS[WindowId d,WindowMouse(\_=c)Able(noLS1@),WindowViewSize s,WindowPen[PenFont f]])p);@(MouseUp p _)s={s&ls=p};@(MouseMove p _)s=:{ls={x,y},io}={s&io=setWindowLook d c(c,(\_{newFrame}i#(w,i)=getFontCharWidth f' '(unfill newFrame i)
    =let g v=let m=y-p.y;n=p.x-x-v*w;s=abs(a m/a n);k|abs m<9&&abs n<w=5|s<0.4142=if(n>0)6 4=sign if(s>2.4143)0n+if(m>0)2 8in[".---.":["|"+++{if(k==e)'0'' '\\e<-[j..j+2]}+++"|"\\j<-[1,4,7]]]++["'---'"]in foldr(\e=drawAt{x=(x/w-5)*w,y=(y/9+e-2)*9}([a+++" "+++b\\a<-g -3&b<-g 3]!!e))i[0..4]))io};@_ s=s
in startIO SDI zero$[]w
```
Make sure that you're using iTasks Clean, have the `Courier` font installed, and have `StdLib` BEFORE any subfolders of `ObjectIO` in the module search path.
Compile with (example, may differ): `clm -IL StdLib -IL ObjectIO -IL "ObjectIO/OS <YOUR_OS_HERE>" -IL Dynamics -IL Generics -IL Platform -nci <MODULE_NAME_HERE>`
If you've never run Clean before, expect this project to take 5+ minutes to compile.
**Ungolfed:**
```
module main
import StdEnv,StdIO,osfont,ostoolbox
height=9
SlopeFor225 :== 0.4142
StartSize :== 8
Universe :== {corner1={x=0,y=0},corner2={x=1,y=1}}
Start :: *World -> *World
Start world = startConsole (openIds 1 world)
startConsole :: ([Id],*World) -> *World
startConsole ([windowID],world)
    # (toolbox,world) = worldGetToolbox world
    # (_,font,toolbox) = osSelectfont ("Consolas",[],height) toolbox
    = startIO SDI {x=0,y=0} (initialise font) [ProcessClose closeProcess] world
where
    initialise font pst
        # (size,pst) = accPIO getProcessWindowSize pst
        # (error,pst) = openWindow undef (window font size) pst
        | error<>NoError = abort "bad window"
        = pst
    window font size
        = Window "Xeyes" NilLS
            [WindowId           windowID
            ,WindowClose        (noLS closeProcess)
            ,WindowMouse        mouseFilter Able (noLS1 track)
            ,WindowViewDomain   Universe//(getViewDomain StartSize)
            ,WindowViewSize     size
            ,WindowPen          [PenFont font]
            ]
    track (MouseDown pos _ _) state=:{ls=point=:{x,y},io}
        # point = pos
        // move to mouse position
        = {state & ls=pos}
    track (MouseMove pos _) state=:{ls=point=:{x,y},io}
        //redraw to point at mouse
        # io = setWindowLook windowID True (True, look) io
        = {state & ls=point,io=io}
    where
        look _ {newFrame} picture
            # picture = unfill newFrame picture
            # (width,picture) = getPenFontCharWidth' 'picture
            = let
                determineSector u
                    # yDist = (y - pos.y)
                    # xDist = (pos.x - u)
                    # slope = abs(toReal yDist / toReal xDist)
                    | (abs yDist) < height && (abs xDist) < width = '9'
                    | slope < SlopeFor225 = if(xDist > 0) '1' '5'
                    | yDist > 0
                        | slope > (2.0+SlopeFor225) = '7'
                        = if(xDist > 0) '8' '6'
                    | slope > (2.0+SlopeFor225) = '3'
                    = if(xDist > 0) '2' '4'
                getEye u=map(map(\e|isDigit e=if(e==determineSector(x+u*width))'0'' '=e))[['.---.'],['|678|'],['|591|'],['|432|'],['\'---\'']]
            in foldr(\i pic=drawAt{x=(x/width-5)*width,y=(y/height+i-2)*height}([toString(a++[' ':b])\\a<-getEye -3&b<-getEye 3]!!i)pic)picture[0..4]
    mouseFilter (MouseDown _ _ _) = True
    mouseFilter (MouseMove _ _) = True
    mouseFilter _ = False
```
As you can see from the ungolfed version, most of the code is just setting up the combination of "monospaced font" with "respond to the mouse". And even though `Courier` doesn't make it easy to tell, it is actually drawing the `.`s and `'`s. Swapping to something like `Consolas` makes it clearer.
[![enter image description here](https://i.stack.imgur.com/iTfxL.gif)](https://i.stack.imgur.com/iTfxL.gif)
[Answer]
# Ruby, 335 + 13 = 348 bytes
+13 bytes for `-rio/console` flag to enable `IO#getch`.
Contains literal ESC (`0x1b`) characters, shown as `‚êõ` below. xxd dump follows.
**Caution:** This does not clean up after itself at exit. See note under **xxd dump** below.
```
include Math
$><<"‚êõ[?1003h"
s=""
(s<<STDIN.getch
($><<"‚êõ[2J"
x,y=$3.ord-32,$4.ord-32
u,v=x,y if$2
u&&[x-u+3,x-u-3].map{|a|b=y-v
e=4*asin(b/sqrt(a**2+b**2))/PI
printf"‚êõ[%d;%dH.---.@|567|@|480|@|321|@'---'".gsub(/(#{(a<0?4-e:b<0?8+e:e).round%8rescue 8})|([0-8])|@/){$1?0:$2?" ":"‚êõ[5D‚êõ[1B"},v-2,x-a-2}
s="")if /M(C|(#))(.)(.)$/=~s)while 1
```
## Ungolfed
This is a pretty na√Øve golf of [my original Ruby implementation](https://gist.github.com/jrunning/b2fb863555c9e35c426d2fec25d9ac27).
```
include Math       # Saves a few bytes for asin, sqrt, and PI
$> << "‚êõ[?1003h"   # Print xterm control sequence to start mouse tracking
s = ""             # Variable to hold input-so-far
(
  s << STDIN.getch   # Read a character from STDIN
  (
    $> << "‚êõ[2J"                     # Clear terminal
    x, y = $3.ord - 32, $4.ord - 32  # Get cursor x and y from last match
    u, v = x, y if $2                # Update eye position if last matched control sequence was click ("#")
    u && [x-u+3, x-u-3].map {|a|     # For each eye's x-position
      b = y - v                                       # Eye's y position
      e = 4 * asin(b / sqrt(a**2 + b**2)) / PI        # Convert cursor (x,y) to angle w/ x-axis as 1/8 turns
      printf "‚êõ[%d;%dH.---.@|567|@|480|@|321|@'---'"  # Control code to move text cursor, followed by template for eye
        .gsub(
          /(#{
            (a < 0 ? 4-e : b < 0 ? 8+e : e).round % 8 rescue 8  # Octant number 0-7 or 8 for center
          })|([0-8])|@/
        ){ $1 ? 0 : $2 ? " " : "‚êõ[5D‚êõ[1B" },            # Replace octant number with pupil; other digits with space; and @s with code to move cursor left and down for next line of eye
        v-2, x-a-2                                      # (y, x) position of top left corner of eye
    }
    s = ""                           # Clear input-so-far
  ) if /M(C|(#))(.)(.)$/ =~ s      # ...when input-so-far matches a movement ("C") or click ("#") control sequence
) while 1                        # ...forever
```
## xxd dump
This program turns on mouse tracking with the xterm control sequence `\e[?1003h` but doesn't turn it off at exit. To turn it off, use the control sequence `\e[?1003l`, e.g.:
```
ruby -rio/console visual_eyes.rb; printf '\e[1003l'
```
Since the program eats all input, it's hard to exit. If you want to be able to exit by pressing Ctrl+C, add the following line below `(s<<STDIN.getch`:
```
exit 130 if s.end_with?(?\003)
```
Without further ado:
```
00000000: 696e 636c 7564 6520 4d61 7468 0a24 3e3c  include Math.$><
00000010: 3c22 1b5b 3f31 3030 3368 220a 733d 2222  <".[?1003h".s=""
00000020: 0a28 733c 3c53 5444 494e 2e67 6574 6368  .(s<<STDIN.getch
00000030: 0a28 243e 3c3c 221b 5b32 4a22 0a78 2c79  .($><<".[2J".x,y
00000040: 3d24 332e 6f72 642d 3332 2c24 342e 6f72  =$3.ord-32,$4.or
00000050: 642d 3332 0a75 2c76 3d78 2c79 2069 6624  d-32.u,v=x,y if$
00000060: 320a 7526 265b 782d 752b 332c 782d 752d  2.u&&[x-u+3,x-u-
00000070: 335d 2e6d 6170 7b7c 617c 623d 792d 760a  3].map{|a|b=y-v.
00000080: 653d 342a 6173 696e 2862 2f73 7172 7428  e=4*asin(b/sqrt(
00000090: 612a 2a32 2b62 2a2a 3229 292f 5049 0a70  a**2+b**2))/PI.p
000000a0: 7269 6e74 6622 1b5b 2564 3b25 6448 2e2d  rintf".[%d;%dH.-
000000b0: 2d2d 2e40 7c35 3637 7c40 7c34 3830 7c40  --.@|567|@|480|@
000000c0: 7c33 3231 7c40 272d 2d2d 2722 2e67 7375  |321|@'---'".gsu
000000d0: 6228 2f28 237b 2861 3c30 3f34 2d65 3a62  b(/(#{(a<0?4-e:b
000000e0: 3c30 3f38 2b65 3a65 292e 726f 756e 6425  <0?8+e:e).round%
000000f0: 3872 6573 6375 6520 387d 297c 285b 302d  8rescue 8})|([0-
00000100: 385d 297c 402f 297b 2431 3f30 3a24 323f  8])|@/){$1?0:$2?
00000110: 2220 223a 221b 5b35 441b 5b31 4222 7d2c  " ":".[5D.[1B"},
00000120: 762d 322c 782d 612d 327d 0a73 3d22 2229  v-2,x-a-2}.s="")
00000130: 6966 202f 4d28 437c 2823 2929 282e 2928  if /M(C|(#))(.)(
00000140: 2e29 242f 3d7e 7329 7768 696c 6520 31    .)$/=~s)while 1
```
]