├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── shard.yml ├── spec ├── ncurses_spec.cr └── spec_helper.cr └── src ├── lib_ncurses.cr ├── ncurses.cr └── ncurses ├── version.cr └── window.cr /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/ 2 | /libs/ 3 | /.crystal/ 4 | /.shards/ 5 | 6 | 7 | # Libraries don't need dependency lock 8 | # Dependencies will be locked in application that uses them 9 | /shard.lock 10 | 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: crystal 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Joakim Reinert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ncurses [![Build Status](https://travis-ci.org/jreinert/ncurses-crystal.svg?branch=master)](https://travis-ci.org/jreinert/ncurses-crystal) 2 | 3 | NCurses Bindings for Crystal 4 | 5 | ## Installation 6 | 7 | 8 | Add this to your application's `shard.yml`: 9 | 10 | ```yaml 11 | dependencies: 12 | ncurses: 13 | github: jreinert/ncurses-crystal 14 | ``` 15 | 16 | 17 | ## Usage 18 | 19 | 20 | ```crystal 21 | require "ncurses" 22 | ``` 23 | 24 | 25 | TODO: Write usage instructions here 26 | 27 | 28 | ## Contributing 29 | 30 | 1. Fork it ( https://github.com/jreinert/ncurses/fork ) 31 | 2. Create your feature branch (git checkout -b my-new-feature) 32 | 3. Commit your changes (git commit -am 'Add some feature') 33 | 4. Push to the branch (git push origin my-new-feature) 34 | 5. Create a new Pull Request 35 | 36 | ## Contributors 37 | 38 | - (https://github.com/jreinert) Joakim Reinert - creator, maintainer 39 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: ncurses 2 | version: 0.1.0 3 | 4 | authors: 5 | - Joakim Reinert 6 | 7 | license: MIT 8 | -------------------------------------------------------------------------------- /spec/ncurses_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe NCurses do 4 | end 5 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/ncurses" 3 | -------------------------------------------------------------------------------- /src/lib_ncurses.cr: -------------------------------------------------------------------------------- 1 | @[Link("ncurses")] 2 | 3 | lib LibNCurses 4 | type Window = Void* 5 | alias FileDescriptor = Int32 6 | 7 | ATTR_SHIFT = 8_u32 8 | $color_pairs = COLOR_PAIRS : Int32 9 | $colors = COLORS : Int32 10 | 11 | enum Attribute 12 | NORMAL = 1_u32 - 1_u32 13 | ATTRIBUTES = 1_u32 << (0_u32 + ATTR_SHIFT) 14 | CHARTEXT = (1_u32 << (0_u32 + ATTR_SHIFT)) - 1_u32 15 | COLOR = ((1_u32 << 8_u32) - 1_u32) << (0_u32 + ATTR_SHIFT) 16 | STANDOUT = 1_u32 << (8_u32 + ATTR_SHIFT) 17 | UNDERLINE = 1_u32 << (9_u32 + ATTR_SHIFT) 18 | REVERSE = 1_u32 << (10_u32 + ATTR_SHIFT) 19 | BLINK = 1_u32 << (11_u32 + ATTR_SHIFT) 20 | DIM = 1_u32 << (12_u32 + ATTR_SHIFT) 21 | BOLD = 1_u32 << (13_u32 + ATTR_SHIFT) 22 | ALTCHARSET = 1_u32 << (14_u32 + ATTR_SHIFT) 23 | INVIS = 1_u32 << (15_u32 + ATTR_SHIFT) 24 | PROTECT = 1_u32 << (16_u32 + ATTR_SHIFT) 25 | HORIZONTAL = 1_u32 << (17_u32 + ATTR_SHIFT) 26 | LEFT = 1_u32 << (18_u32 + ATTR_SHIFT) 27 | LOW = 1_u32 << (19_u32 + ATTR_SHIFT) 28 | RIGHT = 1_u32 << (20_u32 + ATTR_SHIFT) 29 | TOP = 1_u32 << (21_u32 + ATTR_SHIFT) 30 | VERTICAL = 1_u32 << (22_u32 + ATTR_SHIFT) 31 | ITALIC = 1_u32 << (23_u32 + ATTR_SHIFT) 32 | end 33 | 34 | enum Key 35 | DOWN = 0o402 36 | UP = 0o403 37 | LEFT = 0o404 38 | RIGHT = 0o405 39 | HOME = 0o406 40 | BACKSPACE = 0o407 41 | F1 = 0o410 + 1 42 | F2 = 0o410 + 2 43 | F3 = 0o410 + 3 44 | F4 = 0o410 + 4 45 | F5 = 0o410 + 5 46 | F6 = 0o410 + 6 47 | F7 = 0o410 + 7 48 | F8 = 0o410 + 8 49 | F9 = 0o410 + 9 50 | F10 = 0o410 + 10 51 | F11 = 0o410 + 11 52 | F12 = 0o410 + 12 53 | ENTER = 0o527 54 | end 55 | 56 | fun initscr : Window 57 | fun endwin 58 | fun raw 59 | fun noecho 60 | fun wtimeout(window : Window, timeout : Int32) 61 | fun waddch(window : Window, chr : LibC::Char) 62 | fun mvwaddch(window : Window, row : Int32, col : Int32, chr : LibC::Char) 63 | fun wprintw(window : Window, format : UInt8*, ...) 64 | fun wgetch(window : Window) : Int32 65 | fun mvwprintw(window : Window, row : Int32, col : Int32, format : UInt8*, ...) 66 | fun wrefresh(window : Window) 67 | fun keypad(window : Window, value : Bool) 68 | fun wattr_on(window : Window, attribute : Attribute, unused : Void*) 69 | fun wattr_off(window : Window, attribute : Attribute, unused : Void*) 70 | fun getmaxy(window : Window) : Int32 71 | fun getmaxx(window : Window) : Int32 72 | fun notimeout(window : Window, value : Bool) 73 | fun wmove(window : Window, row : Int32, col : Int32) : Int32 74 | fun nodelay(window : Window, value : Bool) 75 | fun wclear(window : Window) 76 | fun wbkgd(window : Window, chtype : UInt32) 77 | fun newwin(height : Int32, width : Int32, row : Int32, col : Int32) : Window 78 | fun start_color : Int32 79 | fun has_colors : Bool 80 | fun can_change_color : Bool 81 | fun init_color(slot : Int16, red : Int16, green : Int16, blue : Int16) : Int32 82 | fun init_pair(slot : Int16, foreground : Int16, background : Int16) : Int32 83 | fun wcolor_set(window : Window, slot : Int16, unused : Void*) : Int32 84 | fun cbreak : Int32 85 | fun nocbreak : Int32 86 | fun flushinp 87 | fun curs_set(visibility : Int32) : Int32 88 | fun move(x : Int32, y : Int32) : Int32 89 | fun addstr(str : LibC::Char*) : Int32 90 | fun addch(chr : LibC::Char) 91 | fun refresh 92 | fun clear 93 | end 94 | -------------------------------------------------------------------------------- /src/ncurses.cr: -------------------------------------------------------------------------------- 1 | require "./lib_ncurses" 2 | require "./ncurses/*" 3 | 4 | lib Locale 5 | # LC_CTYPE is probably 0 (at least in glibc) 6 | LC_CTYPE = 0 7 | fun setlocale(category : Int32, locale : LibC::Char*) : LibC::Char* 8 | end 9 | 10 | Locale.setlocale(Locale::LC_CTYPE, "") 11 | 12 | module NCurses 13 | alias Key = LibNCurses::Key 14 | 15 | # Possible integer result values 16 | ERR = -1 17 | OK = 0 18 | 19 | # Default colors 20 | BLACK = 0 21 | RED = 1 22 | GREEN = 2 23 | YELLOW = 3 24 | BLUE = 4 25 | MAGENTA = 5 26 | CYAN = 6 27 | WHITE = 7 28 | 29 | NCURSES_ATTR_SHIFT = 8 30 | 31 | enum Cursor 32 | INVISIBLE = 0 33 | VISIBLE = 1 34 | HI_VIZ = 2 35 | end 36 | 37 | class Window 38 | def initialize(@window : LibNCurses::Window) 39 | end 40 | 41 | def to_unsafe 42 | @window 43 | end 44 | end 45 | 46 | def lines 47 | if stdscr = @@stdscr 48 | LibNCurses.getmaxy(stdscr.to_unsafe) 49 | end 50 | end 51 | 52 | def cols 53 | if stdscr = @@stdscr 54 | LibNCurses.getmaxx(stdscr.to_unsafe) 55 | end 56 | end 57 | 58 | def no_echo 59 | LibNCurses.noecho 60 | end 61 | 62 | def raw 63 | LibNCurses.raw 64 | end 65 | 66 | def crmode 67 | if ERR == LibNCurses.nocbreak 68 | raise "Unable to switch to crmode" 69 | end 70 | end 71 | 72 | def nocrmode 73 | if ERR == LibNCurses.cbreak 74 | raise "Unable to switch out of crmode" 75 | end 76 | end 77 | 78 | def cbreak 79 | if ERR == LibNCurses.cbreak 80 | raise "Unable to switch to cbreak" 81 | end 82 | end 83 | 84 | def nocbreak 85 | if ERR == LibNCurses.nocbreak 86 | raise "Unable to switch out of cbreak" 87 | end 88 | end 89 | 90 | def flush_input 91 | LibNCurses.flushinp 92 | end 93 | 94 | def curs_set(visibility) 95 | if ERR == LibNCurses.curs_set(visibility) 96 | raise "Unable to set cursor visibility" 97 | end 98 | end 99 | 100 | def setpos(x, y) 101 | move(x, y) 102 | end 103 | 104 | def move(x, y) 105 | if ERR == LibNCurses.move(x, y) 106 | raise "Unable to set cursor position" 107 | end 108 | end 109 | 110 | def addstr(str) 111 | if ERR == LibNCurses.addstr(str) 112 | raise "Unable to add string" 113 | end 114 | end 115 | 116 | def refresh 117 | LibNCurses.refresh 118 | end 119 | 120 | def clear 121 | LibNCurses.clear 122 | end 123 | 124 | def has_colors? 125 | LibNCurses.has_colors 126 | end 127 | 128 | def can_change_color? 129 | LibNCurses.can_change_color 130 | end 131 | 132 | def color_pairs 133 | LibNCurses.color_pairs 134 | end 135 | 136 | def colors 137 | LibNCurses.colors 138 | end 139 | 140 | def start_color 141 | if ERR == LibNCurses.start_color 142 | raise "Unable to start color mode" 143 | end 144 | end 145 | 146 | def init_color(slot, red, green, blue) 147 | if ERR == LibNCurses.init_color(slot.to_i16, red.to_i16, green.to_i16, blue.to_i16) 148 | raise "Unable to init color" 149 | end 150 | end 151 | 152 | def init_color_pair(slot, foreground, background) 153 | if ERR == LibNCurses.init_pair(slot.to_i16, foreground.to_i16, background.to_i16) 154 | raise "Unable to init color pair" 155 | end 156 | end 157 | 158 | def init 159 | return if @@initialized 160 | scr = LibNCurses.initscr 161 | raise "couldn't initialize ncurses" unless scr 162 | @@initialized = true 163 | @@stdscr = Window.new(scr) 164 | end 165 | 166 | def stdscr 167 | scr = @@stdscr 168 | raise "ncurses not yet initialized" unless scr 169 | scr 170 | end 171 | 172 | def new_term(terminal, out_io, in_io) 173 | screen = LibNCurses.newterm(terminal, out_io.fd, in_io.fd) 174 | puts "foo" 175 | @@initialized = true 176 | @@stdscr = Window.new(screen) 177 | end 178 | 179 | def term=(screen) 180 | LibNCurses.set_term(screen) 181 | @@stdscr = Window.new(screen) 182 | end 183 | 184 | def end_win 185 | return unless @@initialized 186 | LibNCurses.endwin 187 | @@initialized = false 188 | end 189 | 190 | def color_pair(n) 191 | ncurses_bits(n, 0) & a_color 192 | end 193 | 194 | private def ncurses_bits(mask, shift) 195 | mask << (shift + NCURSES_ATTR_SHIFT) 196 | end 197 | 198 | private def a_color 199 | ncurses_bits((1_u32 << 8) - 1, 0) 200 | end 201 | 202 | extend self 203 | end 204 | -------------------------------------------------------------------------------- /src/ncurses/version.cr: -------------------------------------------------------------------------------- 1 | module NCurses 2 | VERSION = "0.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /src/ncurses/window.cr: -------------------------------------------------------------------------------- 1 | require "../ncurses" 2 | 3 | module NCurses 4 | class Window 5 | ATTRIBUTES = [ 6 | :normal, :attributes, :chartext, :color, :standout, :underline, :reverse, 7 | :blink, :dim, :bold, :altcharset, :invis, :protect, :horizontal, :left, 8 | :low, :right, :top, :vertical, :italic, 9 | ] 10 | 11 | def initialize(height = nil, width = nil, y = 0, x = 0) 12 | max_height, max_width = NCurses.stdscr.max_x, NCurses.stdscr.max_y 13 | initialize(LibNCurses.newwin(height || max_height, width || max_width, y, x)) 14 | end 15 | 16 | def max_x 17 | LibNCurses.getmaxx(self) 18 | end 19 | 20 | def max_y 21 | LibNCurses.getmaxy(self) 22 | end 23 | 24 | def max_dimensions 25 | {x: max_x, y: max_y} 26 | end 27 | 28 | private macro attr_mask(attributes) 29 | mask = LibNCurses::Attribute::NORMAL 30 | 31 | attributes.each do |attribute| 32 | mask |= case(attribute) 33 | {% for attribute in ATTRIBUTES %} 34 | when {{attribute}} 35 | LibNCurses::Attribute::{{attribute.upcase.id}} 36 | {% end %} 37 | else 38 | raise "unknown attribute #{attribute}" 39 | end 40 | end 41 | 42 | mask 43 | end 44 | 45 | def attr_on(*attributes) 46 | attr_on(attributes.to_a) 47 | end 48 | 49 | def attr_on(attributes : Array(Symbol?)) 50 | LibNCurses.wattr_on(self, attr_mask(attributes), Pointer(Void).null) 51 | end 52 | 53 | def attr_off(*attributes) 54 | attr_off(attributes.to_a) 55 | end 56 | 57 | def attr_off(attributes : Array(Symbol?)) 58 | LibNCurses.wattr_off(self, attr_mask(attributes), Pointer(Void).null) 59 | end 60 | 61 | def with_attr(*attributes, &block) 62 | with_attr(attributes.to_a, &block) 63 | end 64 | 65 | def with_attr(attributes : Array(Symbol?)) 66 | attr_on(attributes) 67 | begin 68 | yield 69 | ensure 70 | attr_off(attributes) 71 | end 72 | end 73 | 74 | def current_color 75 | @current_color ||= 0 76 | end 77 | 78 | def set_color(slot) 79 | LibNCurses.wcolor_set(self, slot.to_i16, nil) 80 | @current_color = slot 81 | end 82 | 83 | def with_color(slot) 84 | old_color = current_color 85 | set_color(slot) 86 | yield 87 | ensure 88 | set_color(old_color || 0) 89 | end 90 | 91 | def current_background 92 | @current_background ||= 0 93 | end 94 | 95 | def set_background(color_pair : Int32) 96 | background = NCurses.color_pair(color_pair) 97 | LibNCurses.wbkgd(self, background) 98 | @current_background = background 99 | end 100 | 101 | def get_char 102 | LibNCurses.wgetch(self) 103 | end 104 | 105 | def no_timeout 106 | LibNCurses.nodelay(self, false) 107 | LibNCurses.notimeout(self, true) 108 | end 109 | 110 | def no_delay 111 | LibNCurses.notimeout(self, false) 112 | LibNCurses.nodelay(self, true) 113 | end 114 | 115 | def timeout=(value) 116 | LibNCurses.notimeout(self, false) 117 | LibNCurses.wtimeout(self, value) 118 | end 119 | 120 | def add_char(chr, position = nil) 121 | if position 122 | LibNCurses.mvwaddch(self, position[0], position[1], chr) 123 | else 124 | LibNCurses.waddch(self, chr) 125 | end 126 | end 127 | 128 | def print(message, position = nil) 129 | if position 130 | LibNCurses.mvwprintw(self, position[0], position[1], message) 131 | else 132 | LibNCurses.wprintw(self, message) 133 | end 134 | end 135 | 136 | def move(x, y) 137 | if ERR == LibNCurses.wmove(self, x, y) 138 | raise "Unable to set cursor position" 139 | end 140 | end 141 | 142 | def clear 143 | LibNCurses.wclear(self) 144 | end 145 | 146 | def refresh 147 | LibNCurses.wrefresh(self) 148 | end 149 | 150 | def on_input 151 | no_timeout 152 | char = get_char 153 | case (char) 154 | when 27 155 | on_special_input { |key, mod| yield(key, mod) } 156 | when 10 157 | yield(:return, nil) 158 | when 32..127 159 | yield(char.chr, nil) 160 | end 161 | end 162 | 163 | private def on_special_input 164 | no_delay 165 | char = get_char 166 | if char == -1 167 | yield(:escape, nil) 168 | elsif char == 91 169 | case (get_char) 170 | when 65 then yield(:up, nil) 171 | when 66 then yield(:down, nil) 172 | end 173 | else 174 | yield(char.chr, :alt) 175 | end 176 | end 177 | end 178 | end 179 | --------------------------------------------------------------------------------