├── .github ├── dependabot.yml └── workflows │ ├── macos.yml │ ├── push_gem.yml │ ├── ubuntu.yml │ └── windows.yml ├── .gitignore ├── .gitmodules ├── BSDL ├── COPYING ├── Gemfile ├── History.md ├── README.md ├── Rakefile ├── curses.gemspec ├── ext └── curses │ ├── curses.c │ ├── depend │ └── extconf.rb ├── lib └── curses.rb └── sample ├── addch.rb ├── attr_demo.rb ├── colors.rb ├── form.rb ├── hello.rb ├── menu.rb ├── mouse.rb ├── mouse_move.rb ├── rain.rb ├── screen.rb ├── view.rb └── view2.rb /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: macos 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: macos-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Install dependencies 11 | run: | 12 | gem install bundler --no-document 13 | bundle install 14 | - name: Build package 15 | run: bundle exec rake build 16 | - name: Install package 17 | run: | 18 | gem install pkg/curses-*.gem 19 | ruby -r curses -e 'puts Curses::VERSION' 20 | -------------------------------------------------------------------------------- /.github/workflows/push_gem.yml: -------------------------------------------------------------------------------- 1 | name: Publish gem to rubygems.org 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | push: 13 | if: github.repository == 'ruby/curses' 14 | runs-on: ubuntu-latest 15 | 16 | environment: 17 | name: rubygems.org 18 | url: https://rubygems.org/gems/curses 19 | 20 | permissions: 21 | contents: write 22 | id-token: write 23 | 24 | steps: 25 | # Set up 26 | - name: Harden Runner 27 | uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1 28 | with: 29 | egress-policy: audit 30 | 31 | - uses: actions/checkout@v4 32 | with: 33 | submodules: true 34 | 35 | - name: Set up Ruby 36 | uses: ruby/setup-ruby@v1 37 | with: 38 | bundler-cache: true 39 | ruby-version: ruby 40 | 41 | # Release 42 | - name: Publish to RubyGems 43 | uses: rubygems/release-gem@v1 44 | 45 | - name: Create GitHub release 46 | run: | 47 | tag_name="$(git describe --tags --abbrev=0)" 48 | gh release create "${tag_name}" --verify-tag --draft --generate-notes 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: ubuntu 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | ruby: [ head, 3.3, 3.2, 3.1, 3.0 ] 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: ruby/setup-ruby@v1 14 | with: 15 | ruby-version: ${{ matrix.ruby }} 16 | - name: Install dependencies 17 | run: | 18 | sudo apt install libncursesw5-dev 19 | gem install bundler --no-document 20 | bundle install 21 | - name: Build package 22 | run: bundle exec rake build 23 | - name: Install package 24 | run: | 25 | gem install pkg/curses-*.gem 26 | ruby -r curses -e 'puts Curses::VERSION' 27 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: windows 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: windows-latest 8 | strategy: 9 | matrix: 10 | ruby: [ 'mingw', 'mswin', '3.3' ] 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | submodules: true 15 | - name: Set up Ruby 16 | uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: ${{ matrix.ruby }} 19 | bundler-cache: true 20 | - name: Build package 21 | run: bundle exec rake build 22 | - name: Install package 23 | run: | 24 | gem install pkg/curses-*.gem 25 | ruby -r curses -e 'puts Curses::VERSION' 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | *.swp 3 | /TAGS 4 | /doc 5 | /lib/curses.bundle 6 | /lib/curses.so 7 | /lib/curses.dll 8 | /pkg 9 | /tmp 10 | /Gemfile.lock 11 | /vendor/x86-mingw32 12 | /vendor/x64-mingw32 13 | /vendor/x64-mswin64* 14 | /ext/curses/Makefile 15 | .ruby-version 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/PDCurses"] 2 | path = vendor/PDCurses 3 | url = https://github.com/shugo/PDCurses.git 4 | -------------------------------------------------------------------------------- /BSDL: -------------------------------------------------------------------------------- 1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the 3 | 2-clause BSDL (see the file BSDL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a) place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b) use the modified software only within your corporation or 18 | organization. 19 | 20 | c) give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d) make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a) distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b) accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c) give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d) make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source "https://rubygems.org" 3 | 4 | gemspec 5 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | This file is no longer being updated after version 1.4.5. 2 | For changes since version 1.4.5, see . 3 | 4 | ### 1.4.5 / 2024-04-24 5 | 6 | New features: 7 | 8 | * Add documentation of TERM. 9 | Issue #80 by rubyFeedback. 10 | * Add MacOS directives to install curses with menu support 11 | Pull request #84 by AlexB52. 12 | 13 | Bug fixes: 14 | 15 | * Add x permissoin to samples. 16 | Pull request #71 by dvarrui. 17 | * Supress gem installation warning. 18 | Issue #77 by rubyFeedback. 19 | * Ignore Curses::UnknownCommandError. 20 | Issue #79 by rubyFeedback. 21 | * Fix installation problem on macOS (Apple Silicon). 22 | Issue #85 by pusewicz. 23 | 24 | ### 1.4.4 / 2022-02-03 25 | 26 | Bug fixes: 27 | 28 | * Define Curses.colors even if COLORS is a macro. 29 | Issue #69 by dvarrui. 30 | * Use require instead of require_relative. 31 | Pull request #68 by dvarrui. 32 | 33 | ### 1.4.3 / 2022-01-06 34 | 35 | New features: 36 | 37 | * Added flushinp, menu mark, fore and back functions. 38 | Pull request #66 by Isty001. 39 | 40 | ### 1.4.2 / 2021-06-14 41 | 42 | New features: 43 | 44 | * Added samples for addch, attron, mouse tracking and colors. 45 | Pull request #62 by coezbek. 46 | 47 | ### 1.4.1 / 2021-05-22 48 | 49 | Bug fixes: 50 | 51 | * Use chtype instead of char to support attributes 52 | * Fixes for Ruby 3.1. 53 | 54 | ### 1.4.0 / 2020-12-10 55 | 56 | New features: 57 | 58 | * Remove fat binary support for newer versions of Ruby 59 | 60 | ### 1.3.2 / 2019-12-20 61 | 62 | Bug fixes: 63 | 64 | * Drop rb_safe_level check for Ruby 2.7 by Eric Wong. 65 | * Try libtinfow first. Issue #52 by Marco Sirabella. 66 | 67 | ### 1.3.1 / 2019-04-21 68 | 69 | Bug fixes: 70 | 71 | * Check whether sizeof(WINDOW) is available to avoid build failures on macOS. 72 | Issue #48 reported by chdiza. 73 | 74 | ### 1.3.0 / 2019-04-16 75 | 76 | New features: 77 | 78 | * Add Curses::Form and Curses::Field. 79 | 80 | Bug fixes: 81 | 82 | * Fix TravisCI issues by amatsuda and znz. 83 | * Fix typo in sample/menu.rb by binford2k. 84 | * Ctrl-/ should return ^_ on Windows. 85 | * Workaround for new Windows console. 86 | https://github.com/Bill-Gray/PDCurses/pull/108 87 | 88 | ### 1.2.7 / 2019-01-10 89 | 90 | Bug fixes: 91 | 92 | * Add curses.so for Ruby 2.5/2.6 on x86-mingw32. 93 | 94 | ### 1.2.6 / 2019-01-09 95 | 96 | New features: 97 | 98 | * Add Curses::Menu and Curses::Item. 99 | 100 | Bug fixes: 101 | 102 | * Link PDCurses statically to avoid LoadError on mingw. 103 | * Use https for documentation link. Pull request #43 by stomar. 104 | * Fix typo in example code. Pull request #44 by stomar. 105 | 106 | ### 1.2.5 / 2018-10-16 107 | 108 | New features: 109 | 110 | * Add mvderwin and derwin methods to Window. Pull request #37 by meschbach. 111 | * Add documentation link. Pull request #39 by atshakil. 112 | 113 | Bug fixes: 114 | 115 | * Favor ncursesw over curses. Pull request #40 by DivineDominion. 116 | 117 | ### 1.2.4 / 2017-09-13 118 | 119 | New features: 120 | 121 | * Update PDCurses. 122 | 123 | Bug fixes: 124 | 125 | * Fix the path of pdcurses.dll for i386-mingw. (Issue #36) 126 | 127 | Documentation: 128 | 129 | * Include reference to ncurses dependency. Pull request #34 by qume. 130 | 131 | ### 1.2.3 / 2017-07-03 132 | 133 | Bug fixes: 134 | 135 | * Fixes for mswin. Pull requests #30, #31, #32 and #33 by unak. 136 | 137 | ### 1.2.2 / 2017-04-22 138 | 139 | New features: 140 | 141 | * Add Curses.get_key_modifiers, Curses.return_key_modifiers, and 142 | Curses.save_key_modifiers. 143 | * Support mswin native build. Pull request #29 by usa. 144 | 145 | ### 1.2.1 / 2017-03-27 146 | 147 | New features: 148 | 149 | * Add touch, untouch, touched?, touch_line, and line_touched?. 150 | 151 | Bug fixes: 152 | 153 | * Fix Pad#subpad to use subpad(3). (Issue #23) 154 | * Fix build issues on macOS. Pull requests #24, #25, #26, #27 and #28 by nobu. 155 | 156 | ### 1.2.0 / 2017-02-19 157 | 158 | New features: 159 | 160 | * Add Curses.assume_default_colors. 161 | 162 | Bug fixes: 163 | 164 | * Curses.unget_char should use String#ord even if unget_wch() is not available. 165 | * The default value of keyboard_encoding should be ASCII-8BIT if get_wch() is 166 | not available. 167 | * NUM2ULONG() should be used in Window#bkgd etc. 168 | 169 | ### 1.1.3 / 2017-02-08 170 | 171 | Bug fixes: 172 | 173 | * Update PDCurses to handle extended keys. 174 | 175 | ### 1.1.2 / 2017-02-06 176 | 177 | Bug fixes: 178 | 179 | * Use the left-alt-fix branch of https://github.com/shugo/PDCurses.git to get 180 | ALT + < etc. 181 | 182 | ### 1.1.1 / 2017-01-25 183 | 184 | Bug fixes: 185 | 186 | * Add -DPDC_WIDE to CPPFLAGS when compiling with PDCurses. 187 | 188 | ### 1.1.0 / 2017-01-24 189 | 190 | New features: 191 | 192 | * Use bundler instead of hoe. Pull request #18 by hsbt. 193 | * Enable appveyor. Pull request #19 by hsbt. 194 | * Add badges for build status to README.md. Pull request #20 by hsbt. 195 | * Add Curses.erase and Curses::Window.erase. 196 | * Add Curses::Window.redraw. 197 | * Add Curses.unget_char, Curses.get_char, and Curses::Window.get_char for 198 | multibyte characters. 199 | * Add Curses.keyboard_encoding and Curses.terminal_encoding. 200 | * Support cross compilation for mingw32. 201 | 202 | Bug fixes: 203 | 204 | * Fix error in attron/attroff documentation. Pull request #14 by stomar. 205 | * Improve code samples. Pull request #15 by stomar. 206 | 207 | ### 1.0.2 / 2016-03-15 208 | 209 | Bug fixes: 210 | 211 | * Fix ncursesw support. Pull request #16 by peter50216, patch by eTM. Issue 212 | #6 by Jean Lazarou. 213 | 214 | ### 1.0.1 / 2014-03-26 215 | 216 | Bug fixes: 217 | 218 | * Curses install is a no-op on ruby with bundled curses. Pull request #4 219 | tiredpixel. 220 | 221 | ### 1.0.0 / 2013-12-10 222 | 223 | Birthday! 224 | 225 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # curses 2 | 3 | [![Gem Version](https://badge.fury.io/rb/curses.svg)](https://badge.fury.io/rb/curses) 4 | [![ubuntu](https://github.com/ruby/curses/workflows/ubuntu/badge.svg)](https://github.com/ruby/curses/actions?query=workflow%3Aubuntu) 5 | [![windows](https://github.com/ruby/curses/workflows/windows/badge.svg)](https://github.com/ruby/curses/actions?query=workflow%3Awindows) 6 | [![macos](https://github.com/ruby/curses/workflows/macos/badge.svg)](https://github.com/ruby/curses/actions?query=workflow%3Amacos) 7 | 8 | * https://github.com/ruby/curses 9 | * https://github.com/ruby/curses/issues 10 | 11 | ## Description 12 | 13 | A Ruby binding for curses, ncurses, and PDCurses. 14 | curses is an extension library for text UI applications. 15 | 16 | Formerly part of the Ruby standard library, [curses was removed and placed in this gem][1] 17 | with the release of Ruby 2.1.0. (see [ruby/ruby@9c5b2fd][2]) 18 | 19 | ## Install 20 | 21 | $ gem install curses 22 | 23 | Requires ncurses or ncursesw (with wide character support). 24 | On Debian based distributions, you can install it with apt: 25 | 26 | $ apt install libncurses5-dev 27 | 28 | Or 29 | 30 | $ apt install libncursesw5-dev 31 | 32 | On Windows, `gem install curses` will build bundled PDCurses, so you 33 | don't need to install extra libraries. 34 | However, if you prefer ncurses to PDCurses, specify the following option: 35 | 36 | > gem install curses -- --use-system-libraries 37 | 38 | On mingw, you need DevKit to compile the extension library. 39 | 40 | On MacOS, `ncurses` menu isn't natively supported. You can install the gem with menu support using homebrew: 41 | 42 | brew install ncurses 43 | gem install curses -- --use-system-libraries --with-ncurses-dir=/usr/local/opt/ncurses 44 | 45 | _with `/usr/local/opt/ncurses` the path where homebrew installed ncurses on your machine_ 46 | 47 | ## Documentation 48 | 49 | Type the following command, and see `[rdoc]` of curses: 50 | 51 | > gem server -l 52 | 53 | ## Limitations 54 | 55 | * curses.gem doesn't support more than 256 color pairs. See https://reversed.top/2019-02-05/more-than-256-curses-color-pairs/ for details. 56 | 57 | ## Developers 58 | 59 | After checking out the repo, run `bundle install` to install dependencies. 60 | 61 | To compile the extension library, run `bundle exec rake compile`. 62 | 63 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `curses.gemspec`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 64 | 65 | ## License 66 | 67 | curses is released under the Ruby and 2-clause BSD licenses. See COPYING for 68 | details. 69 | 70 | It includes a forked version of PDCurses, which is in the public domain: 71 | 72 | https://github.com/Bill-Gray/PDCurses 73 | 74 | The version for Win32 console mode in the wincon subdirectory is used. 75 | 76 | [1]: https://bugs.ruby-lang.org/issues/8584 77 | [2]: https://github.com/ruby/ruby/commit/9c5b2fd8aa0fd343ad094d47a638cfd3f6ae0a81 78 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler" 2 | 3 | Bundler::GemHelper.install_tasks 4 | -------------------------------------------------------------------------------- /curses.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new { |s| 2 | s.name = "curses" 3 | s.version = "1.5.1" 4 | s.author = ["Shugo Maeda", 'Eric Hodel'] 5 | s.email = ["shugo@ruby-lang.org", 'drbrain@segment7.net'] 6 | s.homepage = "https://github.com/ruby/curses" 7 | s.platform = Gem::Platform::RUBY 8 | s.summary = "A Ruby binding for curses, ncurses, and PDCurses. curses is an extension library for text UI applications. Formerly part of the Ruby standard library, [curses was removed and placed in this gem][1] with the release of Ruby 2.1.0. (see [ruby/ruby@9c5b2fd][2])" 9 | s.files = `git ls-files --recurse-submodules -z`.split("\x0") 10 | s.extensions = ["ext/curses/extconf.rb"] 11 | s.require_path = "lib" 12 | s.required_ruby_version = Gem::Requirement.new('>= 3.0') 13 | s.licenses = ['Ruby', 'BSD-2-Clause'] 14 | s.add_development_dependency 'bundler' 15 | s.add_development_dependency 'rake' 16 | } 17 | -------------------------------------------------------------------------------- /ext/curses/depend: -------------------------------------------------------------------------------- 1 | $(OBJS): $(HDRS) $(ruby_headers) \ 2 | $(hdrdir)/ruby/io.h \ 3 | $(hdrdir)/ruby/encoding.h \ 4 | $(hdrdir)/ruby/oniguruma.h \ 5 | $(hdrdir)/ruby/thread.h 6 | -------------------------------------------------------------------------------- /ext/curses/extconf.rb: -------------------------------------------------------------------------------- 1 | require 'mkmf' 2 | require 'shellwords' 3 | require 'fileutils' 4 | 5 | def have_all(*args) 6 | old_libs = $libs.dup 7 | old_defs = $defs.dup 8 | result = [] 9 | begin 10 | args.each {|arg| 11 | r = arg.call(*result) 12 | if !r 13 | return nil 14 | end 15 | result << r 16 | } 17 | result 18 | ensure 19 | if result.length != args.length 20 | $libs = old_libs 21 | $defs = old_defs 22 | end 23 | end 24 | end 25 | 26 | def exec_command(cmd) 27 | puts "> #{cmd}" 28 | if !system(cmd) 29 | STDERR.puts("External command failed") 30 | exit 1 31 | end 32 | end 33 | 34 | $library_candidates = [ 35 | ["ncursesw/curses.h", ["ncursesw"]], 36 | ["ncurses.h", ["ncursesw", "ncurses"]], 37 | ["ncurses/curses.h", ["ncurses"]], 38 | ["curses_colr/curses.h", ["cur_colr"]], 39 | ["curses.h", ["curses", "pdcurses"]], 40 | # ["xcurses.h", ["XCurses"]], # XCurses (PDCurses for X11) 41 | ] 42 | 43 | $mingw = /mingw/ =~ RUBY_PLATFORM 44 | $mswin = /mswin/ =~ RUBY_PLATFORM 45 | $windows = $mingw || $mswin 46 | $x64 = /x64/ =~ RUBY_PLATFORM 47 | $use_system_libs = arg_config('--use-system-libraries', 48 | ENV.key?("CURSES_USE_SYSTEM_LIBRARIES")) 49 | $idefault = nil 50 | $ldefault = nil 51 | $pdcurses_wide_default = nil 52 | $pdcurses_dll_default = nil 53 | $curses_version_default = nil 54 | $use_bundled_pdcurses = !$use_system_libs && /mingw|mswin/ =~ RUBY_PLATFORM 55 | if $use_bundled_pdcurses 56 | $pdcurses_wide_default = true 57 | $curses_version_default = "function" 58 | pdcurses_dir = File.expand_path("../../vendor/PDCurses", __dir__) 59 | $idefault = $ldefault = pdcurses_dir 60 | wincon_dir = File.expand_path("wincon", pdcurses_dir) 61 | old_dir = Dir.pwd 62 | Dir.chdir(wincon_dir) 63 | begin 64 | if $mswin 65 | exec_command "nmake -f Makefile.vc clean all WIDE=Y DLL=Y" 66 | FileUtils.cp("pdcurses.dll", pdcurses_dir) 67 | FileUtils.cp("pdcurses.lib", pdcurses_dir) 68 | $pdcurses_dll_default = true 69 | else 70 | w64 = $x64 ? "_w64=1" : "" 71 | exec_command "make -f Makefile.mng clean all #{w64} WIDE=Y DLL=N CC=\"gcc -std=gnu17\"" 72 | FileUtils.cp("pdcurses.a", File.expand_path("libpdcurses.a", pdcurses_dir)) 73 | end 74 | ensure 75 | Dir.chdir(old_dir) 76 | end 77 | FileUtils.cp(File.expand_path("curses.h", pdcurses_dir), 78 | File.expand_path("pdcurses.h", pdcurses_dir)) 79 | $library_candidates = [ 80 | ["pdcurses.h", ["pdcurses"]] 81 | ] 82 | end 83 | 84 | dir_config('curses', $idefault, $ldefault) 85 | dir_config('ncurses') 86 | dir_config('termcap') 87 | 88 | have_library("mytinfo", "tgetent") if /bow/ =~ RUBY_PLATFORM 89 | have_library("tinfow", "tgetent") || 90 | have_library("tinfo", "tgetent") || 91 | have_library("termcap", "tgetent") 92 | 93 | header_library = nil 94 | $library_candidates.each {|hdr, libs| 95 | header_library = have_all( 96 | lambda { have_header(hdr) && hdr }, 97 | lambda {|h| libs.find {|lib| have_library(lib, "initscr", h) } }) 98 | if header_library 99 | break; 100 | end 101 | } 102 | 103 | if header_library 104 | header, library = header_library 105 | puts "header: #{header}" 106 | puts "library: #{library}" 107 | 108 | curses = [header] 109 | if header == 'curses_colr/curses.h' 110 | curses.unshift("varargs.h") 111 | end 112 | 113 | for f in %w(beep bkgd bkgdset curs_set deleteln doupdate flash 114 | getbkgd getnstr init isendwin keyname keypad resizeterm 115 | scrl set setscrreg ungetch addnwstr 116 | wattroff wattron wattrset wbkgd wbkgdset wdeleteln wgetnstr 117 | wresize wscrl wsetscrreg werase redrawwin waddnwstr mvderwin derwin 118 | touchwin untouchwin wtouchln is_linetouched is_wintouched 119 | def_prog_mode reset_prog_mode timeout wtimeout nodelay 120 | init_color wcolor_set use_default_colors assume_default_colors 121 | newpad unget_wch get_wch wget_wch PDC_get_key_modifiers 122 | chgat wchgat newterm) 123 | have_func(f) || (have_macro(f, curses) && $defs.push(format("-DHAVE_%s", f.upcase))) 124 | end 125 | convertible_int('chtype', [["#undef MOUSE_MOVED\n"]]+curses) or abort 126 | flag = "-D_XOPEN_SOURCE_EXTENDED" 127 | if checking_for("_XOPEN_SOURCE_EXTENDED") { 128 | try_compile(cpp_include(%w[stdio.h stdlib.h]+curses), flag, :werror => true) 129 | } 130 | $defs << flag 131 | end 132 | have_var("ESCDELAY", curses) 133 | have_var("TABSIZE", curses) 134 | have_var("COLORS", curses) 135 | have_var("COLOR_PAIRS", curses) 136 | 137 | # SVR4 curses has a (undocumented) variable char *curses_version. 138 | # ncurses and PDcurses has a function char *curses_version(). 139 | # Note that the original BSD curses doesn't provide version information. 140 | # 141 | # configure option: 142 | # --with-curses-version=function for SVR4 143 | # --with-curses-version=variable for ncurses and PDcurses 144 | # (not given) automatically determined 145 | with_curses_version = with_config("curses-version", $curses_version_default) 146 | case with_curses_version 147 | when "function" 148 | $defs << '-DHAVE_FUNC_CURSES_VERSION' 149 | when "variable" 150 | $defs << '-DHAVE_VAR_CURSES_VERSION' 151 | when nil 152 | func_test_program = cpp_include(curses) + <<-"End" 153 | int main(int argc, char *argv[]) 154 | { 155 | curses_version(); 156 | return EXIT_SUCCESS; 157 | } 158 | End 159 | var_test_program = cpp_include(curses) + <<-"End" 160 | extern char *curses_version; 161 | int main(int argc, char *argv[]) 162 | { 163 | int i = 0; 164 | for (i = 0; i < 100; i++) { 165 | if (curses_version[i] == 0) 166 | return 0 < i ? EXIT_SUCCESS : EXIT_FAILURE; 167 | if (curses_version[i] & 0x80) 168 | return EXIT_FAILURE; 169 | } 170 | return EXIT_FAILURE; 171 | } 172 | End 173 | try = method(CROSS_COMPILING ? :try_link : :try_run) 174 | function_p = checking_for(checking_message('function curses_version', curses)) { try[func_test_program] } 175 | variable_p = checking_for(checking_message('variable curses_version', curses)) { try[var_test_program] } 176 | if function_p and variable_p 177 | if [header, library].grep(/ncurses|pdcurses|xcurses/i) 178 | variable_p = false 179 | else 180 | warn "found curses_version but cannot determine whether it is a" 181 | warn "function or a variable, so assume a variable in old SVR4" 182 | warn "ncurses." 183 | function_p = false 184 | end 185 | end 186 | $defs << '-DHAVE_FUNC_CURSES_VERSION' if function_p 187 | $defs << '-DHAVE_VAR_CURSES_VERSION' if variable_p 188 | else 189 | warn "unexpected value for --with-curses-version: #{with_curses_version}" 190 | end 191 | 192 | if enable_config("pdcurses-wide", $pdcurses_wide_default) 193 | $defs << '-DPDC_WIDE' 194 | end 195 | 196 | if enable_config("pdcurses-dll", $pdcurses_dll_default) 197 | $defs << '-DPDC_DLL_BUILD' 198 | end 199 | 200 | if !$use_bundled_pdcurses && 201 | (have_header("ncursesw/menu.h") || 202 | have_header("ncurses/menu.h") || 203 | have_header("curses/menu.h") || 204 | have_header("menu.h")) && 205 | (have_library("menuw", "new_menu") || 206 | have_library("menu", "new_menu")) 207 | $defs << '-DHAVE_MENU' 208 | end 209 | 210 | if !$use_bundled_pdcurses && 211 | (have_header("ncursesw/form.h") || 212 | have_header("ncurses/form.h") || 213 | have_header("curses/form.h") || 214 | have_header("form.h")) && 215 | (have_library("formw", "new_form") || 216 | have_library("form", "new_form")) 217 | $defs << '-DHAVE_FORM' 218 | have_func("form_driver_w") 219 | end 220 | 221 | ["WINDOW", "MEVENT", "ITEM", "MENU", "FIELD", "FORM", "SCREEN"].each do |type| 222 | checking_for("sizeof(#{type}) is available") { 223 | if try_compile(< 226 | #elif defined(HAVE_NCURSESW_CURSES_H) 227 | # include 228 | #elif defined(HAVE_NCURSES_CURSES_H) 229 | # include 230 | #elif defined(HAVE_NCURSES_H) 231 | # include 232 | #elif defined(HAVE_CURSES_COLR_CURSES_H) 233 | # ifdef HAVE_STDARG_PROTOTYPES 234 | # include 235 | # else 236 | # include 237 | # endif 238 | # include 239 | #else 240 | # include 241 | #endif 242 | 243 | #if defined(HAVE_NCURSESW_MENU_H) 244 | # include 245 | #elif defined(HAVE_NCURSES_MENU_H) 246 | # include 247 | #elif defined(HAVE_CURSES_MENU_H) 248 | # include 249 | #elif defined(HAVE_MENU_H) 250 | # include 251 | #endif 252 | 253 | #if defined(HAVE_NCURSESW_FORM_H) 254 | # include 255 | #elif defined(HAVE_NCURSES_FORM_H) 256 | # include 257 | #elif defined(HAVE_CURSES_FORM_H) 258 | # include 259 | #elif defined(HAVE_FORM_H) 260 | # include 261 | #endif 262 | 263 | const int sizeof_#{type} = (int) sizeof(#{type}); 264 | EOF 265 | $defs << "-DCURSES_SIZEOF_#{type}=sizeof(#{type})" 266 | true 267 | else 268 | $defs << "-DCURSES_SIZEOF_#{type}=0" 269 | false 270 | end 271 | } 272 | end 273 | 274 | if RUBY_VERSION >= '2.1' 275 | create_header 276 | create_makefile("curses") 277 | else 278 | # curses is part of ruby-core pre-2.1.0, so this gem is not required. But 279 | # make pre-2.1.0 a no-op rather than failing or listing as unsupported, to 280 | # aid gems offering multi-version Ruby support. 281 | File.open("Makefile", 'w') do |f| 282 | f.puts dummy_makefile("curses").join 283 | end 284 | end 285 | end 286 | -------------------------------------------------------------------------------- /lib/curses.rb: -------------------------------------------------------------------------------- 1 | pdcurses_dll = File.expand_path("../vendor/PDCurses/pdcurses.dll", __dir__) 2 | if File.exist?(pdcurses_dll) 3 | path = ENV["PATH"] 4 | dir = File::ALT_SEPARATOR ? 5 | File.dirname(pdcurses_dll).tr("/", File::ALT_SEPARATOR) : dir 6 | dirs = path.split(File::PATH_SEPARATOR) 7 | if !dirs.include?(dir) 8 | ENV["PATH"] = [dir, *dirs].join(File::PATH_SEPARATOR) 9 | end 10 | end 11 | 12 | require "curses.so" 13 | 14 | if /mingw|mswin/ =~ RUBY_PLATFORM 15 | Curses.keyboard_encoding = Encoding::UTF_8 16 | end 17 | 18 | if defined?(Curses::Menu) 19 | class Curses::Menu 20 | def left_item 21 | driver(Curses::REQ_LEFT_ITEM) 22 | end 23 | 24 | def right_item 25 | driver(Curses::REQ_RIGHT_ITEM) 26 | end 27 | 28 | def up_item 29 | driver(Curses::REQ_UP_ITEM) 30 | end 31 | 32 | def down_item 33 | driver(Curses::REQ_DOWN_ITEM) 34 | end 35 | 36 | def scroll_up_line 37 | driver(Curses::REQ_SCR_ULINE) 38 | end 39 | 40 | def scroll_down_line 41 | driver(Curses::REQ_SCR_DLINE) 42 | end 43 | 44 | def scroll_up_page 45 | driver(Curses::REQ_SCR_UPAGE) 46 | end 47 | 48 | def scroll_down_page 49 | driver(Curses::REQ_SCR_DPAGE) 50 | end 51 | 52 | def first_item 53 | driver(Curses::REQ_FIRST_ITEM) 54 | end 55 | 56 | def last_item 57 | driver(Curses::REQ_LAST_ITEM) 58 | end 59 | 60 | def next_item 61 | driver(Curses::REQ_NEXT_ITEM) 62 | end 63 | 64 | def prev_item 65 | driver(Curses::REQ_PREV_ITEM) 66 | end 67 | 68 | def toggle_item 69 | driver(Curses::REQ_TOGGLE_ITEM) 70 | end 71 | 72 | def clear_pattern 73 | driver(Curses::REQ_CLEAR_PATTERN) 74 | end 75 | 76 | def back_pattern 77 | driver(Curses::REQ_BACK_PATTERN) 78 | end 79 | 80 | def next_match 81 | driver(Curses::REQ_NEXT_MATCH) 82 | end 83 | 84 | def prev_match 85 | driver(Curses::REQ_PREV_MATCH) 86 | end 87 | end 88 | end 89 | 90 | module Curses 91 | module_function 92 | 93 | def chgat(...) 94 | Curses.stdscr.chgat(...) 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /sample/addch.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # require_relative "../lib/curses" 3 | require 'curses' 4 | 5 | include Curses 6 | 7 | init_screen 8 | begin 9 | addstr("The following letter A should be BOLD and UNDERLINED by using addch:\n") 10 | addch('A'.ord | A_BOLD | A_UNDERLINE) 11 | 12 | addstr("\nIt should look the same as when using attron and addstr:\n") 13 | attron(A_BOLD | A_UNDERLINE) 14 | addstr("A") 15 | getch 16 | 17 | ensure 18 | close_screen 19 | end 20 | -------------------------------------------------------------------------------- /sample/attr_demo.rb: -------------------------------------------------------------------------------- 1 | require "curses" 2 | include Curses 3 | 4 | init_screen 5 | begin 6 | attrs = { 7 | A_NORMAL => 'Normal display (no highlight)', 8 | A_STANDOUT => 'Best highlighting mode of the terminal', 9 | A_UNDERLINE => 'Underlining', 10 | A_REVERSE => 'Reverse video', 11 | A_BLINK => 'Blinking', 12 | A_DIM => 'Half bright', 13 | A_BOLD => 'Extra bright or bold', 14 | A_PROTECT => 'Protected mode', 15 | A_INVIS => 'Invisible or blank mode', 16 | A_ALTCHARSET => 'Alternate character set', 17 | } 18 | 19 | longest_description = attrs.values.map(&:size).max 20 | attrs.each { |attribute, description| 21 | 22 | attrset(A_NORMAL) 23 | addstr("#{description.ljust(longest_description)}: ") 24 | 25 | attrset(attribute) 26 | addstr([*('a'..'z'), *('0'..'9')].join + "\n") 27 | } 28 | getch 29 | setpos(0, 0) 30 | chgat(6, A_UNDERLINE) 31 | getch 32 | ensure 33 | close_screen 34 | end 35 | -------------------------------------------------------------------------------- /sample/colors.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "curses" 4 | include Curses 5 | 6 | # The TERM environment variable should be set to xterm-256color etc. to 7 | # use 256 colors. Curses.colors returns the color numbers of the terminal. 8 | 9 | begin 10 | init_screen 11 | 12 | if !Curses.has_colors? 13 | addstr "This Terminal does not support colors!" 14 | else 15 | start_color 16 | 17 | addstr "This Terminal supports #{colors} colors.\n" 18 | 19 | Curses.colors.times { |i| 20 | Curses.init_pair(i, i, 0) 21 | attrset(color_pair(i)) 22 | addstr("#{i.to_s.rjust(3)} ") 23 | addstr("\n") if i == 15 || (i > 16 && (i - 15) % 36 == 0) 24 | } 25 | end 26 | 27 | getch 28 | 29 | ensure 30 | close_screen 31 | end 32 | -------------------------------------------------------------------------------- /sample/form.rb: -------------------------------------------------------------------------------- 1 | require "curses" 2 | 3 | Curses.init_screen 4 | Curses.cbreak 5 | Curses.noecho 6 | Curses.stdscr.keypad = true 7 | at_exit do 8 | Curses.close_screen 9 | end 10 | 11 | fields = [ 12 | Curses::Field.new(1, 10, 4, 18, 0, 0), 13 | Curses::Field.new(1, 10, 6, 18, 0, 0) 14 | ] 15 | fields.each do |field| 16 | field.set_back(Curses::A_UNDERLINE) 17 | field.opts_off(Curses::O_AUTOSKIP) 18 | end 19 | 20 | form = Curses::Form.new(fields) 21 | form.post 22 | 23 | Curses.setpos(4, 10) 24 | Curses.addstr("Value 1:") 25 | Curses.setpos(6, 10) 26 | Curses.addstr("Value 2:") 27 | 28 | while ch = Curses.get_char 29 | begin 30 | case ch 31 | when Curses::KEY_F1 32 | break 33 | when Curses::KEY_DOWN 34 | form.driver(Curses::REQ_NEXT_FIELD) 35 | form.driver(Curses::REQ_END_LINE) 36 | when Curses::KEY_UP 37 | form.driver(Curses::REQ_PREV_FIELD) 38 | form.driver(Curses::REQ_END_LINE) 39 | when Curses::KEY_RIGHT 40 | form.driver(Curses::REQ_NEXT_CHAR) 41 | when Curses::KEY_LEFT 42 | form.driver(Curses::REQ_PREV_CHAR) 43 | when Curses::KEY_BACKSPACE 44 | form.driver(Curses::REQ_DEL_PREV) 45 | else 46 | form.driver(ch) 47 | end 48 | rescue Curses::RequestDeniedError, Curses::UnknownCommandError 49 | end 50 | end 51 | 52 | form.unpost 53 | -------------------------------------------------------------------------------- /sample/hello.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "curses" 4 | 5 | def show_message(message) 6 | height = 5 7 | width = message.length + 6 8 | top = (Curses.lines - height) / 2 9 | left = (Curses.cols - width) / 2 10 | win = Curses::Window.new(height, width, top, left) 11 | win.box("|", "-") 12 | win.setpos(2, 3) 13 | win.addstr(message) 14 | win.refresh 15 | win.getch 16 | win.close 17 | end 18 | 19 | Curses.init_screen 20 | begin 21 | Curses.crmode 22 | Curses.setpos((Curses.lines - 1) / 2, (Curses.cols - 11) / 2) 23 | Curses.addstr("Hit any key") 24 | Curses.refresh 25 | Curses.getch 26 | show_message("Hello, World!") 27 | ensure 28 | Curses.close_screen 29 | end 30 | -------------------------------------------------------------------------------- /sample/menu.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "curses" 4 | 5 | Curses.init_screen 6 | Curses.cbreak 7 | Curses.noecho 8 | Curses.stdscr.keypad = true 9 | at_exit do 10 | Curses.close_screen 11 | end 12 | 13 | menu = Curses::Menu.new([ 14 | Curses::Item.new("Apple", "Red fruit"), 15 | Curses::Item.new("Orange", "Orange fruit"), 16 | Curses::Item.new("Banana", "Yellow fruit") 17 | ]) 18 | menu.post 19 | 20 | while ch = Curses.getch 21 | begin 22 | case ch 23 | when Curses::KEY_UP, ?k 24 | menu.up_item 25 | when Curses::KEY_DOWN, ?j 26 | menu.down_item 27 | else 28 | break 29 | end 30 | rescue Curses::RequestDeniedError 31 | end 32 | end 33 | 34 | menu.unpost 35 | -------------------------------------------------------------------------------- /sample/mouse.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "curses" 4 | include Curses 5 | 6 | def show_message(*msgs) 7 | message = msgs.join 8 | height, width = 5, message.length + 6 9 | top, left = (lines - height) / 2, (cols - width) / 2 10 | win = Window.new(height, width, top, left) 11 | win.keypad = true 12 | win.attron(color_pair(COLOR_RED)) do 13 | win.box("|", "-", "+") 14 | end 15 | win.setpos(2, 3) 16 | win.addstr(message) 17 | win.refresh 18 | win.getch 19 | win.close 20 | end 21 | 22 | init_screen 23 | start_color 24 | init_pair(COLOR_BLUE, COLOR_BLUE, COLOR_WHITE) 25 | init_pair(COLOR_RED, COLOR_RED, COLOR_WHITE) 26 | crmode 27 | noecho 28 | stdscr.keypad(true) 29 | 30 | begin 31 | mousemask(BUTTON1_CLICKED|BUTTON2_CLICKED|BUTTON3_CLICKED|BUTTON4_CLICKED) 32 | setpos((lines - 1) / 2, (cols - 5) / 2) 33 | attron(color_pair(COLOR_BLUE)|A_BOLD) do 34 | addstr("click") 35 | end 36 | refresh 37 | loop do 38 | c = getch 39 | case c 40 | when KEY_MOUSE 41 | m = getmouse 42 | if m 43 | show_message("getch = #{c.inspect}, ", 44 | "mouse event = #{'0x%x' % m.bstate}, ", 45 | "axis = (#{m.x},#{m.y},#{m.z})") 46 | end 47 | break 48 | end 49 | end 50 | refresh 51 | ensure 52 | close_screen 53 | end 54 | -------------------------------------------------------------------------------- /sample/mouse_move.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # Mouse movement tracking using CSI 1003 and 1006 (SGR). Will print a cursor that moves with the mouse. 4 | # 5 | 6 | require "curses" 7 | include Curses 8 | 9 | @positions = [] 10 | 11 | def draw_cursor(y, x, button, state) 12 | 13 | # Keep a trail of 10 previous positions visible on the screen 14 | if @positions.size >= 10 15 | y_old, x_old = @positions.shift 16 | setpos(y_old, x_old) 17 | addstr(" ") 18 | end 19 | 20 | setpos(2, 1) 21 | addstr("Mouse is at y=#{y.to_s.ljust(3)} x=#{x.to_s.ljust(3)} button=#{button.to_s.ljust(3)} #{'%08b' % button}") 22 | 23 | setpos(y, x) 24 | addstr("+") 25 | @positions << [y, x] 26 | end 27 | 28 | init_screen 29 | crmode 30 | noecho 31 | stdscr.box('|', "-") 32 | setpos(1,1) 33 | addstr("Please move your mouse. Press 'q' to close.") 34 | 35 | begin 36 | # Switch on mouse continous position reporting 37 | print("\x1b[?1003h") 38 | 39 | # Also enable SGR extended reporting, because otherwise we can only 40 | # receive values up to 160x94. Everything else confuses Ruby Curses. 41 | print("\x1b[?1006h") 42 | 43 | loop do 44 | c = get_char 45 | case c 46 | when "q" 47 | return 48 | when "\e" # ESC 49 | case get_char 50 | when '[' 51 | csi = "" 52 | loop do 53 | d = get_char 54 | csi += d 55 | if d.ord >= 0x40 && d.ord <= 0x7E 56 | break 57 | end 58 | end 59 | if /<(\d+);(\d+);(\d+)(m|M)/ =~ csi 60 | button = $1.to_i 61 | x = $2.to_i 62 | y = $3.to_i 63 | state = $4 64 | draw_cursor(y, x, button, state) 65 | end 66 | end 67 | end 68 | end 69 | 70 | ensure 71 | print("\x1b[?1003l") 72 | print("\x1b[?1006l") 73 | close_screen 74 | end 75 | 76 | -------------------------------------------------------------------------------- /sample/rain.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "curses" 4 | 5 | def onsig(signal) 6 | Curses.close_screen 7 | exit signal 8 | end 9 | 10 | def place_string(y, x, string) 11 | Curses.setpos(y, x) 12 | Curses.addstr(string) 13 | end 14 | 15 | def cycle_index(index) 16 | (index + 1) % 5 17 | end 18 | 19 | %w[HUP INT QUIT TERM].each do |sig| 20 | unless trap(sig, "IGNORE") == "IGNORE" # previous handler 21 | trap(sig) {|s| onsig(s) } 22 | end 23 | end 24 | 25 | Curses.init_screen 26 | Curses.nl 27 | Curses.noecho 28 | Curses.curs_set 0 29 | srand 30 | 31 | xpos, ypos = {}, {} 32 | x_range = 2..(Curses.cols - 3) 33 | y_range = 2..(Curses.lines - 3) 34 | (0..4).each do |i| 35 | xpos[i], ypos[i] = rand(x_range), rand(y_range) 36 | end 37 | 38 | i = 0 39 | loop do 40 | x, y = rand(x_range), rand(y_range) 41 | 42 | place_string(y, x, ".") 43 | 44 | place_string(ypos[i], xpos[i], "o") 45 | 46 | i = cycle_index(i) 47 | place_string(ypos[i], xpos[i], "O") 48 | 49 | i = cycle_index(i) 50 | place_string(ypos[i] - 1, xpos[i], "-") 51 | place_string(ypos[i], xpos[i] - 1, "|.|") 52 | place_string(ypos[i] + 1, xpos[i], "-") 53 | 54 | i = cycle_index(i) 55 | place_string(ypos[i] - 2, xpos[i], "-") 56 | place_string(ypos[i] - 1, xpos[i] - 1, "/ \\") 57 | place_string(ypos[i], xpos[i] - 2, "| O |") 58 | place_string(ypos[i] + 1, xpos[i] - 1, "\\ /") 59 | place_string(ypos[i] + 2, xpos[i], "-") 60 | 61 | i = cycle_index(i) 62 | place_string(ypos[i] - 2, xpos[i], " ") 63 | place_string(ypos[i] - 1, xpos[i] - 1, " ") 64 | place_string(ypos[i], xpos[i] - 2, " ") 65 | place_string(ypos[i] + 1, xpos[i] - 1, " ") 66 | place_string(ypos[i] + 2, xpos[i], " ") 67 | 68 | xpos[i], ypos[i] = x, y 69 | 70 | Curses.refresh 71 | sleep(0.5) 72 | end 73 | -------------------------------------------------------------------------------- /sample/screen.rb: -------------------------------------------------------------------------------- 1 | require "curses" 2 | 3 | screen = Curses::Screen.new(STDOUT, STDIN, "xterm") 4 | screen.set_term 5 | 6 | Curses.addstr("Hit any key") 7 | Curses.refresh 8 | Curses.getch 9 | Curses.close_screen 10 | -------------------------------------------------------------------------------- /sample/view.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "curses" 4 | include Curses 5 | 6 | unless ARGV.size == 1 7 | puts "usage: #{$0} file" 8 | exit 9 | end 10 | 11 | begin 12 | data_lines = File.readlines(ARGV[0]) 13 | rescue 14 | raise "cannot open file: #{ARGV[0]}" 15 | end 16 | 17 | init_screen 18 | #keypad(stdscr, true) 19 | nonl 20 | cbreak 21 | noecho 22 | #scrollok(stdscr, true) 23 | 24 | lptr = 0 25 | loop do 26 | lines.times do |i| 27 | setpos(i, 0) 28 | #clrtoeol 29 | addstr(data_lines[lptr + i] || "") 30 | end 31 | refresh 32 | 33 | explicit = false 34 | n = 0 35 | c = nil 36 | loop do 37 | c = getch 38 | if c =~ /[0-9]/ 39 | n = 10 * n + c.to_i 40 | explicit = true 41 | else 42 | break 43 | end 44 | end 45 | 46 | n = 1 if !explicit && n == 0 47 | 48 | case c 49 | when "n" #when KEY_DOWN 50 | i = 0 51 | n.times do 52 | if lptr + lines < data_lines.size 53 | lptr += 1 54 | else 55 | break 56 | end 57 | i += 1 58 | end 59 | #wscrl(i) 60 | when "p" #when KEY_UP 61 | i = 0 62 | n.times do 63 | if lptr > 0 64 | lptr -= 1 65 | else 66 | break 67 | end 68 | i += 1 69 | end 70 | #wscrl(-i) 71 | when "q" 72 | break 73 | end 74 | end 75 | 76 | close_screen 77 | -------------------------------------------------------------------------------- /sample/view2.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "curses" 4 | 5 | 6 | # A curses based file viewer. 7 | class FileViewer 8 | 9 | # Create a new FileViewer and view the file. 10 | def initialize(filename) 11 | @data_lines = [] 12 | @screen = nil 13 | @top = nil 14 | init_curses 15 | load_file(filename) 16 | interact 17 | end 18 | 19 | # Perform the curses setup. 20 | def init_curses 21 | Curses.init_screen 22 | Curses.nonl 23 | Curses.cbreak 24 | Curses.noecho 25 | 26 | @screen = Curses.stdscr 27 | 28 | @screen.scrollok(true) 29 | #@screen.keypad(true) 30 | end 31 | 32 | # Load the file into memory and 33 | # put the first part on the curses display. 34 | def load_file(filename) 35 | @data_lines = File.readlines(filename).map(&:chomp) 36 | @top = 0 37 | @data_lines[0..@screen.maxy-1].each_with_index do |line, idx| 38 | @screen.setpos(idx, 0) 39 | @screen.addstr(line) 40 | end 41 | @screen.setpos(0, 0) 42 | @screen.refresh 43 | rescue 44 | raise "cannot open file '#{filename}' for reading" 45 | end 46 | 47 | 48 | # Scroll the display up by one line. 49 | def scroll_up 50 | if @top > 0 51 | @screen.scrl(-1) 52 | @top -= 1 53 | str = @data_lines[@top] 54 | if str 55 | @screen.setpos(0, 0) 56 | @screen.addstr(str) 57 | end 58 | return true 59 | else 60 | return false 61 | end 62 | end 63 | 64 | # Scroll the display down by one line. 65 | def scroll_down 66 | if @top + @screen.maxy < @data_lines.length 67 | @screen.scrl(1) 68 | @top += 1 69 | str = @data_lines[@top + @screen.maxy - 1] 70 | if str 71 | @screen.setpos(@screen.maxy - 1, 0) 72 | @screen.addstr(str) 73 | end 74 | return true 75 | else 76 | return false 77 | end 78 | end 79 | 80 | # Allow the user to interact with the display. 81 | # This uses Emacs-like keybindings, and also 82 | # vi-like keybindings as well, except that left 83 | # and right move to the beginning and end of the 84 | # file, respectively. 85 | def interact 86 | loop do 87 | result = true 88 | c = Curses.getch 89 | case c 90 | when Curses::KEY_DOWN, Curses::KEY_CTRL_N, "j" 91 | result = scroll_down 92 | when Curses::KEY_UP, Curses::KEY_CTRL_P, "k" 93 | result = scroll_up 94 | when Curses::KEY_NPAGE, " " 95 | (@screen.maxy - 1).times do |i| 96 | if !scroll_down && i == 0 97 | result = false 98 | break 99 | end 100 | end 101 | when Curses::KEY_PPAGE 102 | (@screen.maxy - 1).times do |i| 103 | if !scroll_up && i == 0 104 | result = false 105 | break 106 | end 107 | end 108 | when Curses::KEY_LEFT, Curses::KEY_CTRL_T, "h" 109 | while scroll_up 110 | end 111 | when Curses::KEY_RIGHT, Curses::KEY_CTRL_B, "l" 112 | while scroll_down 113 | end 114 | when "q" 115 | break 116 | else 117 | @screen.setpos(0, 0) 118 | @screen.addstr("[unknown key `#{Curses.keyname(c)}'=#{c}] ") 119 | end 120 | if !result 121 | Curses.beep 122 | end 123 | @screen.setpos(0, 0) 124 | end 125 | Curses.close_screen 126 | end 127 | end 128 | 129 | 130 | # If we are being run as a main program... 131 | if __FILE__ == $0 132 | unless ARGV.size == 1 133 | puts "usage: #{$0} file" 134 | exit 135 | end 136 | 137 | viewer = FileViewer.new(ARGV[0]) 138 | end 139 | --------------------------------------------------------------------------------