Gray Soft / Terminal Trickstag:graysoftinc.com,2014-03-20:/categories/242016-01-11T14:26:42ZJames Edward Gray IIA Curses Applicationtag:graysoftinc.com,2015-03-20:/posts/1392016-01-11T14:26:42ZA breakdown of an example curses application that shows how the various pieces fit together.<p>I've now written two articles covering <a href="http://graysoftinc.com/terminal-tricks/basic-curses">low-level <code>curses</code></a> and <a href="http://graysoftinc.com/terminal-tricks/curses-windows-pads-and-panels">some higher abstractions</a>. We know what <code>curses</code> can do at this point, but we haven't really seen how to put everything together and build a full application. Let's do that today by examining an example of moderate size.</p>
<p>I have written the beginnings of <a href="https://github.com/JEG2/bird_of_paradise">a command-line Twitter client</a> using <code>curses</code>. To make this easier, I developed <a href="https://github.com/JEG2/rurses">a super simple wrapper over the raw <code>curses</code> API, called Rurses</a>. Rurses, for Ruby <code>curses</code>, provides more Ruby-like abstractions over the clunky C API. Here's how the Twitter client looks in action:</p>
<pre><code>┌─JEG2─────────────────────────────────┐ @
│ ↑│
│Alex Harms @onealexh… 20/03/2015 14:57│ Clayton Flesher @Cal… 19/03/2015 20:52
│RT @postsecret: http://t.co/LrV0IIYgUM│ @TrevorBramble its inspired by, as in
│ │ I played it once for about ten minutes
│SimKlabnik 2000 @ste… 20/03/2015 14:57│ and then @JEG2 said 'go make your
│the closure docs are finally flowing │ version of that'.
│from my fingertips, it seems │
│ │ Mandy Moore @theruby… 19/03/2015 18:31
│ashe dryden @ashedry… 20/03/2015 14:57│ Thanks to @JEG2 I now want a MiP robot
│Can anyone recommend an interestingly│ in the worst kind of way!!!
│written (not dry) history of Haiti? │
│Preferably written by a Haitian. │ Sam Livingston-Gray … 19/03/2015 14:23
│ │ @avdi @JEG2 hush! Keep this up and
│Jessica Lord @jllord 20/03/2015 14:57│ EVERYONE WILL KNOW
│RT @PDX44: Welcome to Portland, where │ https://t.co/deJBBjoOTV
│the old airport carpet is dressed up │
│as a human and named Grand Marshal of │ Josh Susser @joshsus… 19/03/2015 14:06
│a parade. http://t.co/aTCicqSzEI │ @geeksam @jeg2 @avdi POLS issue. No
│ │ standard == no way to avoid surprising
│Garann Means @garannm 20/03/2015 14:56│ many developers
│RT @verge: Robyn launches tech │
│ ↓│ ↓
└──────────────────────────────────────┘
</code></pre>
<p>I don't want to take you through every line of code that I wrote. A lot of it doesn't really relate to <code>curses</code> anyway. The curious can dig into GitHub and fiddle with the code as much as they like, but let me give you the dime tour.</p>
<h4>Curses Programs</h4>
<p>If you've read the previous articles in this series, you know there's always a fair bit of boilerplate in any <code>curses</code> example that I show. Step one was make this feel more Rubyish. Here's the <code>curses</code> invocation in my Twitter client, Bird of Paradise, thanks to the helper library Rurses:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">BirdOfParadise</span>
<span class="k">class</span> <span class="nc">UI</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">show</span><span class="p">(</span><span class="n">screen_name</span><span class="p">:</span> <span class="p">,</span> <span class="ss">timeline</span><span class="p">:</span> <span class="p">,</span> <span class="ss">mentions</span><span class="p">:</span> <span class="p">)</span>
<span class="no">Rurses</span><span class="o">.</span><span class="n">program</span><span class="p">(</span>
<span class="ss">modes</span><span class="p">:</span> <span class="o">%</span><span class="n">i</span><span class="o">[</span><span class="n">c_break</span> <span class="n">no_echo</span> <span class="n">keypad</span> <span class="n">non_blocking</span> <span class="n">hide_cursor</span><span class="o">]</span>
<span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">screen</span><span class="o">|</span>
<span class="vi">@screen</span> <span class="o">=</span> <span class="no">Screen</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">screen</span><span class="p">,</span> <span class="n">event_q</span><span class="p">,</span> <span class="n">screen_name</span><span class="p">,</span> <span class="n">timeline</span><span class="p">,</span> <span class="n">mentions</span><span class="p">)</span>
<span class="n">listen_for_events</span>
<span class="n">keyboard</span><span class="o">.</span><span class="n">read</span>
<span class="n">wait_for_exit</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>The most obvious change here is the use of the block. Rurses will setup <code>curses</code>, call the block, and clean up afterwords. This greatly reduces the amount of boilerplate code we have to use.</p>
<p>The other notable change in this code is the invocation of the various modes that <code>curses</code> provides. They are now passed as simple arguments to <code>Rurses.program()</code>. I've also cleaned up the mode names a tiny bit.</p>
<p>The code that provides these niceties isn't very complex. Here's the main entry point of the Rurses library:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">Rurses</span>
<span class="c1"># ...</span>
<span class="kp">module_function</span>
<span class="k">def</span> <span class="nf">curses</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">program</span><span class="p">(</span><span class="ss">modes</span><span class="p">:</span> <span class="o">[</span> <span class="o">]</span><span class="p">)</span>
<span class="vi">@stdscr</span> <span class="o">=</span> <span class="no">Window</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">curses_ref</span><span class="p">:</span> <span class="n">curses</span><span class="o">.</span><span class="n">initscr</span><span class="p">,</span> <span class="n">standard_screen</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span>
<span class="vi">@stdscr</span><span class="o">.</span><span class="n">change_modes</span><span class="p">(</span><span class="n">modes</span><span class="p">)</span>
<span class="k">yield</span><span class="p">(</span><span class="vi">@stdscr</span><span class="p">)</span>
<span class="k">ensure</span>
<span class="n">curses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
<span class="c1"># ...</span>
<span class="k">end</span>
</pre></div>
<p>You should recognize a couple of <code>curses</code> calls in here: <code>initscr()</code> and <code>endwin()</code>. We'll take more about the <code>Window</code> object that I wrapped <code>stdscr</code> in later, but know that I wanted this new API to favor the use of objects where it makes sense.</p>
<p>You can see here that the <code>modes</code> are passed into a <code>change_modes()</code> method of <code>Window</code>. This is a judgment call that I made while building this new API. Some modes are window specific, some aren't, and at least one takes a window parameter that is ignored. I didn't want to build three different systems for changing modes and force users to remember which to use where. Because of that, I've pushed all modes into the <code>Window</code> objects. If you want to set global modes, just make the call on <code>stdscr</code> as I do here. Or better yet, don't make any calls to <code>change_modes()</code> manually and just pass what you want as arguments to <code>program()</code>.</p>
<p>Here's the actual mode changing code:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">Rurses</span>
<span class="k">class</span> <span class="nc">Window</span>
<span class="no">MODE_NAMES</span> <span class="o">=</span> <span class="p">{</span>
<span class="n">c_break</span><span class="p">:</span> <span class="ss">:cbreak</span><span class="p">,</span>
<span class="n">no_echo</span><span class="p">:</span> <span class="ss">:noecho</span><span class="p">,</span>
<span class="ss">keypad</span><span class="p">:</span> <span class="o">[</span><span class="ss">:keypad</span><span class="p">,</span> <span class="ss">:window</span><span class="p">,</span> <span class="kp">true</span><span class="o">]</span><span class="p">,</span>
<span class="n">hide_cursor</span><span class="p">:</span> <span class="o">[</span><span class="ss">:curs_set</span><span class="p">,</span> <span class="mi">0</span><span class="o">]</span><span class="p">,</span>
<span class="n">non_blocking</span><span class="p">:</span> <span class="o">[</span><span class="ss">:timeout</span><span class="p">,</span> <span class="mi">0</span><span class="o">]</span>
<span class="p">}</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">change_modes</span><span class="p">(</span><span class="n">modes</span><span class="p">)</span>
<span class="n">modes</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="nb">name</span><span class="o">|</span>
<span class="n">mode</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">(</span><span class="no">MODE_NAMES</span><span class="o">[</span><span class="nb">name</span><span class="o">]</span> <span class="o">||</span> <span class="nb">name</span><span class="p">)</span>
<span class="no">Rurses</span><span class="o">.</span><span class="n">curses</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="o">*</span><span class="n">mode</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">arg</span><span class="o">|</span> <span class="n">arg</span> <span class="o">==</span> <span class="ss">:window</span> <span class="p">?</span> <span class="n">curses_ref</span> <span class="p">:</span> <span class="n">arg</span> <span class="p">})</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>The <code>MODE_NAMES</code> mapping links my slightly more readable aliases with the details for invoking that mode. Obviously, this doesn't handle all of <code>curses</code> modes yet. I've only mapped what I needed so far.</p>
<p>I've decided to prefer descriptive names (<code>:non_blocking</code>) to magic arguments (<code>timeout(0)</code>), but this doesn't handle the full range of <code>curses</code> capabilities. It's totally viable to call <code>timeout(100)</code> or <code>timeout(500)</code> and I can't make up sensible names for all possible combinations. Eventually, some exceptions would need to be made for these modes.</p>
<p>The <code>:window</code> flag is special. It gets replaced with the <code>curses</code> pointer as the mode is invoked. Again, not all modes need this, but we have to support those that do.</p>
<h4>Do Several Things At Once</h4>
<p>I chose my example project carefully. One of the primary reasons to work with a library like <code>curses</code> is that you can't afford to wait on keyboard input. A Twitter client is a good example of this. Twitter offers streaming APIs that you can connect to. As new tweets come in, they will be pushed down to your connection. This means tweets can come in at any time and you need to be ready for them. Twitter will close the connection if you don't keep up.</p>
<p>But we also need to watch the keyboard so the user can press keys to navigate the content we have already displayed. The user doesn't want to wait for the next tweet to come in before some instruction from the keyboard is honored. We must pay attention to both needs at the same time.</p>
<p>Let's dig into the code that waits for each of these data sources. First, the streaming code for Twitter:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">BirdOfParadise</span>
<span class="k">class</span> <span class="nc">Stream</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">read</span>
<span class="vi">@thread</span> <span class="o">=</span> <span class="no">Thread</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">streaming_client</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stream</span><span class="o">|</span>
<span class="n">stream</span><span class="o">.</span><span class="n">user</span> <span class="k">do</span> <span class="o">|</span><span class="n">message</span><span class="o">|</span>
<span class="k">case</span> <span class="n">message</span>
<span class="k">when</span> <span class="no">Twitter</span><span class="o">::</span><span class="no">Streaming</span><span class="o">::</span><span class="no">FriendList</span>
<span class="n">update_followings</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="k">when</span> <span class="no">Twitter</span><span class="o">::</span><span class="no">Tweet</span>
<span class="n">queue_tweet</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">update_followings</span><span class="p">(</span><span class="n">list</span><span class="p">)</span>
<span class="vi">@followings</span> <span class="o">=</span> <span class="n">list</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">queue_tweet</span><span class="p">(</span><span class="n">tweet</span><span class="p">)</span>
<span class="n">q</span> <span class="o"><<</span> <span class="no">Event</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="nb">name</span><span class="p">:</span> <span class="ss">:add_tweet</span><span class="p">,</span> <span class="ss">details</span><span class="p">:</span> <span class="n">build_tweet</span><span class="p">(</span><span class="n">tweet</span><span class="p">))</span>
<span class="k">end</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p><code>Stream.read()</code> could block regularly, waiting on the next call of the block passed to <code>user()</code> (for a <em>user's</em> tweets). We sidestep this issue by wrapping the procedure in a <code>Thread</code>. We don't care how much time it spends waiting since it will just tie up that <code>Thread</code> and the rest of our code can keep running.</p>
<p>I should mention that this Twitter streaming code is incomplete. Twitter can send other events, like instructions to delete a tweet, that a full client does need to handle.</p>
<p>Notice that incoming tweets are just pushed onto an event queue. Whenever you have multiple incoming events, it's usually best to funnel them into the same pipeline and have some other chunk of code work through making the needed changes. This means several channels of execution won't be manipulating shared resources like the screen at the same time.</p>
<p>The keyboard code looks quite similar:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">BirdOfParadise</span>
<span class="k">class</span> <span class="nc">Keyboard</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">read</span>
<span class="vi">@thread</span> <span class="o">=</span> <span class="no">Thread</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">key_reader</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">events</span><span class="p">,</span> <span class="n">keys</span><span class="o">|</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="k">case</span> <span class="n">keys</span><span class="o">.</span><span class="n">call</span>
<span class="k">when</span> <span class="s2">"</span><span class="se">\t</span><span class="s2">"</span>
<span class="n">events</span> <span class="o"><<</span> <span class="no">Event</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="nb">name</span><span class="p">:</span> <span class="ss">:switch_column</span><span class="p">)</span>
<span class="k">when</span> <span class="ss">:UP_ARROW</span><span class="p">,</span> <span class="s2">"k"</span><span class="p">,</span> <span class="s2">"p"</span><span class="p">,</span> <span class="s2">"w"</span>
<span class="n">events</span> <span class="o"><<</span> <span class="no">Event</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="nb">name</span><span class="p">:</span> <span class="ss">:move_up</span><span class="p">)</span>
<span class="k">when</span> <span class="ss">:DOWN_ARROW</span><span class="p">,</span> <span class="s2">"j"</span><span class="p">,</span> <span class="s2">"n"</span><span class="p">,</span> <span class="s2">"s"</span>
<span class="n">events</span> <span class="o"><<</span> <span class="no">Event</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="nb">name</span><span class="p">:</span> <span class="ss">:move_down</span><span class="p">)</span>
<span class="k">when</span> <span class="s2">"q"</span>
<span class="n">events</span> <span class="o"><<</span> <span class="no">Event</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="nb">name</span><span class="p">:</span> <span class="ss">:exit</span><span class="p">)</span>
<span class="k">end</span>
<span class="nb">sleep</span> <span class="mi">0</span><span class="o">.</span><span class="mi">1</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>Again, we tuck some code in a <code>Thread</code> that just loops over incoming keys and turns them into events in a queue. It's not shown here, but the <code>key_reader</code> is a tiny wrapper over <code>Rurses.get_key()</code>.</p>
<p>Before we look at that code, let's talk about the gotcha in the code above. See how my <code>Thread</code> includes a call to <code>sleep()</code>? Earlier, I turned on <code>:non_blocking</code> (<code>timeout(0)</code>) mode, so the <code>key_reader</code> doesn't block. Here I add the small pause to keep this loop from pegging a CPU core. However, you may be wondering, who cares if we block inside that <code>Thread</code>? Won't the rest of the code keep running? In this case, no, it won't.</p>
<p>As you may have heard, Ruby has a <a href="http://en.wikipedia.org/wiki/Global_Interpreter_Lock">Global Interpreter Lock (GIL)</a>. This means only one <code>Thread</code> can truly execute at once. This protects us from some problems we could run into, especially with C extensions. If the <code>Thread</code> above pauses, waiting on a key, the GIL will prevent other code from running while we wait.</p>
<p>However, Twitter's <code>Thread</code> didn't hit this limitation, did it? If you dug down into Twitter's client code, you would find that somewhere deep in the stack it's based on Ruby's <code>IO</code> primitives. Those tools are aware of the GIL and they know a few tricks to avoid it. For example, when they are about to block waiting on input, they release the GIL so that other code may run. When some input finally arrives, they politely wait their turn to reacquire the GIL and only then return the input to your code.</p>
<p><code>curses</code> is different. Remember that we're using <code>ffi-ncurses</code> to make calls into a C API at the lowest levels. FFI doesn't know which calls it could safely release the GIL for, so it never does. That's why we need <code>:non_blocking</code> mode and some pauses. We're giving other code some time to run before we make another C call.</p>
<p>Enough about tricky threading details. Here's the <code>Rurses.get_key()</code> code that's indirectly used above:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">Rurses</span>
<span class="no">SPECIAL_KEYS</span> <span class="o">=</span> <span class="no">Hash</span><span class="o">[</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">KeyDefs</span>
<span class="o">.</span><span class="n">constants</span>
<span class="o">.</span><span class="n">select</span> <span class="p">{</span> <span class="o">|</span><span class="nb">name</span><span class="o">|</span> <span class="nb">name</span><span class="o">.</span><span class="n">to_s</span><span class="o">.</span><span class="n">start_with?</span><span class="p">(</span><span class="s2">"KEY_"</span><span class="p">)</span> <span class="p">}</span>
<span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="nb">name</span><span class="o">|</span>
<span class="o">[</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">KeyDefs</span><span class="o">.</span><span class="n">const_get</span><span class="p">(</span><span class="nb">name</span><span class="p">),</span>
<span class="nb">name</span><span class="o">.</span><span class="n">to_s</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr">/\AKEY_/</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span><span class="o">.</span><span class="n">to_sym</span> <span class="o">]</span>
<span class="p">}</span>
<span class="o">]</span>
<span class="kp">module_function</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">get_key</span>
<span class="k">case</span> <span class="p">(</span><span class="n">char</span> <span class="o">=</span> <span class="n">curses</span><span class="o">.</span><span class="n">getch</span><span class="p">)</span>
<span class="k">when</span> <span class="n">curses</span><span class="o">::</span><span class="no">KeyDefs</span><span class="o">::</span><span class="no">KEY_CODE_YES</span><span class="o">.</span><span class="n">.curses</span><span class="o">::</span><span class="no">KeyDefs</span><span class="o">::</span><span class="no">KEY_MAX</span>
<span class="no">SPECIAL_KEYS</span><span class="o">[</span><span class="n">char</span><span class="o">]</span>
<span class="k">when</span> <span class="n">curses</span><span class="o">::</span><span class="no">ERR</span>
<span class="kp">nil</span>
<span class="k">else</span>
<span class="n">char</span><span class="o">.</span><span class="n">chr</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># ...</span>
<span class="k">end</span>
</pre></div>
<p>The <code>getch()</code> function from <code>curses</code> can only return integers, so it uses different numbers to mean different things. Some are ASCII codes for keys on the keyboard, others are special constants for things like arrow keys, and one even means there's no available input right now (when you're in <code>:non_blocking</code> mode and don't wish to wait).</p>
<p>In Ruby, I can return different objects for the different cases and still sort out the comparisons with a simple <code>case</code> statement. Given that, I premap all of the <code>SPECIAL_KEYS</code> to <code>Symbol</code> names, use <code>nil</code> for no input, and transform the ASCII codes into actual <code>String</code> characters. You can scroll back up to see how each type is handled, if you need a reminder.</p>
<h4>Controlling Where Output Goes</h4>
<p>Rurses encourages code to work with <code>Window</code> objects, which wrap <code>curses</code> windows. The idea behind Ruby's version of the <code>Window</code> is straightforward: keep a reference to the <code>curses</code> window pointer and pass it to functions called as needed. Here's how that looks in practice:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">Rurses</span>
<span class="k">class</span> <span class="nc">Window</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="o">**</span><span class="n">details</span><span class="p">)</span>
<span class="vi">@curses_ref</span> <span class="o">=</span> <span class="n">details</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="ss">:curses_ref</span><span class="p">)</span> <span class="p">{</span>
<span class="no">Rurses</span><span class="o">.</span><span class="n">curses</span><span class="o">.</span><span class="n">newwin</span><span class="p">(</span>
<span class="n">details</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="ss">:lines</span><span class="p">),</span>
<span class="n">details</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="ss">:columns</span><span class="p">),</span>
<span class="n">details</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="ss">:y</span><span class="p">),</span>
<span class="n">details</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="ss">:x</span><span class="p">)</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="vi">@standard_screen</span> <span class="o">=</span> <span class="n">details</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="ss">:standard_screen</span><span class="p">)</span> <span class="p">{</span> <span class="kp">false</span> <span class="p">}</span>
<span class="vi">@subwindows</span> <span class="o">=</span> <span class="p">{</span> <span class="p">}</span>
<span class="k">end</span>
<span class="kp">attr_reader</span> <span class="ss">:curses_ref</span><span class="p">,</span> <span class="ss">:subwindows</span>
<span class="k">def</span> <span class="nf">standard_screen?</span>
<span class="vi">@standard_screen</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">cursor_x</span>
<span class="no">Rurses</span><span class="o">.</span><span class="n">curses</span><span class="o">.</span><span class="n">getcurx</span><span class="p">(</span><span class="n">curses_ref</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">cursor_y</span>
<span class="no">Rurses</span><span class="o">.</span><span class="n">curses</span><span class="o">.</span><span class="n">getcury</span><span class="p">(</span><span class="n">curses_ref</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">cursor_xy</span>
<span class="n">y</span><span class="p">,</span> <span class="n">x</span> <span class="o">=</span> <span class="no">Rurses</span><span class="o">.</span><span class="n">curses</span><span class="o">.</span><span class="n">getyx</span><span class="p">(</span><span class="n">curses_ref</span><span class="p">)</span>
<span class="p">{</span><span class="ss">x</span><span class="p">:</span> <span class="n">x</span><span class="p">,</span> <span class="ss">y</span><span class="p">:</span> <span class="n">y</span><span class="p">}</span>
<span class="k">end</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>You get the idea. You can either pass a <code>curses_ref</code> when you create the <code>Window</code> (as <code>Rurses.program()</code> does for <code>stdscr</code>) or just pass dimensions and coordinates to have the underlying structure created for you. Have a look at the <code>cursor_*()</code> methods to see how the reference is used.</p>
<p>Now, I also want to have an easy way to manage <code>curses</code> subwindows. You can see that the code above allocates a <code>Hash</code> for them and sets up a reader. Here's a couple more methods in <code>Window</code> for managing subwindows:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">Rurses</span>
<span class="k">class</span> <span class="nc">Window</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">create_subwindow</span><span class="p">(</span> <span class="nb">name</span><span class="p">:</span> <span class="p">,</span> <span class="n">top_padding</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">left_padding</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="n">right_padding</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">bottom_padding</span><span class="p">:</span> <span class="mi">0</span> <span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">size</span>
<span class="n">xy</span> <span class="o">=</span> <span class="n">cursor_xy</span>
<span class="n">subwindows</span><span class="o">[</span><span class="nb">name</span><span class="o">]</span> <span class="o">=</span>
<span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">new</span><span class="p">(</span>
<span class="n">curses_ref</span><span class="p">:</span> <span class="no">Rurses</span><span class="o">.</span><span class="n">curses</span><span class="o">.</span><span class="n">derwin</span><span class="p">(</span>
<span class="n">curses_ref</span><span class="p">,</span>
<span class="n">s</span><span class="o">[</span><span class="ss">:lines</span><span class="o">]</span> <span class="o">-</span> <span class="p">(</span><span class="n">top_padding</span> <span class="o">+</span> <span class="n">bottom_padding</span><span class="p">),</span>
<span class="n">s</span><span class="o">[</span><span class="ss">:columns</span><span class="o">]</span> <span class="o">-</span> <span class="p">(</span><span class="n">left_padding</span> <span class="o">+</span> <span class="n">right_padding</span><span class="p">),</span>
<span class="n">xy</span><span class="o">[</span><span class="ss">:y</span><span class="o">]</span> <span class="o">+</span> <span class="n">top_padding</span><span class="p">,</span>
<span class="n">xy</span><span class="o">[</span><span class="ss">:x</span><span class="o">]</span> <span class="o">+</span> <span class="n">left_padding</span>
<span class="p">)</span>
<span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">subwindow</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
<span class="n">subwindows</span><span class="o">[</span><span class="nb">name</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p><code>create_subwindow()</code> constructs another <code>Window</code> object, using some relative coordinate math, and adds it to the <code>Hash</code> by name. You can then later access any subwindow by name using the <code>subwindow()</code> method.</p>
<p>My Twitter client uses this combination of <code>Window</code> objects and their attached subwindows to divide the screen into columns. It also separates content from bordered regions to avoid any overwriting. Here's the code that arranges the screen, called on start and in the event of a terminal resize operation:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">BirdOfParadise</span>
<span class="k">class</span> <span class="nc">Screen</span>
<span class="c1"># ...</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">layout</span>
<span class="n">timeline_width</span><span class="p">,</span> <span class="n">mentions_width</span><span class="p">,</span> <span class="n">lines</span> <span class="o">=</span> <span class="n">calculate_column_sizes</span>
<span class="o">[</span>
<span class="p">{</span> <span class="ss">x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="ss">columns</span><span class="p">:</span> <span class="n">timeline_width</span><span class="p">,</span> <span class="ss">feed</span><span class="p">:</span> <span class="n">timeline</span> <span class="p">},</span>
<span class="p">{</span> <span class="ss">x</span><span class="p">:</span> <span class="n">timeline_width</span><span class="p">,</span> <span class="ss">columns</span><span class="p">:</span> <span class="n">mentions_width</span><span class="p">,</span> <span class="ss">feed</span><span class="p">:</span> <span class="n">mentions</span> <span class="p">}</span>
<span class="o">].</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">details</span><span class="o">|</span>
<span class="n">window</span> <span class="o">=</span> <span class="no">Rurses</span><span class="o">::</span><span class="no">Window</span><span class="o">.</span><span class="n">new</span><span class="p">(</span>
<span class="ss">lines</span><span class="p">:</span> <span class="n">lines</span><span class="p">,</span>
<span class="ss">columns</span><span class="p">:</span> <span class="n">details</span><span class="o">[</span><span class="ss">:columns</span><span class="o">]</span><span class="p">,</span>
<span class="ss">x</span><span class="p">:</span> <span class="n">details</span><span class="o">[</span><span class="ss">:x</span><span class="o">]</span><span class="p">,</span>
<span class="ss">y</span><span class="p">:</span> <span class="mi">0</span>
<span class="p">)</span>
<span class="n">window</span><span class="o">.</span><span class="n">create_subwindow</span><span class="p">(</span>
<span class="nb">name</span><span class="p">:</span> <span class="ss">:content</span><span class="p">,</span>
<span class="n">top_padding</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="n">left_padding</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="n">right_padding</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="n">bottom_padding</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">)</span>
<span class="n">panels</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">window</span><span class="p">)</span>
<span class="n">details</span><span class="o">[</span><span class="ss">:feed</span><span class="o">].</span><span class="n">window</span> <span class="o">=</span> <span class="n">window</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>This method builds two <code>Window</code> objects, adds a <code>:content</code> subwindow to each, and adds them as the canvas that a couple of not-yet-shown <code>Feed</code> objects will draw their output on. Let's have a look at that drawing code:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">BirdOfParadise</span>
<span class="k">class</span> <span class="nc">Feed</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">redraw</span>
<span class="k">if</span> <span class="n">changed?</span>
<span class="n">content</span> <span class="o">=</span> <span class="n">window</span><span class="o">.</span><span class="n">subwindow</span><span class="p">(</span><span class="ss">:content</span><span class="p">)</span>
<span class="n">size</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">size</span>
<span class="n">content</span><span class="o">.</span><span class="n">clear</span>
<span class="n">tweets</span>
<span class="o">.</span><span class="n">lines</span><span class="p">(</span><span class="ss">count</span><span class="p">:</span> <span class="n">size</span><span class="o">[</span><span class="ss">:lines</span><span class="o">]</span><span class="p">,</span> <span class="ss">width</span><span class="p">:</span> <span class="n">size</span><span class="o">[</span><span class="ss">:columns</span><span class="o">]</span><span class="p">)</span>
<span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">line</span><span class="p">,</span> <span class="n">cursor_location</span><span class="o">|</span>
<span class="k">if</span> <span class="n">line</span>
<span class="n">attributes</span> <span class="o">=</span> <span class="n">selected?</span> <span class="o">&&</span> <span class="n">cursor_location</span> <span class="p">?</span> <span class="o">%</span><span class="n">i</span><span class="o">[</span><span class="n">bold</span><span class="o">]</span> <span class="p">:</span> <span class="o">[</span> <span class="o">]</span>
<span class="n">content</span><span class="o">.</span><span class="n">style</span><span class="p">(</span><span class="o">*</span><span class="n">attributes</span><span class="p">)</span> <span class="k">do</span>
<span class="n">content</span><span class="o">.</span><span class="n">draw_string_on_a_line</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">else</span>
<span class="n">content</span><span class="o">.</span><span class="n">skip_line</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="vi">@changed</span> <span class="o">=</span> <span class="kp">false</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>This process is pretty basic. The <code>:content</code> area is cleared, the visible chunk of tweets is rendered as some lines, and the lines are added to the <code>:content</code> area one by one. I realize that I haven't show you all of the methods used here, but I bet you can guess what most of them do.</p>
<p>What you don't see here is any cursor moving code. The reason for that is that I'm using some methods in <code>Window</code> that add some sensible cursor management to standard operations. Have a look:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">Rurses</span>
<span class="k">class</span> <span class="nc">Window</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">move_cursor</span><span class="p">(</span><span class="ss">x</span><span class="p">:</span> <span class="p">,</span> <span class="ss">y</span><span class="p">:</span> <span class="p">)</span>
<span class="no">Rurses</span><span class="o">.</span><span class="n">curses</span><span class="o">.</span><span class="n">wmove</span><span class="p">(</span><span class="n">curses_ref</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">draw_string</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
<span class="no">Rurses</span><span class="o">.</span><span class="n">curses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">curses_ref</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">draw_string_on_a_line</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
<span class="n">old_y</span> <span class="o">=</span> <span class="n">cursor_y</span>
<span class="n">draw_string</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
<span class="n">new_y</span> <span class="o">=</span> <span class="n">cursor_y</span>
<span class="n">move_cursor</span><span class="p">(</span><span class="ss">x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="ss">y</span><span class="p">:</span> <span class="n">new_y</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="k">if</span> <span class="n">new_y</span> <span class="o">==</span> <span class="n">old_y</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">skip_line</span>
<span class="n">move_cursor</span><span class="p">(</span><span class="ss">x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="ss">y</span><span class="p">:</span> <span class="n">cursor_y</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">clear</span><span class="p">(</span><span class="n">reset_cursor</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span>
<span class="no">Rurses</span><span class="o">.</span><span class="n">curses</span><span class="o">.</span><span class="n">wclear</span><span class="p">(</span><span class="n">curses_ref</span><span class="p">)</span>
<span class="n">move_cursor</span><span class="p">(</span><span class="ss">x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="ss">y</span><span class="p">:</span> <span class="mi">0</span><span class="p">)</span> <span class="k">if</span> <span class="n">reset_cursor</span>
<span class="k">end</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>See how <code>draw_string_on_a_line()</code> bumps the cursor down (assuming it didn't wrap) after each add? <code>clear()</code> also restores the cursor to the top left corner by default. This can save user code from needing to do a lot of manual move commands, in some cases.</p>
<h4>Managing Redraw</h4>
<p>If you were paying close attention earlier, you may have caught this line that I never explained:</p>
<div class="highlight highlight-ruby"><pre> <span class="c1"># ...</span>
<span class="n">panels</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">window</span><span class="p">)</span>
<span class="c1"># ...</span>
</pre></div>
<p>As each <code>Window</code> is constructed in the Twitter client, it is added to a <code>Rurses::PanelStack</code>. This wraps <code>curses</code> panels much like <code>Rurses::Window</code> wraps windows. However, there's just this one call, even with subwindows involved. That's because <code>PanelStack</code> also has some added niceties:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">Rurses</span>
<span class="k">class</span> <span class="nc">PanelStack</span>
<span class="k">def</span> <span class="nf">initialize</span>
<span class="vi">@window_to_panel_map</span> <span class="o">=</span> <span class="p">{</span> <span class="p">}</span>
<span class="k">end</span>
<span class="kp">attr_reader</span> <span class="ss">:window_to_panel_map</span>
<span class="kp">private</span> <span class="ss">:window_to_panel_map</span>
<span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="n">add_subwindows</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span>
<span class="n">window_to_panel_map</span><span class="o">[</span><span class="n">window</span><span class="o">]</span> <span class="o">=</span> <span class="no">Rurses</span><span class="o">.</span><span class="n">curses</span><span class="o">.</span><span class="n">new_panel</span><span class="p">(</span><span class="n">window</span><span class="o">.</span><span class="n">curses_ref</span><span class="p">)</span>
<span class="k">if</span> <span class="n">add_subwindows</span>
<span class="n">window</span><span class="o">.</span><span class="n">subwindows</span><span class="o">.</span><span class="n">each_value</span> <span class="k">do</span> <span class="o">|</span><span class="n">subwindow</span><span class="o">|</span>
<span class="n">add</span><span class="p">(</span><span class="n">subwindow</span><span class="p">,</span> <span class="n">add_subwindows</span><span class="p">:</span> <span class="n">add_subwindows</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">alias_method</span> <span class="ss">:<<</span><span class="p">,</span> <span class="ss">:add</span>
<span class="k">def</span> <span class="nf">remove</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="n">remove_subwindows</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span>
<span class="k">if</span> <span class="n">remove_subwindows</span>
<span class="n">window</span><span class="o">.</span><span class="n">subwindows</span><span class="o">.</span><span class="n">each_value</span> <span class="k">do</span> <span class="o">|</span><span class="n">subwindow</span><span class="o">|</span>
<span class="n">remove</span><span class="p">(</span><span class="n">subwindow</span><span class="p">,</span> <span class="n">remove_subwindows</span><span class="p">:</span> <span class="n">remove_subwindows</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">window</span><span class="o">.</span><span class="n">clear</span>
<span class="no">Rurses</span><span class="o">.</span><span class="n">curses</span><span class="o">.</span><span class="n">del_panel</span><span class="p">(</span><span class="n">window_to_panel_map</span><span class="o">[</span><span class="n">window</span><span class="o">]</span><span class="p">)</span>
<span class="no">Rurses</span><span class="o">.</span><span class="n">curses</span><span class="o">.</span><span class="n">delwin</span><span class="p">(</span><span class="n">window</span><span class="o">.</span><span class="n">curses_ref</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">refresh_in_memory</span>
<span class="no">Rurses</span><span class="o">.</span><span class="n">curses</span><span class="o">.</span><span class="n">update_panels</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>Both <code>add()</code> and <code>remove()</code> handle any subwindows by default. This object also keeps a mapping of Ruby <code>Window</code> objects to <code>curses</code> panels, so user code doesn't need to worry about tracking another set of references. I've found that just this much support makes panel management all but invisible.</p>
<p>The final piece of the puzzle is the method that updates the screen. It is called after each event is pulled from the queue and processed. Here's the code:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">BirdOfParadise</span>
<span class="k">class</span> <span class="nc">Screen</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">update</span>
<span class="n">timeline</span><span class="o">.</span><span class="n">redraw</span>
<span class="n">mentions</span><span class="o">.</span><span class="n">redraw</span>
<span class="n">panels</span><span class="o">.</span><span class="n">refresh_in_memory</span>
<span class="no">Rurses</span><span class="o">.</span><span class="n">update_screen</span>
<span class="k">end</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>We have now looked at every call in there except the very last one and it just wraps <code>doupdate()</code> from <code>curses</code>.</p>
<p>This completes our tour of the primary <code>curses</code> interactions, but, as I said before, <a href="https://github.com/JEG2/bird_of_paradise">the full application is on GitHub</a>. Interested parties are encourages to explore the code further.</p>James Edward Gray IICurses Windows, Pads, and Panelstag:graysoftinc.com,2015-03-11:/posts/1382015-09-14T23:42:58ZThis article looks at the abstractions curses provides for managing drawing your content to various parts of the screen.<p>In <a href="http://graysoftinc.com/terminal-tricks/basic-curses">the previous article</a>, I showed how you can accomplish some terminal input and output operations using the <code>(n)curses(w)</code> library. What I showed for output were mostly low-level writing routines though. Included in <code>curses</code> are some higher level abstractions that can be used to manage your program's output. This time, I want to show some of those tools.</p>
<h4>Windows</h4>
<p>The primary abstraction used in <code>curses</code> is the concept of a <code>window</code>. We actually already used them last time, but we stuck with <code>stdscr</code> (<em>standard screen</em>) which is just a window that fills the entire terminal. That's more for using <code>curses</code> without thinking much about windows.</p>
<p>When you want them though, you can section off the terminal screen into rectangular chunks. Each chunk is a window that you can print output inside of. Here's the most basic example that I could dream up:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">cbreak</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">noecho</span>
<span class="n">window</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">newwin</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">15</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="c1"># make a new window</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="s2">"Hello world!"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span> <span class="c1"># still need this</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">window</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
</pre></div>
<p>The main new bit in this code is the call to <code>newwin()</code>. As you can probably guess, this creates a new window. The window can contain the number of lines and characters specified in this call, <code>5</code> and <code>15</code> respectively in the code above. The other two numbers specify where this window is to be located on the screen: <code>4</code> lines down and <code>2</code> characters indented. (Remember, <code>curses</code> favors putting row before column.)</p>
<p>The two calls to <code>wrefresh()</code> may surprise you. <code>curses</code> still creates the <code>stdscr</code> and you can kind of think of it as backdrop of our output. We still need to <em>draw</em> it to clear the contents behind our window. Then our window can be drawn on top of that. Honestly though, the way I've done it here isn't ideal.</p>
<p>I used <code>wrefresh()</code> because we talked about it last time, but it doesn't make much sense, once multiple windows are involved. The reason is that <code>curses</code> maintains a kind of staged drawing pipeline. First, an in-memory virtual screen is <em>refreshed</em> with any new contents. Then the actual screen is <em>updated</em> with the contents of the virtual screen. <code>wrefresh()</code> confusingly triggers both of these steps. So the code above refreshes, updates, then refreshes and updates again right away. It would make more sense to just refresh the contents of both windows into the virtual screen, then update the real screen one time. To do that, we switch these two lines:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span> <span class="c1"># still need this</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">window</span><span class="p">)</span>
<span class="c1"># ...</span>
</pre></div>
<p>to the more efficient:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span> <span class="c1"># still need this</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">window</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">doupdate</span>
<span class="c1"># ...</span>
</pre></div>
<p>I'm guessing the added <code>nout</code> stands for <em>no output</em>, but the purpose of <code>wnoutrefresh()</code> is to refresh without an automatic update. We do that for both windows, then we call for the update with the surprisingly well named <code>doupdate()</code>. <strong>You'll want to separate refresh and update operations when using multiple <code>curses</code> windows.</strong></p>
<p>Now if you run that code, you'll probably see the not-too-impressive output:</p>
<pre><code>
Hello world!
</code></pre>
<p>It doesn't look like much yet, does it? You can kind of see that my window is displaced from the top left corner due to the extra whitespace, but you can't really see the majority of the window itself because the parts that don't have content are invisible. Let's see if we can fix that;</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">cbreak</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">noecho</span>
<span class="n">window</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">newwin</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">15</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="c1"># draw a box</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="s2">"Hello world!"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">window</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">doupdate</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
</pre></div>
<p>The sadly cryptic call to <code>wborder()</code> asks <code>curses</code> to draw a box around our window. That gives us the possibly surprising output of:</p>
<pre><code>
Hello world!--+
| |
| |
| |
+-------------+
</code></pre>
<p>Your border may not look exactly like mine. More on why that is a little later. I'm also going to delay a discussion of the eight magic <code>0</code>'s. We're having enough trouble trying to get the border in the right place for now and those arguments won't help.</p>
<p>The good news is that we can now see our window. The bad news is that the border was drawn inside our content space and then we wrote over it. Oops. Knowing that you can write over the border could be potentially useful for titling windows, but I was hoping to see some content inside a box.</p>
<p>It's possibly interesting to note that reversing these two operations:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="c1"># draw a box</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="s2">"Hello world!"</span><span class="p">)</span>
<span class="c1"># ...</span>
</pre></div>
<p>to:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="s2">"Hello world!"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="c1"># draw a box</span>
<span class="c1"># ...</span>
</pre></div>
<p>still fails to produce the desired results. Instead we see:</p>
<pre><code>
+-------------+
| |
| |
| |
+-------------+
</code></pre>
<p>In this case, our content was added, then drawn over by the box. Not helpful, right?</p>
<p>The two operations that I actually wanted were these:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">mvwaddstr</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">"Hello world!"</span><span class="p">)</span> <span class="c1"># move then add content</span>
<span class="c1"># ...</span>
</pre></div>
<p>I've changed the content to be drawn a little down and in from the top of the window to put it inside the border. That gives the result I've been trying to achieve:</p>
<pre><code>
+-------------+
|Hello world! |
| |
| |
+-------------+
</code></pre>
<p>Seeing these border complications made me wonder what would happen if you overran the boundaries of a window. The answer is not what I expected, so we better talk about how that works:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">cbreak</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">noecho</span>
<span class="n">window</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">newwin</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">mvwaddstr</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">"123456</span><span class="se">\n</span><span class="s2">7890</span><span class="se">\n</span><span class="s2">abcd</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> <span class="c1"># overflow</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">window</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">doupdate</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
</pre></div>
<p>This code produces, well, a mess:</p>
<pre><code>+----+
|12345
6
7890
</code></pre>
<p>Again we see that we can overwrite a border here. We also see that excess content on a line is hard wrapped to the next line. However, excess lines are discarded.</p>
<p>I wouldn't describe at least two of those operations as desirable. Probably the easiest workaround, in this case, is to swap the order of operations and let the border win:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">mvwaddstr</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">"123456</span><span class="se">\n</span><span class="s2">7890</span><span class="se">\n</span><span class="s2">abcd</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="c1"># ...</span>
</pre></div>
<p>This gets things looking more normal, but it's still probably not desirable:</p>
<pre><code>+----+
|1234|
| |
+----+
</code></pre>
<p>We now see nothing on the second line because the content that was there, a lone wrapped <code>6</code>, was overwritten by the border and the following newline pushed everything else down a line. This put our second line of content under the bottom border and pushed the rest into oblivion.</p>
<p>Honestly though, this issue is worse than it seems. Remember this?</p>
<pre><code>+----+
|12345
6
7890
</code></pre>
<p>We know that the <code>5</code> ate one piece of the right side border, but what happened to the other one? Let's try a simpler experiment:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">mvwaddstr</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">"1</span><span class="se">\n</span><span class="s2">2"</span><span class="p">)</span>
<span class="c1"># ...</span>
</pre></div>
<p>This yields:</p>
<pre><code>+----+
|1
2 |
+----+
</code></pre>
<p>Newlines break borders. Not ideal. Again we could draw the box last, but there are just two many issues here to easily get around. <strong>My advice: strip newlines out of the content you write into <code>curses</code> windows (use move operations instead).</strong></p>
<p>We'll come back to better ways of protecting your borders from being overwritten, but first let's think a little more on those characters that were thrown into oblivion.</p>
<h4>Pads</h4>
<p>Many applications are going to have more content than will fit in a <code>curses</code> window. Using a plain window, you'll need to manage that content in some data structure and regularly redraw the currently visible portion into the window. That's one option.</p>
<p>Another option is to use a <em>pad</em> instead of a window. In <code>curses</code>, a pad is a window that can hold more content than it shows. You then just tell <code>curses</code> which portion of the content to show on the screen. I thought that sounded pretty awesome when I first heard about it, but it's not without tradeoffs. Let's walk through an example and I'll try to point out the good and not-so-good parts:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">cbreak</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">noecho</span>
<span class="n">pad</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">newpad</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span> <span class="c1"># instead of newwin()</span>
<span class="c1">#</span>
<span class="c1"># Create some data like:</span>
<span class="c1">#</span>
<span class="c1"># 00AA</span>
<span class="c1"># 11BB</span>
<span class="c1"># 22CC</span>
<span class="c1"># 33DD</span>
<span class="c1">#</span>
<span class="mi">4</span><span class="o">.</span><span class="n">times</span> <span class="k">do</span> <span class="o">|</span><span class="n">n</span><span class="o">|</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">mvwaddstr</span><span class="p">(</span> <span class="n">pad</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="s2">"</span><span class="si">#{</span><span class="n">n</span><span class="si">}</span><span class="s2">"</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span>
<span class="p">(</span><span class="s2">"A"</span><span class="o">.</span><span class="n">codepoints</span><span class="o">.</span><span class="n">first</span> <span class="o">+</span> <span class="n">n</span><span class="p">)</span><span class="o">.</span><span class="n">chr</span> <span class="o">*</span> <span class="mi">2</span> <span class="p">)</span>
<span class="k">end</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">pnoutrefresh</span><span class="p">(</span><span class="n">pad</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="c1"># instead of wnoutrefresh()</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">doupdate</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">pnoutrefresh</span><span class="p">(</span><span class="n">pad</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">doupdate</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
</pre></div>
<p>The comments point out the main differences here. First, pads are created with <code>newpad()</code>, and unlike <code>newwin()</code>, that does not take screen location arguments. You only pass the sizes for the content area.</p>
<p>Once created a pad is <strong>mostly</strong> a window. You can generally use the same functions that you use on windows, as I have with <code>mvwaddstr()</code> in the code above.</p>
<p>The exception is that the <code>*refresh()</code> functions do not work on pads. Instead you'll need to call <code>prefresh()</code> or <code>pnoutrefresh()</code> (<code>p</code> for <em>pad</em>). These functions require some extra arguments. The first two, after the pad, are the coordinates of the top left corner in your content that you wish to display. The next two are the coordinates you would have normally passed to <code>newwin()</code> for the top left corner to use on the screen. The last two give the lower right corner for the screen. The lower right corner of the content is inferred using the size of the box on screen.</p>
<p>Let's see how this works in action. If I run that code, it will show me the upper left corner of the content before I push a button:</p>
<pre><code>00
11
</code></pre>
<p>When I push the button, it skips past the first <code>getch()</code> to the second <code>pnoutrefresh()</code>. Those coordinates shift the view into the middle of the content:</p>
<pre><code>1B
2C
</code></pre>
<p>The ability to move around some content like that is awesome, if you ask me, but the means for doing it, via the <code>*refresh()</code> calls, feels strange to me. I don't think I would often have a desire to shift the visible window around on the screen, which seems to be the case these coordinates are optimized for. Instead, I think I would prefer to keep passing the visible corners into <code>newpad()</code>, as I do <code>newwin()</code>, and then just update which portion of the content is currently shown. I would also prefer to set the content coordinates outside of the refresh mechanism and have them stick through redraws until I change them again. The reason for that gets us to the first major drawback of pads.</p>
<p>We've talked about how our code triggers the various stages of the redraw pipeline. Sometimes though, <code>curses</code> itself triggers redraws. This can happen because of terminal scrolling or the need to echo out some input. However, because all of the coordinates decisions of pads are tied to the refresh stage, <code>curses</code> won't know the current numbers to use. As such pads cannot be automatically updated.</p>
<p>The other reason I'm not super sold on pads is that they still don't make it very easy to update the content they hold. You have to set a fixed size on the content portion, then rewrite that content as needed using the same tools you do for windows. That means it's easy to add lines onto the end, assuming you still have the room, but adding a line at the beginning would mean rewriting all of the content in the pad. That doesn't seem superior to using windows.</p>
<p>As you can see, it's all tradeoffs. If I needed to scroll some static content or move the visible window around the screen for some reason (<a href="http://en.wikipedia.org/wiki/Fog_of_war#Simulations_and_games">fog of war</a> in a <a href="http://en.wikipedia.org/wiki/Roguelike">roguelike</a> game?), I might reach for a pad. Otherwise, I'm pretty sure the content will need to be in a separate data structure anyway and I can probably use a window just as easily.</p>
<h4>Borders</h4>
<p>Remember all of that hand waving I did about borders in my early examples? Well, it's time to make good on those promised explanations.</p>
<p>Here's a trivial border example to refresh your memory:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">cbreak</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">noecho</span>
<span class="n">window</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">newwin</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">window</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">doupdate</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
</pre></div>
<p>In one terminal, that draws this box for me:</p>
<pre><code>+----+
| |
| |
+----+
</code></pre>
<p>In a different terminal, presumably with better encoding defaults set, I get:</p>
<pre><code>┌────┐
│ │
│ │
└────┘
</code></pre>
<p>The differences occur because the eight <code>0</code>'s tell <code>curses</code> that we'll accept the default character for that position. It seems the chosen default can vary depending on the environment.</p>
<p>Each <code>0</code> represents a different character in the border. Probably the easiest way to see which character each argument represents is just to have <code>curses</code> show you. Change this line:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="c1"># ...</span>
</pre></div>
<p>to this:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="o">*</span><span class="p">(</span><span class="mi">1</span><span class="o">.</span><span class="n">.</span><span class="mi">8</span><span class="p">)</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">n</span><span class="o">|</span> <span class="n">n</span><span class="o">.</span><span class="n">to_s</span><span class="o">.</span><span class="n">codepoints</span><span class="o">.</span><span class="n">first</span> <span class="p">})</span>
<span class="c1"># ...</span>
</pre></div>
<p>You should see this cheatsheet:</p>
<pre><code>533336
1 2
1 2
744448
</code></pre>
<p>Using these locations, you can make whatever fancy border you would like. For example, this code:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span>
<span class="n">window</span><span class="p">,</span>
<span class="s2">">"</span><span class="o">.</span><span class="n">codepoints</span><span class="o">.</span><span class="n">first</span><span class="p">,</span>
<span class="s2">"<"</span><span class="o">.</span><span class="n">codepoints</span><span class="o">.</span><span class="n">first</span><span class="p">,</span>
<span class="s2">"v"</span><span class="o">.</span><span class="n">codepoints</span><span class="o">.</span><span class="n">first</span><span class="p">,</span>
<span class="s2">"^"</span><span class="o">.</span><span class="n">codepoints</span><span class="o">.</span><span class="n">first</span><span class="p">,</span>
<span class="s2">"</span><span class="se">\\</span><span class="s2">"</span><span class="o">.</span><span class="n">codepoints</span><span class="o">.</span><span class="n">first</span><span class="p">,</span>
<span class="s2">"/"</span><span class="o">.</span><span class="n">codepoints</span><span class="o">.</span><span class="n">first</span><span class="p">,</span>
<span class="s2">"/"</span><span class="o">.</span><span class="n">codepoints</span><span class="o">.</span><span class="n">first</span><span class="p">,</span>
<span class="s2">"</span><span class="se">\\</span><span class="s2">"</span><span class="o">.</span><span class="n">codepoints</span><span class="o">.</span><span class="n">first</span>
<span class="p">)</span>
<span class="c1"># ...</span>
</pre></div>
<p>gives us a Tie-fighter looking window:</p>
<pre><code>\vvvv/
> <
> <
/^^^^\
</code></pre>
<p>The real thing you need to catch from these examples is that characters are specified as <code>Integer</code>s, not <code>String</code>s. The examples I've shown so far are just for ASCII characters. You can generally use <em>wide</em> characters (read: Unicode) instead, with uglier code:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="n">chars</span> <span class="o">=</span> <span class="no">Hash</span><span class="o">[</span> <span class="o">%</span><span class="n">i</span><span class="o">[</span><span class="no">ULCORNER</span> <span class="no">LLCORNER</span> <span class="no">URCORNER</span> <span class="no">LRCORNER</span> <span class="no">HLINE</span> <span class="no">VLINE</span><span class="o">].</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="nb">name</span><span class="o">|</span>
<span class="n">char</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">WinStruct</span><span class="o">::</span><span class="no">CCharT</span><span class="o">.</span><span class="n">new</span>
<span class="n">char</span><span class="o">[</span><span class="ss">:chars</span><span class="o">][</span><span class="mi">0</span><span class="o">]</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">const_get</span><span class="p">(</span><span class="s2">"WACS_</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="o">[</span><span class="nb">name</span><span class="p">,</span> <span class="n">char</span><span class="o">]</span>
<span class="p">}</span> <span class="o">]</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder_set</span><span class="p">(</span>
<span class="n">window</span><span class="p">,</span>
<span class="n">chars</span><span class="o">[</span><span class="ss">:VLINE</span><span class="o">]</span><span class="p">,</span>
<span class="n">chars</span><span class="o">[</span><span class="ss">:VLINE</span><span class="o">]</span><span class="p">,</span>
<span class="n">chars</span><span class="o">[</span><span class="ss">:HLINE</span><span class="o">]</span><span class="p">,</span>
<span class="n">chars</span><span class="o">[</span><span class="ss">:HLINE</span><span class="o">]</span><span class="p">,</span>
<span class="n">chars</span><span class="o">[</span><span class="ss">:ULCORNER</span><span class="o">]</span><span class="p">,</span>
<span class="n">chars</span><span class="o">[</span><span class="ss">:URCORNER</span><span class="o">]</span><span class="p">,</span>
<span class="n">chars</span><span class="o">[</span><span class="ss">:LLCORNER</span><span class="o">]</span><span class="p">,</span>
<span class="n">chars</span><span class="o">[</span><span class="ss">:LRCORNER</span><span class="o">]</span>
<span class="p">)</span>
<span class="c1"># ...</span>
</pre></div>
<p>That code gives me the fancier box, even on the terminal that defaulted to using <code>+</code>, <code>-</code>, and <code>|</code>. The two steps for using Unicode are that you need to construct and fill in <code>FFI::NCurses::WinStruct::CCharT</code> objects for each character and pass those to <code>wborder_set()</code>, instead of <code>wborder()</code>.</p>
<p>For this example, I've used a set of constants defined by <code>curses</code>: <code>WACS_VLINE</code>, <code>WACS_HLINE</code>, etc. These are the wide character defaults for box drawing. You could substitute the codepoints for any Unicode characters you need.</p>
<h4>Subwindows</h4>
<p>OK, so we know it's easy for our content to clobber our borders. I also found myself wanting to answer questions like, "Could I introduce some HTML-like padding between a border and the content?" The solution to both of these problems is <em>subwindows</em>.</p>
<p>A subwindow is just another window. However, it has a parent window, so to speak, and it actually occupies a space inside that parent window. Let's show that in some code:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">cbreak</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">noecho</span>
<span class="n">window</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">newwin</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">content</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">derwin</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="c1"># create a subwindow</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="s2">"123456</span><span class="se">\n</span><span class="s2">7890</span><span class="se">\n</span><span class="s2">abcd</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">window</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">touchwin</span><span class="p">(</span><span class="n">window</span><span class="p">)</span> <span class="c1"># do this before refreshing subwindows</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">doupdate</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
</pre></div>
<p>The comment points out the line that creates the subwindow. On that line, the <code>2, 4</code> arguments define the content size, just as they would for a normal window. The following <code>2, 2</code> arguments then position it using offsets from the parent window. (There are other ways to create subwindows, but they use absolute coordinates and I find that confusing.)</p>
<p>The other new element here is the added call to <code>touchwin()</code>. I don't think it's actually needed in this case, because the full window was just refreshed on the line above (because it was the first refresh of <code>window</code>), but the documentation does recommend <em>touching</em> before any refresh of a subwindow.</p>
<p>I suppose that means we should discuss the concept of touching. <code>curses</code> aims to be efficient, so it tries to just refresh changed characters in a window. This means it needs to keep track of what has and has not changed. If you overwrite some content, obviously it changed and <code>curses</code> knows that, without your help. However, in scenarios where you have windows laying on top of other windows, a change in the front window could require some refreshing of the back window. For example, any whitespace placed in the front window may allow some content of the back window to bleed through. If those characters aren't touched, <code>curses</code> won't know that it needs to redraw them.</p>
<p>Now, the tool I used above, <code>touchwin()</code>, is crude. It just says, "This window may have changed." <code>curses</code> is forced to refresh all characters in the window for this call. You can be a little more specific and indicate which rows may have changed. This would save <code>curses</code> some work. To do that, we would change this line:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">touchwin</span><span class="p">(</span><span class="n">window</span><span class="p">)</span> <span class="c1"># do this before refreshing subwindows</span>
<span class="c1"># ...</span>
</pre></div>
<p>to:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">touchline</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="c1"># absolute y, line count</span>
<span class="c1"># ...</span>
</pre></div>
<p>I have no idea why this function is called <code>touch<b>line</b>()</code> and yet affects many <strong>lines</strong>. Also, I'm not aware of a way to specify the columns affected. It seems the best you can do is full rows.</p>
<p>Anyway, that code prints content in a window with padding and an intact border:</p>
<pre><code>+------+
| |
| 1234 |
| 56 |
| |
+------+
</code></pre>
<p>Notice that my content contained nasty newlines, but the window frame is unaffected. <strong>This is the power of subwindows, in my opinion. You can use them to separate different kinds of content (including borders) and keep them from interfering with each other.</strong></p>
<p>Could you do this magic trick with normal windows layered on top of each other? Yes. The only differences are that you would need to use absolute coordinates everywhere. (Subwindows are a mixed bag in comparison. I can create them with relative coordinates, but I still need to manage touching using absolute coordinates.) Also the documentation says that subwindows share some memory with their parent windows. I'm not sure how much that helps in cases like the code above where they are never truly drawing on top of each other, but it sounds nice.</p>
<h4>Panels</h4>
<p>Once you start managing overlapping windows and/or subwindows, keeping track of things like touching and refreshing can get pretty complex. <code>curses</code> itself doesn't offer much relief for that, but there is another library, called <code>panel</code>, that is very commonly installed with <code>curses</code>, and it does address this problem. Luckily for us, <code>ffi-ncurses</code> wraps both libraries.</p>
<p>First, let's look at some problematic code:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">cbreak</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">noecho</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">keypad</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="kp">true</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">curs_set</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="c1"># hide the cursor</span>
<span class="n">moveable</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">newwin</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">moveable</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="mi">3</span><span class="o">.</span><span class="n">times</span> <span class="k">do</span> <span class="o">|</span><span class="n">y</span><span class="o">|</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">mvwaddstr</span><span class="p">(</span><span class="n">moveable</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">"mmm"</span><span class="p">)</span>
<span class="k">end</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wmove</span><span class="p">(</span><span class="n">moveable</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">stationary</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">newwin</span><span class="p">(</span><span class="mi">15</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">stationary</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="mi">13</span><span class="o">.</span><span class="n">times</span> <span class="k">do</span> <span class="o">|</span><span class="n">y</span><span class="o">|</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">mvwaddstr</span><span class="p">(</span><span class="n">stationary</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">"sss"</span><span class="p">)</span>
<span class="k">end</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">moveable</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stationary</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">doupdate</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="n">y</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getbegy</span><span class="p">(</span><span class="n">moveable</span><span class="p">)</span>
<span class="n">x</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getbegx</span><span class="p">(</span><span class="n">moveable</span><span class="p">)</span>
<span class="n">lines</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getmaxy</span><span class="p">(</span><span class="n">moveable</span><span class="p">)</span>
<span class="k">case</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span>
<span class="k">when</span> <span class="s2">"q"</span><span class="o">.</span><span class="n">codepoints</span><span class="o">.</span><span class="n">first</span>
<span class="k">break</span>
<span class="k">when</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">KeyDefs</span><span class="o">::</span><span class="no">KEY_LEFT</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">mvwin</span><span class="p">(</span><span class="n">moveable</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">when</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">KeyDefs</span><span class="o">::</span><span class="no">KEY_RIGHT</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">mvwin</span><span class="p">(</span><span class="n">moveable</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">moveable</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stationary</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">doupdate</span>
<span class="k">end</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
</pre></div>
<p>This code has a moveable window (filled with <code>m</code>'s) and a stationary window (filled with <code>s</code>'s). The left and right arrow keys can be used to push the moveable window in those directions via the <code>mvwin()</code> function.</p>
<p>The code is lengthy but hopefully not too tricky. After the typical setup, with a first-seen-here mode used to make the cursor invisible, the two chunks of code just create and fill windows. The next chunk does the initial display. Then we go into a <code>loop</code> where we respond to the keys by moving the window in the indicated direction and redrawing.</p>
<p>This code doesn't really work yet. If I run it and nudge the moveable window right for a while, then back to the left, my screen looks like this:</p>
<pre><code> +---+
|sss|
|sss|
|sss|
|sss|
+++++---+++++s|
|||||mmm|||||s|
|||||mmm|||||s|
|||||mmm|||||s|
+++++---+++++s|
|sss|
|sss|
|sss|
|sss|
+---+
</code></pre>
<p>You can see that the old locations aren't being properly cleared. We should understand the reason for this now. We need to touch the affected windows so <code>curses</code> will refresh their content. The fix is to add to this refreshing code inside the <code>loop</code>:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">moveable</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stationary</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">doupdate</span>
<span class="c1"># ...</span>
</pre></div>
<p>Here are the additions:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">touchline</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">lines</span><span class="p">)</span> <span class="c1"># update the background</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">moveable</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">touchline</span><span class="p">(</span><span class="n">stationary</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">lines</span><span class="p">)</span> <span class="c1"># update the other window</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stationary</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">doupdate</span>
<span class="c1"># ...</span>
</pre></div>
<p>This gives us smooth movement:</p>
<pre><code> +---+
|sss|
|sss|
|sss|
|sss|
+--|sss|
|mm|sss|
|mm|sss|
|mm|sss|
+--|sss|
|sss|
|sss|
|sss|
|sss|
+---+
</code></pre>
<p>If we would prefer to see the moving window on top, we could reorder the refreshes:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">touchline</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">lines</span><span class="p">)</span> <span class="c1"># update the background</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">touchline</span><span class="p">(</span><span class="n">stationary</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">lines</span><span class="p">)</span> <span class="c1"># update the other window</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">stationary</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wnoutrefresh</span><span class="p">(</span><span class="n">moveable</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">doupdate</span>
<span class="c1"># ...</span>
</pre></div>
<p>Then I see this:</p>
<pre><code> +---+
|sss|
|sss|
|sss|
|sss|
+---+s|
|mmm|s|
|mmm|s|
|mmm|s|
+---+s|
|sss|
|sss|
|sss|
|sss|
+---+
</code></pre>
<p>That all works, but it's kind of a lot to keep track of. Plus we're just dealing with two windows so far. More windows could add a lot more complexity.</p>
<p>That's where <code>panel</code> comes in. It provides a stack of panels and once you place windows in that stack, <code>panel</code> will manage the refresh order and touching for you. <code>stdscr</code> is not explicitly part of the stack, but it is managed for you as well. Let's rewrite the code to use this extra abstraction:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">cbreak</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">noecho</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">keypad</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="kp">true</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">curs_set</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">moveable</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">newwin</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">moveable</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="mi">3</span><span class="o">.</span><span class="n">times</span> <span class="k">do</span> <span class="o">|</span><span class="n">y</span><span class="o">|</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">mvwaddstr</span><span class="p">(</span><span class="n">moveable</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">"mmm"</span><span class="p">)</span>
<span class="k">end</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wmove</span><span class="p">(</span><span class="n">moveable</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">stationary</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">newwin</span><span class="p">(</span><span class="mi">15</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wborder</span><span class="p">(</span><span class="n">stationary</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="mi">13</span><span class="o">.</span><span class="n">times</span> <span class="k">do</span> <span class="o">|</span><span class="n">y</span><span class="o">|</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">mvwaddstr</span><span class="p">(</span><span class="n">stationary</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">"sss"</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1"># construct the panels, back to front</span>
<span class="n">panels</span> <span class="o">=</span> <span class="o">[</span> <span class="o">]</span>
<span class="n">panels</span> <span class="o"><<</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">new_panel</span><span class="p">(</span><span class="n">moveable</span><span class="p">)</span>
<span class="n">panels</span> <span class="o"><<</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">new_panel</span><span class="p">(</span><span class="n">stationary</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">update_panels</span> <span class="c1"># touch and refresh all panels as needed</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">doupdate</span> <span class="c1"># then update normally</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="n">y</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getbegy</span><span class="p">(</span><span class="n">moveable</span><span class="p">)</span>
<span class="n">x</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getbegx</span><span class="p">(</span><span class="n">moveable</span><span class="p">)</span>
<span class="k">case</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span>
<span class="k">when</span> <span class="s2">"q"</span><span class="o">.</span><span class="n">codepoints</span><span class="o">.</span><span class="n">first</span>
<span class="k">break</span>
<span class="k">when</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">KeyDefs</span><span class="o">::</span><span class="no">KEY_LEFT</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">move_panel</span><span class="p">(</span><span class="n">panels</span><span class="o">.</span><span class="n">first</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="c1"># move panels not windows</span>
<span class="k">when</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">KeyDefs</span><span class="o">::</span><span class="no">KEY_RIGHT</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">move_panel</span><span class="p">(</span><span class="n">panels</span><span class="o">.</span><span class="n">first</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">update_panels</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">doupdate</span>
<span class="k">end</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
</pre></div>
<p>The comments again point out the major differences, but they are:</p>
<ol>
<li>We now construct the stack of panels</li>
<li>We replace all touching and refreshing code with a single function call</li>
<li>We need to move panels instead of windows so the panel can manage changes</li>
</ol><p>As you can see, it's less code and it still works the same. Because we don't have to handle touching with this approach, we can stop worrying about the oddity of needing absolute coordinates for our subwindows as well.</p>
<p>This version will show the moveable window behind the stationary one, because that's the order we built the stack in. To swap them, we could reorder the stack building code, or just use one of <code>panel</code>'s reordering functions like this:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="c1"># construct the panels, back to front</span>
<span class="n">panels</span> <span class="o">=</span> <span class="o">[</span> <span class="o">]</span>
<span class="n">panels</span> <span class="o"><<</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">new_panel</span><span class="p">(</span><span class="n">moveable</span><span class="p">)</span>
<span class="n">panels</span> <span class="o"><<</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">new_panel</span><span class="p">(</span><span class="n">stationary</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">top_panel</span><span class="p">(</span><span class="n">panels</span><span class="o">.</span><span class="n">first</span><span class="p">)</span> <span class="c1"># move panel to the top</span>
<span class="c1"># ...</span>
</pre></div>
<p>I hope you'll agree that panels are a lot easier to manage than the manual triggering of touches and refreshing. <strong>My last piece of advice is probably pretty obvious: prefer panels for managing window ordering.</strong></p>James Edward Gray IIBasic Cursestag:graysoftinc.com,2015-02-28:/posts/1372015-03-11T15:55:34ZA reworking of all my terminal tricks examples to use the curses library.<p>In <a href="http://graysoftinc.com/terminal-tricks/random-access-terminal">my last article</a>, I showed off some of the various magic commands a Unix terminal recognizes and how you can use those commands to move the cursor around, change the colors of your output, and read input in a character by character fashion. It's not very common to manipulate things manually as I did in those examples. There are libraries that wrap these mechanisms and add abstractions of their own. Using one of them can be easier and less error prone.</p>
<p>Probably the most famous of these higher abstraction libraries is <code>curses</code>. However, your version won't be called that. <code>curses</code> was the original library for System V UNIX. These days you are far more likely to have <code>ncurses</code> which a replacement library that emulates the original. The truth is, you probably don't have exactly that library either. Instead, you may have <code>ncursesw</code>, which is the same library with <em>wide character</em> (read: non-ASCII) support. Also, <code>curses</code> is often discussed with several add on libraries: <code>panel</code>, <code>menu</code>, and <code>form</code>. Documentation often covers these separate units together.</p>
<p>Speaking of documentation, coverage of using <code>curses</code> in Ruby is pretty non-existent. That's the main reason I'm writing these articles. Most of the Ruby wrappers are almost direct copies of the C APIs, so you can mostly use that documentation with minor changes. (I'm aware of at least <a href="https://github.com/grosser/dispel">one higher level wrapper</a>, but the documentation story isn't much better there.) My three best sources for learning <code>curses</code> have been <a href="http://tldp.org/HOWTO/NCURSES-Programming-HOWTO/">this very thorough <em>How To</em></a> from The Linux Documentation Project, <a href="http://invisible-island.net/ncurses/man/ncurses.3x.html">the <code>man</code> pages</a>, and <a href="https://github.com/seanohalpin/ffi-ncurses/tree/master/examples">the <code>examples</code> directory in the <code>ffi-ncurses</code> gem</a>. Hopefully I can save you the trouble of reading all of that with these articles.</p>
<p>The previously mentioned <a href="https://rubygems.org/gems/ffi-ncurses"><code>ffi-ncurses</code> gem</a> is one of your choices for wrappers in Ruby. It's the library I'll be covering, because:</p>
<ul>
<li>It worked for me without additional installs on Mac OS X (Yosemite)</li>
<li>It does some reasonable setup for you to make using Unicode characters pretty painless</li>
<li>It supports JRuby</li>
</ul><p>According to <a href="https://www.ruby-toolbox.com/">The Ruby Toolbox</a>, <a href="https://rubygems.org/gems/ncursesw">the more popular library is <code>ncursesw</code></a>. It doesn't quite meet the advantages I listed above, but it does wrap <code>menu</code> and <code>form</code> (which <code>ffi-ncurses</code> does not).</p>
<p>Again though, both of these libraries a very nearly direct copies of the C API, so using either is pretty similar.</p>
<p>Enough background. Let's play with some <code>curses</code> code. I'll redo the examples from my last article below, using <code>ffi-ncurses</code> and point out the differences as we go. It may be handy to have those <a href="http://graysoftinc.com/terminal-tricks/random-access-terminal">previous examples</a> open at the same time so you can compare the two versions as you read this.</p>
<h4>Output</h4>
<p>Last time, my first example was how to move the cursor around relative to it's current position. I don't think this kind of treatment is as common when using <code>curses</code> for reasons I'll get into in a bit, but it can still be done.</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span> <span class="c1"># gem install ffi-ncurses</span>
<span class="k">begin</span>
<span class="c1"># setup</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span> <span class="c1"># start curses</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">cbreak</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">noecho</span>
<span class="c1"># write some content</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="s2">"onez</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="s2">"twos</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="s2">"threes</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span> <span class="c1"># pause waiting on a keypress</span>
<span class="n">y</span><span class="p">,</span> <span class="n">x</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getyx</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span> <span class="c1"># find the cursor</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wmove</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="n">y</span> <span class="o">-</span> <span class="mi">3</span><span class="p">,</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">3</span><span class="p">)</span> <span class="c1"># make a relative move</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="s2">"s"</span><span class="p">)</span> <span class="c1"># fix the content</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wmove</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span> <span class="c1"># go back to where we were</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span> <span class="c1"># update what the user is seeing</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span> <span class="c1"># pause waiting on a keypress</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span> <span class="c1"># end curses</span>
<span class="k">end</span>
</pre></div>
<p>OK, so the first major difference you notice is that it's a lot more code and it's not too pretty. We're straight up using a C API here, so it's not going to be very Rubyish. I'll try to fix that in a later article, but I wanted to cover how this stuff works first. Also, I could have done an <code>include FFI::NCurses</code> and dropped a ton of references to the module here. I didn't do that for two reasons:</p>
<ul>
<li>So you could easily see what's part of the module's API</li>
<li>Doing so would dump a <strong>ton</strong> of methods into Ruby's top-level scope, which is probably not a great practice</li>
</ul><p>Another difference between the code in my last article and what you see above is the need to initialize <code>curses</code> and shut it down when we're done. You will pretty much always see this <code>begin … ensure … end</code> pattern with calls to <code>initscr()</code> and <code>endwin()</code> when working with <code>curses</code> in Ruby.</p>
<p>Ignore the calls to <code>cbreak()</code> and <code>noecho()</code> for now. I'll discuss those below.</p>
<p>Finally, the first chunk of <code>waddstr()</code> calls should start to look something like the example we're rewriting. <code>waddstr()</code> is one of about a gazillion ways that you can write to the screen using <code>curses</code>. It writes a <code>String</code>. I recommend pretty much always using this method for several reasons:</p>
<ul>
<li>It's easy to understand</li>
<li>It will do the right thing if your <code>String</code> includes Unicode characters</li>
<li>It allow you to ignore quite a bit of the <code>curses</code> API</li>
</ul><p>This last point is a pretty big deal. <code>curses</code> is a massive collection of functions. A great many of them provide alternate ways to do similar things. Variety is nice, but Ruby already gives you so many useful tools. For example, there's a <code>printf()</code>-like variation of the <code>String</code> writing function I showed you. You don't need that. Use <a href="http://ruby-doc.org/core-2.2.0/String.html#method-i-25"><code>String#%</code> in Ruby</a> and hand the result to <code>waddstr()</code>. It's true that some of the <code>curses</code> functions I'm skipping probably perform a tiny bit better due to skipping some steps, but on a modern computer you are unlikely to need more drawing speed even with a full screen terminal. Instead, I recommend getting a handle on the API basics and adding the other stuff only when you find yourself with a genuine need.</p>
<p>At the end of the output, notice that I needed a call to <code>wrefresh()</code>. In <code>curses</code> adding content is a separate operation from drawing that content to the screen. We have to explicitly tell a <em>window</em> when to redraw itself, which is what <code>wrefresh()</code> does.</p>
<p>I haven't talked about what a <code>curses</code> window is yet and, to be honest, I'm going to save most of that for a future article. But here's all you need to know for now:</p>
<ul>
<li>The <code>w</code> prefix on most of these functions stands for <em>window</em> and it means they expect a window as their first argument</li>
<li>A window is a place where you can draw output to the screen in <code>curses</code> code</li>
<li>When <code>curses</code> starts up, it creates a default window, that is the full size of the terminal, called <code>stdscr</code> (for <em>standard screen</em> I assume)</li>
<li>
<code>initscr()</code> returns <code>stdscr</code>
</li>
<li>
<code>curses</code> has a duplicate API without the <code>w</code> prefixes that mostly just assumes you want to work with <code>stdscr</code> (<code>addstr()</code>, <code>refresh()</code>, etc.)</li>
<li>You can't do everything with that less verbose API, so I'm voting you ignore it for now</li>
</ul><p>To sum up: <code>curses</code> will give you the <code>stdscr</code> window. Just pass that everywhere you need a window reference until you're ready to start making windows of your own.</p>
<p>OK, another change in this example is that I added two pauses using <code>getch()</code>. This just waits for you to push a key and it's the reason that I set the <code>cbreak()</code> and <code>noecho()</code> modes early on. (More on modes below.) It's worth noting that I had to do this. <code>curses</code> usually clears the screen as you enter and exit <code>curses</code> mode so, without the pauses, you wouldn't have seen any output.</p>
<p>We're finally ready to discuss the meat of this example, the moving and changing code. To refresh your memory, here are the lines I'm talking about:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="n">y</span><span class="p">,</span> <span class="n">x</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getyx</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span> <span class="c1"># find the cursor</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wmove</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="n">y</span> <span class="o">-</span> <span class="mi">3</span><span class="p">,</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">3</span><span class="p">)</span> <span class="c1"># make a relative move</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="s2">"s"</span><span class="p">)</span> <span class="c1"># fix the content</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wmove</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span> <span class="c1"># go back to where we were</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span> <span class="c1"># update what the user is seeing</span>
<span class="c1"># ...</span>
</pre></div>
<p><code>getyx()</code> is the reason I had to use the window referencing functions in this example. Though it doesn't have a <code>w</code> prefix, it does require a window to find the cursor in. It's also one of the few places the <code>ffi-ncurses</code> deviates from the C API, switching to Ruby's multiple-assignment friendly return of an <code>Array</code> of two <code>Integer</code>s instead of forcing you to pass two pointer arguments.</p>
<p>It's worth noting that <code>*yx()</code> functions in the <code>curses</code> API are always in that order, Y then X. This can trip you up if your accustom to X then Y ordering.</p>
<p>The rest of this code should be pretty easy to grok as it just moves around using the known cursor coordinates. This is very similar to the original example, except that we didn't need to find the cursor last time.</p>
<p>I know that was a slog because I had to cover through so many <code>curses</code>-isms, but that should allow us to coast through these next few examples now that we have the basics. Here's the similar example from last time using absolute movement:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">cbreak</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">noecho</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="s2">"onez</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="s2">"twos</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="s2">"threes</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span>
<span class="n">y</span><span class="p">,</span> <span class="n">x</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getyx</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span> <span class="c1"># save cursor position</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">mvwaddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="s2">"s"</span><span class="p">)</span> <span class="c1"># a combined move and add</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wmove</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span> <span class="c1"># restore the cursor position</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span> <span class="c1"># update what the user sees</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
</pre></div>
<p>Only the commented lines differ in this example and they correspond pretty closely to the matching example from the previous article. There's really just one surprise here. Remember when I said that terminals index rows and columns starting at <code>1</code>? Well <code>curses</code> switches back to the more common <code>0</code> for maximum confusion. That's why <code>1;4</code> has become <code>0, 3</code> in this version.</p>
<p>I do believe this is a more common usage pattern with <code>curses</code> code. The reason is that we know the screen has been cleared and we know that we're drawing into some window at a known location. With those givens, absolute coordinates make a lot of sense.</p>
<p>The colors example has more changes:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="k">def</span> <span class="nf">color_pairs</span>
<span class="vi">@color_pairs</span> <span class="o">||=</span> <span class="p">{</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">color_pair</span><span class="p">(</span><span class="n">foreground</span><span class="p">,</span> <span class="n">background</span><span class="p">)</span>
<span class="n">foreground</span> <span class="o">=</span> <span class="n">foreground</span><span class="o">.</span><span class="n">to_s</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr">/\Abright_/</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span><span class="o">.</span><span class="n">to_sym</span>
<span class="n">color</span> <span class="o">=</span> <span class="o">[</span><span class="n">foreground</span><span class="p">,</span> <span class="n">background</span><span class="o">]</span>
<span class="c1"># add the color pair if this is the first time we've seen it</span>
<span class="k">unless</span> <span class="n">color_pairs</span><span class="o">.</span><span class="n">include?</span><span class="p">(</span><span class="n">color</span><span class="p">)</span>
<span class="n">foreground_color</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">Color</span><span class="o">.</span><span class="n">const_get</span><span class="p">(</span><span class="n">foreground</span><span class="o">.</span><span class="n">upcase</span><span class="p">)</span>
<span class="n">background_color</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">Color</span><span class="o">.</span><span class="n">const_get</span><span class="p">(</span><span class="n">background</span><span class="o">.</span><span class="n">upcase</span><span class="p">)</span>
<span class="n">number</span> <span class="o">=</span> <span class="n">color_pairs</span><span class="o">.</span><span class="n">size</span> <span class="o">+</span> <span class="mi">1</span>
<span class="n">color_pairs</span><span class="o">[</span><span class="n">color</span><span class="o">]</span> <span class="o">=</span> <span class="n">number</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">init_pair</span><span class="p">(</span><span class="n">number</span><span class="p">,</span> <span class="n">foreground_color</span><span class="p">,</span> <span class="n">background_color</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1"># lookup the color pair</span>
<span class="n">color_pairs</span><span class="o">[</span><span class="n">color</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">color</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="n">foreground</span><span class="p">,</span> <span class="n">background</span><span class="p">)</span>
<span class="n">weight</span> <span class="o">=</span> <span class="n">foreground</span><span class="o">.</span><span class="n">to_s</span><span class="o">.</span><span class="n">start_with?</span><span class="p">(</span><span class="s2">"bright"</span><span class="p">)</span> <span class="p">?</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">A_BOLD</span>
<span class="p">:</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">A_NORMAL</span>
<span class="n">pair</span> <span class="o">=</span> <span class="n">color_pair</span><span class="p">(</span><span class="n">foreground</span><span class="p">,</span> <span class="n">background</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wattr_set</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="n">weight</span><span class="p">,</span> <span class="n">pair</span><span class="p">,</span> <span class="kp">nil</span><span class="p">)</span> <span class="c1"># change colors</span>
<span class="k">yield</span>
<span class="k">ensure</span>
<span class="c1"># change back to default colors</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wattr_set</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">A_NORMAL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="kp">nil</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">cbreak</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">noecho</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">start_color</span> <span class="c1"># turn colors on</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">assume_default_colors</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># make color pair 0 the defaults</span>
<span class="o">[</span> <span class="o">[</span><span class="s2">"C"</span><span class="p">,</span> <span class="ss">:red</span><span class="o">]</span><span class="p">,</span>
<span class="o">[</span><span class="s2">"O"</span><span class="p">,</span> <span class="ss">:bright_red</span><span class="o">]</span><span class="p">,</span>
<span class="o">[</span><span class="s2">"L"</span><span class="p">,</span> <span class="ss">:bright_yellow</span><span class="o">]</span><span class="p">,</span>
<span class="o">[</span><span class="s2">"O"</span><span class="p">,</span> <span class="ss">:bright_green</span><span class="o">]</span><span class="p">,</span>
<span class="o">[</span><span class="s2">"R"</span><span class="p">,</span> <span class="ss">:bright_blue</span><span class="o">]</span><span class="p">,</span>
<span class="o">[</span><span class="s2">"S"</span><span class="p">,</span> <span class="ss">:blue</span><span class="o">]</span><span class="p">,</span>
<span class="o">[</span><span class="s2">"!"</span><span class="p">,</span> <span class="ss">:bright_magenta</span><span class="o">]</span> <span class="o">].</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">char</span><span class="p">,</span> <span class="n">color</span><span class="o">|</span>
<span class="n">color</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="n">color</span><span class="p">,</span> <span class="ss">:white</span><span class="p">)</span> <span class="k">do</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="n">char</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
</pre></div>
<p>When you want to use colors in <code>curses</code> you need to set them up. First, that means that you should call <code>start_color()</code> as part of your initialization sequence. I would also recommend calling <code>FFI::NCurses.assume_default_colors(-1, -1)</code>. Let me explain why I say that.</p>
<p>To use colors in <code>curses</code>, you create <em>color pairs</em>: numbered groupings of foreground and background colors. Pair <code>0</code> already exists. It's set to black on black, I believe. Not so helpful. (The documentation says you can't change it though I'm pretty sure I've been allowed to do so in the past.) Anyway, if you call <code>FFI::NCurses.assume_default_colors(-1, -1)</code>, it switches pair <code>0</code> to being whatever the default foreground and background are for the terminal. That's a much more useful default, if you ask me.</p>
<p>Now that you know about color pairs, you can probably puzzle out my <code>color_pair()</code> method. It just pulls color pair numbers out of the <code>color_pairs()</code> <code>Hash</code>. If it's the first time a pair has been seen, it inserts it into the <code>Hash</code> in the next numbered slot before reading it back. It does this by looking up some predefined constants in <code>FFI::NCurses::Color</code> for named terminal colors.</p>
<p><code>color()</code> works similar to how it did in the example from my last article: switch colors, write some content (by invoking the block in this case), and then switch back (using that useful pair <code>0</code> default that we setup earlier). Note that <em>bright</em> colors are handled here by setting the <code>FFI::NCurses::A_BOLD</code> attribute with the color pair.</p>
<p>The rest of the code is much like it was before.</p>
<p>The <em>Status Line Trick</em> from the previous article no longer applies when we've gone all in on loading <code>curses</code>. However, I did have a tiny example last time that showed how to get the screen size when I introduced <code>io/console</code> in the <em>Input</em> section. For completeness, here's the similar code for <code>curses</code>:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="n">rows</span><span class="p">,</span> <span class="n">cols</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">,</span> <span class="kp">nil</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="n">rows</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getmaxy</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="n">cols</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getmaxx</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="s2">"Columns: </span><span class="si">#{</span><span class="n">cols</span><span class="si">}</span><span class="s2">, Rows: </span><span class="si">#{</span><span class="n">rows</span><span class="si">}</span><span class="s2">"</span>
</pre></div>
<p>I prefer to do it in two steps like this because you don't have to worry about pointers. <code>getmaxy()</code> and <code>getmaxx()</code> are a set of functions classified as a <em>legacy</em> interface, but they've been available forever and I don't think there's any danger of them being removed.</p>
<p>If you prefer though, you can do it in one step by passing <code>Array</code>s as the pointers to be filled in:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="n">rows</span><span class="p">,</span> <span class="n">cols</span> <span class="o">=</span> <span class="o">[</span> <span class="o">]</span><span class="p">,</span> <span class="o">[</span> <span class="o">]</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getmaxyx</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="n">rows</span><span class="p">,</span> <span class="n">cols</span><span class="p">)</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="s2">"Columns: </span><span class="si">#{</span><span class="n">cols</span><span class="o">.</span><span class="n">first</span><span class="si">}</span><span class="s2">, Rows: </span><span class="si">#{</span><span class="n">rows</span><span class="o">.</span><span class="n">first</span><span class="si">}</span><span class="s2">"</span>
</pre></div>
<p>Note the added calls to <code>first()</code> in the last line to get the <code>Integer</code>s back out of the <code>Array</code>s.</p>
<h4>Input</h4>
<p>If you'll recall, the trick of getting terminal input right is all about using the right modes. Well, you just got to the good stuff! <code>curses</code> has a rich set of modes that you can mix and match to suite exactly your current needs. In my opinion, this is one of the biggest reasons to use <code>curses</code>.</p>
<p>Here's the simplest way to read individual characters as they are typed using <code>curses</code>:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">cbreak</span> <span class="c1"># like raw, but don't eat things like control-c</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">noecho</span> <span class="c1"># don't echo what the user types</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="n">key</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span><span class="o">.</span><span class="n">chr</span> <span class="c1"># read and convert to a String</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="s2">"You typed: </span><span class="si">#{</span><span class="n">key</span><span class="o">.</span><span class="n">inspect</span><span class="si">}</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="k">break</span> <span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="sc">?q</span>
<span class="k">end</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
</pre></div>
<p>Most of the work here is handled by two of the most boring lines. The call to <code>cbreak()</code> puts us in a raw-like mode where we can read single keystrokes as they are made, but where we can still interrupt the program (with control-C) or suspend it (with control-Z).</p>
<p><code>noecho()</code> turns off the automatic copying of what's typed to the screen. Notice how this is a separate concern from <code>cbreak()</code> mode in <code>curses</code> and I get to handle them independently.</p>
<p>Once your modes are set, getting input is as simple as calling <code>getch()</code>. Remember that it will return <code>Integer</code>s though, so you'll usually want a call to <code>chr()</code> to get the <code>String</code> for what was typed.</p>
<p>The next example we looked at last time was how to read keys only if they are available. Again, <code>curses</code> modes to the rescue:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">halfdelay</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># set a read delay of one tenth of a second</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">noecho</span>
<span class="n">last_read</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span>
<span class="n">prompted</span> <span class="o">=</span> <span class="kp">false</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="n">char</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span> <span class="c1"># this will wait for the read delay</span>
<span class="k">if</span> <span class="n">char</span> <span class="o">!=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">ERR</span> <span class="c1"># ERR is returned when we hit the delay</span>
<span class="n">last_read</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span>
<span class="n">key</span> <span class="o">=</span> <span class="n">char</span><span class="o">.</span><span class="n">chr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="s2">"You typed: </span><span class="si">#{</span><span class="n">key</span><span class="o">.</span><span class="n">inspect</span><span class="si">}</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="k">break</span> <span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="sc">?q</span>
<span class="k">elsif</span> <span class="o">!</span><span class="n">prompted</span> <span class="o">&&</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span> <span class="o">-</span> <span class="n">last_read</span> <span class="o">></span> <span class="mi">3</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="s2">"Please type a character.</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="n">prompted</span> <span class="o">=</span> <span class="kp">true</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
</pre></div>
<p>This new <code>halfdelay()</code> mode is like <code>cbreak()</code>, but with an added delay that you specify in tenths of a second. In this mode, calls to <code>getch()</code> will wait for input until the delay is exhausted, then return <code>FFI::NCurses::ERR</code>. This solves exactly the problem we had last time, doesn't require the use of additional libraries or cryptic low-level methods, and it even allows us to drop the call to <code>sleep()</code>. You just have to check to see if you got <code>FFI::NCurses::ERR</code> instead of an actual key press.</p>
<p>If you ever need a full non-breaking, zero wait read with <code>curses</code> you can have that too. <a href="http://invisible-island.net/ncurses/man/curs_inopts.3x.html">The <code>timeout()</code>/<code>wtimeout()</code> functions</a> are there when you need them.</p>
<p>It's also worth pointing out that we never ran into the newline translation problem with the <code>curses</code>. That's yet another mode. I never turned it off and we never has a problem.</p>
<p>Which brings us to the final example. Last time we noted that our code didn't properly handle special keys, like the arrow keys. Both examples above have the same issue. This probably won't surprise you by now, but <code>curses</code> has a mode for that too:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"ffi-ncurses"</span>
<span class="k">begin</span>
<span class="n">stdscr</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">initscr</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">halfdelay</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">noecho</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">keypad</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="kp">true</span><span class="p">)</span> <span class="c1"># handle special keys</span>
<span class="n">last_read</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span>
<span class="n">prompted</span> <span class="o">=</span> <span class="kp">false</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="n">char</span> <span class="o">=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">getch</span>
<span class="k">if</span> <span class="n">char</span> <span class="o">!=</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">ERR</span>
<span class="n">last_read</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span>
<span class="n">key</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="c1"># find key in special constants</span>
<span class="k">if</span> <span class="p">(</span><span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">KEY_CODE_YES</span><span class="o">.</span><span class="n">.</span><span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">KEY_MAX</span><span class="p">)</span><span class="o">.</span><span class="n">include?</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">KeyDefs</span><span class="o">.</span><span class="n">constants</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">key_name</span><span class="o">|</span>
<span class="k">if</span> <span class="n">key_name</span><span class="o">.</span><span class="n">to_s</span><span class="o">.</span><span class="n">start_with?</span><span class="p">(</span><span class="s2">"KEY_"</span><span class="p">)</span> <span class="o">&&</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">KeyDefs</span><span class="o">.</span><span class="n">const_get</span><span class="p">(</span><span class="n">key_name</span><span class="p">)</span> <span class="o">==</span> <span class="n">char</span>
<span class="n">key</span> <span class="o">=</span> <span class="n">key_name</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">else</span>
<span class="n">key</span> <span class="o">=</span> <span class="n">char</span><span class="o">.</span><span class="n">chr</span>
<span class="k">end</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="s2">"You typed: </span><span class="si">#{</span><span class="n">key</span><span class="o">.</span><span class="n">inspect</span><span class="si">}</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="k">break</span> <span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="sc">?q</span>
<span class="k">elsif</span> <span class="o">!</span><span class="n">prompted</span> <span class="o">&&</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span> <span class="o">-</span> <span class="n">last_read</span> <span class="o">></span> <span class="mi">3</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">waddstr</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="s2">"Please type a character.</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">wrefresh</span><span class="p">(</span><span class="n">stdscr</span><span class="p">)</span>
<span class="n">prompted</span> <span class="o">=</span> <span class="kp">true</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">ensure</span>
<span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">.</span><span class="n">endwin</span>
<span class="k">end</span>
</pre></div>
<p>The new call here is <code>FFI::NCurses.keypad(stdscr, true)</code> which turns on the handling of special characters. I can't think of very many cases where that isn't desirable, so I always turn it on.</p>
<p>I used a fancy scan of constants in the <code>if</code> statement to show the name of the pressed key in the output of this program, but the typical usage is much simpler. You often write code like this:</p>
<div class="highlight highlight-ruby"><pre><span class="k">case</span> <span class="n">char</span>
<span class="k">when</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">KeyDefs</span><span class="o">::</span><span class="no">KEY_UP</span>
<span class="c1"># ...</span>
<span class="k">when</span> <span class="no">FFI</span><span class="o">::</span><span class="no">NCurses</span><span class="o">::</span><span class="no">KeyDefs</span><span class="o">::</span><span class="no">KEY_RIGHT</span>
<span class="c1"># ...</span>
<span class="k">end</span>
</pre></div>
<p>Anyway, when I run the code above and push some arrow keys, I see output like:</p>
<pre><code>You typed: :KEY_UP
You typed: :KEY_RIGHT
</code></pre>
<p>I still have trouble with some keystrokes not being detected (like control-alt-o) and other being swallowed by my terminal for other purposes (some function keys). I believe some of this could be solved by tweaking my terminal settings, but I'm generally only ever after the arrow keys anyway and they work fine.</p>
<p>That concludes our adaptation of basic terminal manipulation code to similar operations using <code>curses</code>. This shows you some basics, but there's more to <code>curses</code> than what I've covered here. Next time I'll look at some of the abstractions <code>curses</code> adds beyond these basic tools.</p>James Edward Gray IIRandom Access Terminaltag:graysoftinc.com,2015-01-30:/posts/1362017-07-18T21:40:47ZA look at the special codes your terminal understands, its modes of operation, and how we can use those details to do fancy I/O.<p>I've recently been playing around with fancy terminal output in Ruby. I've learned quite a bit about this arcane magic. I've also realized that the documentation is pretty spotty. I want to see if I can improve that with a few blog posts, so let's dive right in.</p>
<h4>Output</h4>
<p>Program output typically happens in a linear order from top to bottom. For example, this code:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">puts</span> <span class="s2">"onez"</span>
<span class="nb">puts</span> <span class="s2">"twos"</span>
<span class="nb">puts</span> <span class="s2">"threes"</span>
</pre></div>
<p>generates this output:</p>
<pre><code>onez
twos
threes
</code></pre>
<p>But what if you need to change some output? Could you replace the <code>z</code> above with an <code>s</code> if you needed to? Yes, but it can get a little involved.</p>
<h5>ANSI Escape Codes</h5>
<p>In many cases, we just push some characters to <code>$stdout</code> (the stream <code>Kernel#puts</code> is writing to above) and your terminal program happily shows them to the user. However, your terminal is watching these characters for <a href="http://en.wikipedia.org/wiki/ANSI_escape_code">special sequences</a> that it understands. Some of those sequences of characters can cause your terminal to take actions other than just writing some output to the screen.</p>
<p>For example, there are sequences that move the cursor (the point where output is written) to different locations. Using those special codes, we could change the <code>z</code> in our example:</p>
<div class="highlight highlight-ruby"><pre><span class="no">CSI</span> <span class="o">=</span> <span class="s2">"</span><span class="se">\e</span><span class="s2">["</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">puts</span> <span class="s2">"onez"</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">puts</span> <span class="s2">"twos"</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">puts</span> <span class="s2">"threes"</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">write</span> <span class="s2">"</span><span class="si">#{</span><span class="no">CSI</span><span class="si">}</span><span class="s2">3A"</span> <span class="c1"># move up three lines</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">write</span> <span class="s2">"</span><span class="si">#{</span><span class="no">CSI</span><span class="si">}</span><span class="s2">3C"</span> <span class="c1"># move right three characters</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">write</span> <span class="s2">"s"</span> <span class="c1"># overwrite the 'z'</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">write</span> <span class="s2">"</span><span class="si">#{</span><span class="no">CSI</span><span class="si">}</span><span class="s2">3B"</span> <span class="c1"># move down three lines (after our output)</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">write</span> <span class="s2">"</span><span class="si">#{</span><span class="no">CSI</span><span class="si">}</span><span class="s2">4D"</span> <span class="c1"># move back four characters (to the start of the line)</span>
</pre></div>
<p>If you run that code, it will initially produce the output from the previous example. It will then move the cursor to the <code>z</code>, overwrite it with an <code>s</code>, and return the cursor to where it was. This likely happens too fast for you to see the change, but you can insert <code>sleep()</code> calls if you want to watch it work.</p>
<p>All of the escapes used in this example are commands used to move the cursor around by a specific number of lines or characters in an indicated direction. Note that I switched to using the <code>write()</code> method when I started using escapes. <code>puts()</code> would have printed my escape followed by a newline (moving me away from where I wanted to be).</p>
<p>I used relative positioning commands above, meaning that I moved from where I knew the cursor was to where I wanted it be. I couldn't move to some absolute coordinates, because I don't know what else is on your screen when the program is run. You probably have at least a command prompt above the output and I have no idea how big that is. Now, if I clear the screen before I generate output, I could go on to treat it like a known grid of coordinates:</p>
<div class="highlight highlight-ruby"><pre><span class="no">CSI</span> <span class="o">=</span> <span class="s2">"</span><span class="se">\e</span><span class="s2">["</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">write</span> <span class="s2">"</span><span class="si">#{</span><span class="no">CSI</span><span class="si">}</span><span class="s2">2J"</span> <span class="c1"># clear screen</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">write</span> <span class="s2">"</span><span class="si">#{</span><span class="no">CSI</span><span class="si">}</span><span class="s2">1;1H"</span> <span class="c1"># move to top left corner</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">puts</span> <span class="s2">"onez"</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">puts</span> <span class="s2">"twos"</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">puts</span> <span class="s2">"threes"</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">write</span> <span class="s2">"</span><span class="si">#{</span><span class="no">CSI</span><span class="si">}</span><span class="s2">s"</span> <span class="c1"># save cursor position</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">write</span> <span class="s2">"</span><span class="si">#{</span><span class="no">CSI</span><span class="si">}</span><span class="s2">1;4H"</span> <span class="c1"># move to line 1, character 4</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">write</span> <span class="s2">"s"</span> <span class="c1"># overwrite the 'z'</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">write</span> <span class="s2">"</span><span class="si">#{</span><span class="no">CSI</span><span class="si">}</span><span class="s2">u"</span> <span class="c1"># restore cursor position</span>
</pre></div>
<p>This is the same trick, but using absolute positioning and some other fancy codes to <em>save</em> and <em>restore</em> the cursor's position. The movement escape used here, <code>CSIy;xH</code>, allows us to jump directly to any position on the screen. The top left corner is <code>1;1</code> with <code>y</code> counting lines down and <code>x</code> counting characters to the right.</p>
<p>Clearing the screen can be a little tricky. First, you can clear part or all of it. I chose all here with the <code>2</code> in <code>CSI2J</code>. You also need to remember though that clearing the screen doesn't usually change the position of the cursor. That's why I sent it back to the top left corner after the clear.</p>
<p>This code was pretty eye opening to me. It helped me to realize that a terminal doesn't have to be treated as a sequence of lines. It can alternately be treated like a drawing canvas with coordinates used to represent each spot that a character can be placed.</p>
<p><strong>WARNING</strong>: terminals vary in exactly which codes they support. Most of the examples I'm showing will probably work on most UNIX-like terminals. I've tested them on my own UNIX box, but even there I found minor differences. For example, the code above works as expected in my normal terminal, but the terminal emulation mode I often use in Emacs doesn't seem to honor the save and restore cursor codes. It can be quite a bit of work to properly support the various kinds of terminals you can encounter.</p>
<h6>Colors</h6>
<p>It probably goes without saying that there are codes to change the color of your output and do other fancy graphic tricks. These can get slightly complex because they require a little math to indicate choice of foreground or background colors. They can also involve multiple codes at once when you want to select the <em>bright</em> version of a color. Here's a pretty trivial example:</p>
<div class="highlight highlight-ruby"><pre><span class="no">CSI</span> <span class="o">=</span> <span class="s2">"</span><span class="se">\e</span><span class="s2">["</span>
<span class="no">COLORS</span> <span class="o">=</span> <span class="p">{</span>
<span class="ss">black</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="ss">red</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="ss">green</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="ss">yellow</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="ss">blue</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="ss">magenta</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
<span class="ss">cyan</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
<span class="ss">white</span><span class="p">:</span> <span class="mi">7</span>
<span class="p">}</span>
<span class="k">def</span> <span class="nf">color</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="n">foreground</span><span class="p">,</span> <span class="n">background</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">)</span>
<span class="n">colored</span> <span class="o">=</span> <span class="n">color_name_to_escape_code</span><span class="p">(</span><span class="n">foreground</span><span class="p">,</span> <span class="mi">30</span><span class="p">)</span>
<span class="n">colored</span> <span class="o"><<</span> <span class="n">color_name_to_escape_code</span><span class="p">(</span><span class="n">background</span><span class="p">,</span> <span class="mi">40</span><span class="p">)</span> <span class="k">if</span> <span class="n">background</span>
<span class="n">colored</span> <span class="o"><<</span> <span class="n">content</span>
<span class="n">colored</span> <span class="o"><<</span> <span class="s2">"</span><span class="si">#{</span><span class="no">CSI</span><span class="si">}</span><span class="s2">0m"</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">color_name_to_escape_code</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">layer</span><span class="p">)</span>
<span class="n">short_name</span> <span class="o">=</span> <span class="nb">name</span><span class="o">.</span><span class="n">to_s</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr">/\Abright_/</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span>
<span class="n">color</span> <span class="o">=</span> <span class="no">COLORS</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="n">short_name</span><span class="o">.</span><span class="n">to_sym</span><span class="p">)</span>
<span class="n">escape</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="no">CSI</span><span class="si">}#{</span><span class="n">layer</span> <span class="o">+</span> <span class="n">color</span><span class="si">}</span><span class="s2">"</span>
<span class="n">escape</span> <span class="o"><<</span> <span class="s2">";1"</span> <span class="k">if</span> <span class="n">short_name</span><span class="o">.</span><span class="n">size</span> <span class="o"><</span> <span class="nb">name</span><span class="o">.</span><span class="n">size</span>
<span class="n">escape</span> <span class="o"><<</span> <span class="s2">"m"</span>
<span class="n">escape</span>
<span class="k">end</span>
<span class="o">[</span> <span class="o">[</span><span class="s2">"C"</span><span class="p">,</span> <span class="ss">:red</span><span class="o">]</span><span class="p">,</span>
<span class="o">[</span><span class="s2">"O"</span><span class="p">,</span> <span class="ss">:bright_red</span><span class="o">]</span><span class="p">,</span>
<span class="o">[</span><span class="s2">"L"</span><span class="p">,</span> <span class="ss">:bright_yellow</span><span class="o">]</span><span class="p">,</span>
<span class="o">[</span><span class="s2">"O"</span><span class="p">,</span> <span class="ss">:bright_green</span><span class="o">]</span><span class="p">,</span>
<span class="o">[</span><span class="s2">"R"</span><span class="p">,</span> <span class="ss">:bright_blue</span><span class="o">]</span><span class="p">,</span>
<span class="o">[</span><span class="s2">"S"</span><span class="p">,</span> <span class="ss">:blue</span><span class="o">]</span><span class="p">,</span>
<span class="o">[</span><span class="s2">"!"</span><span class="p">,</span> <span class="ss">:bright_magenta</span><span class="o">]</span> <span class="o">].</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">char</span><span class="p">,</span> <span class="n">color</span><span class="o">|</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">write</span> <span class="n">color</span><span class="p">(</span><span class="n">char</span><span class="p">,</span> <span class="n">color</span><span class="p">,</span> <span class="ss">:white</span><span class="p">)</span>
<span class="k">end</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">puts</span>
</pre></div>
<p>If you run that, you should see not-quite-a-rainbow of colored letters on white, regardless of the default color settings in your terminal.</p>
<p>It's pretty important to remember to issue a reset code (<code>CSI0m</code>) after playing with graphic settings like this. If your program exits without doing so, the terminal could be left in some pretty garish states. You can see that my <code>color()</code> method sets some colors, writes the desired content, then immediately resets. This process minimizes the chances of me bleeding over graphics changes into other output.</p>
<p>Visit that link I gave earlier on <a href="http://en.wikipedia.org/wiki/ANSI_escape_code">ANSI escape codes</a> for specifics about any of the sequences I've used here, a good description of what the <code>CSI</code> is, and more.</p>
<h5>The Status Line Trick</h5>
<p>While ANSI codes allow you to do pretty much any output trick you can dream up with enough effort, there's another trick that just so easy it's worth knowing for the times when it can save you some work.</p>
<p>We usually end lines of output with a newline character (<code>\n</code>). <code>Kernel#puts</code> does this for us automatically. The exact effect of that character depends on the mode your terminal is currently in. We'll cover that more below, but there's another way to end lines. You could use the carriage return (<code>\r</code>) character. (In truth, using both characters is also an option. Again, I'll go into that below.)</p>
<p>A <code>\r</code> does move the cursor back to the beginning of the line, but it does not advance to the next line. This gives you a simple way to overwrite the content you previously placed on the last line only. That can come in handy for updating a status line or progress bar.</p>
<p>Here's a contrived example that just shows progress while it pretends to do some work:</p>
<div class="highlight highlight-ruby"><pre><span class="n">chunks</span> <span class="o">=</span> <span class="mi">0</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">puts</span> <span class="s2">"Time left:"</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">write</span> <span class="s2">"|</span><span class="si">#{</span><span class="s1">'#'</span> <span class="o">*</span> <span class="n">chunks</span><span class="si">}#{</span><span class="s1">' '</span> <span class="o">*</span> <span class="p">(</span><span class="mi">10</span> <span class="o">-</span> <span class="n">chunks</span><span class="p">)</span><span class="si">}</span><span class="s2">|</span><span class="se">\r</span><span class="s2">"</span>
<span class="k">if</span> <span class="n">chunks</span> <span class="o">==</span> <span class="mi">10</span>
<span class="vg">$stdout</span><span class="o">.</span><span class="n">puts</span>
<span class="k">break</span>
<span class="k">end</span>
<span class="n">next_chunk</span> <span class="o">=</span> <span class="nb">rand</span><span class="p">(</span><span class="mi">1</span><span class="o">.</span><span class="n">.</span><span class="p">(</span><span class="mi">10</span> <span class="o">-</span> <span class="n">chunks</span><span class="p">))</span>
<span class="nb">sleep</span> <span class="n">next_chunk</span>
<span class="n">chunks</span> <span class="o">+=</span> <span class="n">next_chunk</span>
<span class="k">end</span>
</pre></div>
<p>Sample output from partway through a run looks like this:</p>
<pre><code>Time left:
|##### |
</code></pre>
<p>You may need to run it to appreciate the result, but the last line just keeps updating with the bar of <code>#</code>'s, growing each time. Notice that there is a final <code>puts()</code> when we exit the <code>loop()</code>, so the new command prompt won't erase the progress bar. You may or may not wish to do that in your own programs.</p>
<h4>Input</h4>
<p>I'll be honest and admit that I covered output first because it's easier. Buckle up and we'll see if can survive the jump into input.</p>
<p>I've mentioned before that <a href="http://graysoftinc.com/my-projects/i-just-want-one-character">programmers often ask how to read a single character from the keyboard</a> and it can get tricky. This time I'm going to try to explain what's really going on without hiding behind a library that simplifies the process. I'll also show you some fancy new ways to accomplish the task.</p>
<h5>Terminal Modes</h5>
<p>Your terminal can operate in <a href="http://en.wikipedia.org/wiki/POSIX_terminal_interface#History">a variety of modes</a>. The default <em>cooked mode</em> has several effects. One of those effects is that input is buffered until you press the return key. This buffering allows you to edit content you are entering—using arrow keys, backspace/delete keys, etc.—without all programs needing to handle all of those special cases. That's a great feature, right up to the point where you just want to read one character.</p>
<p>Watch what happens when I try:</p>
<div class="highlight highlight-ruby"><pre><span class="kp">loop</span> <span class="k">do</span>
<span class="n">char</span> <span class="o">=</span> <span class="vg">$stdin</span><span class="o">.</span><span class="n">getc</span>
<span class="nb">puts</span> <span class="s2">"You typed: </span><span class="si">#{</span><span class="n">char</span><span class="o">.</span><span class="n">inspect</span><span class="si">}</span><span class="s2">"</span>
<span class="k">break</span> <span class="k">if</span> <span class="n">char</span> <span class="o">==</span> <span class="sc">?q</span>
<span class="k">end</span>
</pre></div>
<p>Here's some output from a run of that program:</p>
<pre><code>a
You typed: "a"
You typed: "\n"
q
You typed: "q"
</code></pre>
<p>What you need to catch here is that I pushed an <code>a</code>, but my program wouldn't acknowledge it, until I pressed return too. Then it read both characters. The <code>q</code> was treated the same way, the program just ended before showing the second newline.</p>
<p>If we really want to do this right, we've got to get out of cooked mode (or at least disable its line buffering feature). In older Ruby code, you had three choices for doing this: shell out to <code>stty</code> (tricky to get right), make very arcane C-style calls to <code>ioctl()</code> (a nightmare), or use a gem that hid the details from you (your best bet, by far). Now you understand <a href="http://graysoftinc.com/my-projects/i-just-want-one-character">my old advice</a> to just use <code>HighLine</code>. But I have good news! Ruby is older and wiser now.</p>
<h6>A New Standard Library</h6>
<p>The easiest approach to reading just one character is to switch into <em>raw mode</em> (or a similar mode) and try again. However, this change will affect your program's output as well due to other features of cooked mode. Given that, your best bet is to jump into raw mode, grab a character, and return to cooked mode immediately.</p>
<p>Ruby now ships with a standard library that will do exactly that:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"io/console"</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="n">char</span> <span class="o">=</span> <span class="vg">$stdin</span><span class="o">.</span><span class="n">getch</span>
<span class="nb">puts</span> <span class="s2">"You typed: </span><span class="si">#{</span><span class="n">char</span><span class="o">.</span><span class="n">inspect</span><span class="si">}</span><span class="s2">"</span>
<span class="k">break</span> <span class="k">if</span> <span class="n">char</span> <span class="o">==</span> <span class="sc">?q</span>
<span class="k">end</span>
</pre></div>
<p>Behold the magic:</p>
<pre><code>You typed: "a"
You typed: "q"
</code></pre>
<p>There are no newlines this time because the computer is now responding the instant I press a key. Victory. Sort of.</p>
<p>Even this tiny dip of our toes into raw mode has changed other things. You don't see what I typed <em>echoed</em> before the program provides its own output as it was before. This is another feature of cooked mode and you'll need to do your own echoing when reading like this, if you need it.</p>
<p>Here's another change that happens when I push control-C:</p>
<pre><code>You typed: "\u0003"
</code></pre>
<p>As you can see, it no longer interrupts my process. In fact, all of the control sequences have lost their magic powers (control-D to close <code>$stdin</code> and control-Z to suspend the program, for example). You will now need to handle these keystrokes yourself if you want them to have some effect. (Please do support control-C at the very least!)</p>
<p>Terminals have another mode that helps with this control keystroke issue, called <em>cbreak mode</em>. It gives you a near raw mode experience, but with these low-level keystrokes still handled. It makes me very sad to report that <code>io/console</code> doesn't yet support this handy mode.</p>
<p>Still, <code>io/console</code> is great to have. You can read about what it can do in <a href="http://www.ruby-doc.org/stdlib-2.2.0/libdoc/io/console/rdoc/IO.html">the API documentation</a> and <a href="https://github.com/nobu/io-console">its README</a>. Here's one more simple example of how easy <code>io/console</code> can make things, like finding the screen size:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"io/console"</span>
<span class="n">rows</span><span class="p">,</span> <span class="n">cols</span> <span class="o">=</span> <span class="vg">$stdout</span><span class="o">.</span><span class="n">winsize</span>
<span class="nb">puts</span> <span class="s2">"Columns: </span><span class="si">#{</span><span class="n">cols</span><span class="si">}</span><span class="s2">, Rows: </span><span class="si">#{</span><span class="n">rows</span><span class="si">}</span><span class="s2">"</span>
</pre></div>
<h6>Full Raw Mode</h6>
<p>Dipping into raw mode to grab characters is great when it works for your needs, but the code I've showed so far has a drawback. <code>io/console</code>'s <code>getch()</code> is a blocking method. It will wait for input. This is a problem if you need to manage your own event loop, take user input when it's available, but continue making things happen when it's not.</p>
<p>If there's a way to solve this problem with just <code>io/console</code>, I haven't been able to derive the proper incantation. However, we can pull it off by pulling one more standard library into the mix.</p>
<p>To show how this works, imagine that we want to prompt a user with what to do if they don't take any action for a few seconds. Here's some code:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"io/console"</span>
<span class="nb">require</span> <span class="s2">"io/wait"</span>
<span class="vg">$stdin</span><span class="o">.</span><span class="n">raw</span> <span class="k">do</span> <span class="o">|</span><span class="n">io</span><span class="o">|</span>
<span class="n">last_read</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span>
<span class="n">prompted</span> <span class="o">=</span> <span class="kp">false</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="n">char</span> <span class="o">=</span> <span class="n">io</span><span class="o">.</span><span class="n">ready?</span> <span class="o">&&</span> <span class="n">io</span><span class="o">.</span><span class="n">sysread</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">if</span> <span class="n">char</span>
<span class="n">last_read</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span>
<span class="n">prompted</span> <span class="o">=</span> <span class="kp">false</span>
<span class="nb">puts</span> <span class="s2">"You typed: </span><span class="si">#{</span><span class="n">char</span><span class="o">.</span><span class="n">inspect</span><span class="si">}</span><span class="s2">"</span>
<span class="k">break</span> <span class="k">if</span> <span class="n">char</span> <span class="o">==</span> <span class="sc">?q</span>
<span class="k">else</span>
<span class="k">if</span> <span class="o">!</span><span class="n">prompted</span> <span class="o">&&</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span> <span class="o">-</span> <span class="n">last_read</span> <span class="o">></span> <span class="mi">3</span>
<span class="nb">puts</span> <span class="s2">"Please type a character."</span>
<span class="n">prompted</span> <span class="o">=</span> <span class="kp">true</span>
<span class="k">end</span>
<span class="nb">sleep</span> <span class="mi">0</span><span class="o">.</span><span class="mi">1</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>This time I used <code>io/console</code> to put the whole program into raw mode. Cooked mode is restored when the block passed to <code>raw()</code> exits.</p>
<p>The other library I used is <code>io/wait</code>. It adds a <code>ready?()</code> method to <code>$stdin</code> that will return <code>true</code> when there's content waiting to be read. I pull that content out a byte at a time using the low-level <code>sysread()</code> to avoid any buffering.</p>
<p>This kind of works, but we've picked up a new issue:</p>
<pre><code>Please type a character.
You typed: "a"
You typed: "q"
</code></pre>
<p>See how my output is now staggered?</p>
<p>Yet another feature of cooked mode is that newlines in your program's output are translated to a carriage return newline sequence (<code>\r\n</code>). Rubyists are used to <code>puts()</code>ing out some content, with a newline added, and seeing the cursor move to the beginning of the next line. But a newline doesn't really do that by itself.</p>
<p>The newline character means move down a line. As we saw earlier, a carriage return sends you to the beginning of the current line. So it's really the combined effect of both that we're used to being normal.</p>
<p>My output above has only the newline effect, because we're in raw mode and the translation to a two character sequence isn't active. That explains the problem and gives us a way around it. In raw mode, you'll need to manually end lines with <code>\r\n</code> when you want to move to the beginning of the next line.</p>
<p>Here's a fixed version of the code above:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"io/console"</span>
<span class="nb">require</span> <span class="s2">"io/wait"</span>
<span class="vg">$stdin</span><span class="o">.</span><span class="n">raw</span> <span class="k">do</span> <span class="o">|</span><span class="n">io</span><span class="o">|</span>
<span class="n">last_read</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span>
<span class="n">prompted</span> <span class="o">=</span> <span class="kp">false</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="n">char</span> <span class="o">=</span> <span class="n">io</span><span class="o">.</span><span class="n">ready?</span> <span class="o">&&</span> <span class="n">io</span><span class="o">.</span><span class="n">sysread</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">if</span> <span class="n">char</span>
<span class="n">last_read</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span>
<span class="n">prompted</span> <span class="o">=</span> <span class="kp">false</span>
<span class="nb">puts</span> <span class="s2">"You typed: </span><span class="si">#{</span><span class="n">char</span><span class="o">.</span><span class="n">inspect</span><span class="si">}</span><span class="se">\r\n</span><span class="s2">"</span> <span class="c1"># added \r\n</span>
<span class="k">break</span> <span class="k">if</span> <span class="n">char</span> <span class="o">==</span> <span class="sc">?q</span>
<span class="k">else</span>
<span class="k">if</span> <span class="o">!</span><span class="n">prompted</span> <span class="o">&&</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span> <span class="o">-</span> <span class="n">last_read</span> <span class="o">></span> <span class="mi">3</span>
<span class="nb">puts</span> <span class="s2">"Please type a character.</span><span class="se">\r\n</span><span class="s2">"</span> <span class="c1"># added \r\n</span>
<span class="n">prompted</span> <span class="o">=</span> <span class="kp">true</span>
<span class="k">end</span>
<span class="nb">sleep</span> <span class="mi">0</span><span class="o">.</span><span class="mi">1</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>The comments show the two minor changes to the code.</p>
<p>And now the output looks more typical:</p>
<pre><code>Please type a character.
You typed: "a"
You typed: "q"
</code></pre>
<p>We've handled a lot of cases, but there are still issues with a raw approach like this. I'll show you one more. Watch what happens when I press an arrow key while running our latest program:</p>
<pre><code>You typed: "\e"
You typed: "["
You typed: "A"
</code></pre>
<p>Ugh. That's all one keystroke, but we pulled it out as separate bytes. This means we wouldn't be able to have conditionals in our program check for this keypress.</p>
<p>Can we fix it? Sure. We can add some more code to detect these escape sequences and combine them into a single <code>String</code>. Here's what that might look like:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"io/console"</span>
<span class="nb">require</span> <span class="s2">"io/wait"</span>
<span class="no">CSI</span> <span class="o">=</span> <span class="s2">"</span><span class="se">\e</span><span class="s2">["</span>
<span class="k">def</span> <span class="nf">get_char_or_sequence</span><span class="p">(</span><span class="n">io</span><span class="p">)</span>
<span class="k">if</span> <span class="n">io</span><span class="o">.</span><span class="n">ready?</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">io</span><span class="o">.</span><span class="n">sysread</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">while</span> <span class="p">(</span> <span class="no">CSI</span><span class="o">.</span><span class="n">start_with?</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> <span class="o">||</span>
<span class="p">(</span> <span class="n">result</span><span class="o">.</span><span class="n">start_with?</span><span class="p">(</span><span class="no">CSI</span><span class="p">)</span> <span class="o">&&</span>
<span class="o">!</span><span class="n">result</span><span class="o">.</span><span class="n">codepoints</span><span class="o">[-</span><span class="mi">1</span><span class="o">].</span><span class="n">between?</span><span class="p">(</span><span class="mi">64</span><span class="p">,</span> <span class="mi">126</span><span class="p">)</span> <span class="p">)</span> <span class="p">)</span> <span class="o">&&</span>
<span class="p">(</span><span class="n">next_char</span> <span class="o">=</span> <span class="n">get_char_or_sequence</span><span class="p">(</span><span class="n">io</span><span class="p">))</span>
<span class="n">result</span> <span class="o"><<</span> <span class="n">next_char</span>
<span class="k">end</span>
<span class="n">result</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="vg">$stdin</span><span class="o">.</span><span class="n">raw</span> <span class="k">do</span> <span class="o">|</span><span class="n">io</span><span class="o">|</span>
<span class="n">last_read</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span>
<span class="n">prompted</span> <span class="o">=</span> <span class="kp">false</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="n">char</span> <span class="o">=</span> <span class="n">get_char_or_sequence</span><span class="p">(</span><span class="n">io</span><span class="p">)</span>
<span class="k">if</span> <span class="n">char</span>
<span class="n">last_read</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span>
<span class="n">prompted</span> <span class="o">=</span> <span class="kp">false</span>
<span class="nb">puts</span> <span class="s2">"You typed: </span><span class="si">#{</span><span class="n">char</span><span class="o">.</span><span class="n">inspect</span><span class="si">}</span><span class="se">\r\n</span><span class="s2">"</span>
<span class="k">break</span> <span class="k">if</span> <span class="n">char</span> <span class="o">==</span> <span class="sc">?q</span>
<span class="k">else</span>
<span class="k">if</span> <span class="o">!</span><span class="n">prompted</span> <span class="o">&&</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span> <span class="o">-</span> <span class="n">last_read</span> <span class="o">></span> <span class="mi">3</span>
<span class="nb">puts</span> <span class="s2">"Please type a character.</span><span class="se">\r\n</span><span class="s2">"</span>
<span class="n">prompted</span> <span class="o">=</span> <span class="kp">true</span>
<span class="k">end</span>
<span class="nb">sleep</span> <span class="mi">0</span><span class="o">.</span><span class="mi">1</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>All of the new code here is in the <code>get_char_or_sequence()</code> method. The <code>while</code> loop in there checks to see if a read character looks like the start of a <code>CSI</code> sequence and keeps reading, assuming there's more input, until we've seen the full <code>CSI</code>. Then, if what we've read starts with a <code>CSI</code> and there's still more input, we keep reading until we see a termination character for the sequence.</p>
<p>The end result? We can now treat arrow keys as a single unit (as long as our terminal returns them as ANSI escape sequences with a leading <code>CSI</code>):</p>
<pre><code>You typed: "\e[A"
</code></pre>
<p>That's progress. There's definitely more we could handle though, like non-<code>CSI</code> escape sequences and larger Unicode characters. This rabbit hole goes pretty deep. You'll need to decide how many such cases you need to handle for your programs needs.</p>
<p>If you have to deal with many of the various topics covered by this post, you may want to consider using a gem that can handle some of them for you. They can really smooth over some of these edge cases. The details of that are another blog post, but my hope is that this article starts to at least get you familiar with what's involved in fancy terminal I/O.</p>James Edward Gray II