├── .gitignore
├── .spi.yml
├── .zed
└── settings.json
├── LICENSE
├── Package.swift
├── README.md
├── Sources
├── C_ncurses
│ ├── c_ncurses.h
│ └── module.modulemap
├── C_ncursesBinds
│ ├── C_ncursesBinds.c
│ └── include
│ │ └── C_ncursesBinds.h
├── C_ncursesw
│ ├── module.modulemap
│ └── ncursesw.h
├── Examples
│ ├── 01-helloWorld.swift
│ ├── 02-init.swift
│ ├── 03-print.swift
│ ├── 04-input.swift
│ ├── 05-attributes.swift
│ ├── 06-chgat.swift
│ ├── 07-window.swift
│ ├── 08-more-border-functions.swift
│ ├── 09-colors.swift
│ ├── 10-keybboard.swift
│ ├── 11-mouse.swift
│ ├── 12-temporarilyLeavingCursesMode.swift
│ ├── README.md
│ ├── main.swift
│ └── x01-color-definition.swift
└── SwiftCurses
│ ├── LinuxCompatibility.swift
│ ├── Window+attributes.swift
│ ├── Window+border.swift
│ ├── Window+clear.swift
│ ├── Window+controls.swift
│ ├── Window+dump.swift
│ ├── Window+input.swift
│ ├── Window+mouse.swift
│ ├── Window+output.swift
│ ├── Window+util.swift
│ ├── Window.swift
│ ├── color.swift
│ ├── error.swift
│ ├── initScreen.swift
│ ├── kernel.swift
│ ├── keycode.swift
│ ├── mouseEvent.swift
│ └── settings.swift
└── Tests
└── ncursesTests
└── ncursesTests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | **/.DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 | test.c
11 | a.out
12 |
--------------------------------------------------------------------------------
/.spi.yml:
--------------------------------------------------------------------------------
1 | version: 1
2 | builder:
3 | configs:
4 | - documentation_targets: [SwiftCurses]
5 |
6 |
--------------------------------------------------------------------------------
/.zed/settings.json:
--------------------------------------------------------------------------------
1 | // Folder-specific settings
2 | //
3 | // For a full list of overridable settings, and general information on folder-specific settings,
4 | // see the documentation: https://zed.dev/docs/configuring-zed#settings-files
5 | {
6 | "languages": {
7 | "Swift": {
8 | "tab_size": 4,
9 | "hard_tabs": true
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Jonas Everaert and contributers
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.7
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | var targets: [Target] = [
7 | .systemLibrary(
8 | name: "C_ncurses",
9 | pkgConfig: "ncurses",
10 | providers: [
11 | .apt(["libncurses5-dev"]),
12 | ]),
13 | .target(
14 | name: "C_ncursesBinds",
15 | dependencies: ["C_ncurses"],
16 | cSettings: [
17 | .headerSearchPath("../C_ncurses")
18 | ]),
19 | .target(
20 | name: "SwiftCurses",
21 | dependencies: [
22 | "C_ncurses",
23 | "C_ncursesBinds",
24 | ],
25 | swiftSettings: []),
26 | .executableTarget(
27 | name: "Examples",
28 | dependencies: ["SwiftCurses"],
29 | exclude: ["README.md"],
30 | swiftSettings: [.unsafeFlags([
31 | "-Xfrontend",
32 | "-warn-long-function-bodies=100",
33 | "-Xfrontend",
34 | "-warn-long-expression-type-checking=100"
35 | ])]),
36 | .testTarget(
37 | name: "ncursesTests",
38 | dependencies: ["SwiftCurses"]),
39 | ]
40 |
41 | #if os(macOS)
42 | targets
43 | .first(where: { $0.name == "SwiftCurses" })!
44 | .swiftSettings!
45 | .append(.define("SWIFTCURSES_OPAQUE"))
46 | #else
47 | targets.append(
48 | .systemLibrary(
49 | name: "C_ncursesw",
50 | pkgConfig: "ncursesw",
51 | providers: [
52 | .brew(["ncurses"]),
53 | .apt(["libncursesw5-dev"]),
54 | .yum(["ncurses-devel"])
55 | ])
56 | )
57 | targets
58 | .first(where: { $0.name == "SwiftCurses" })!
59 | .dependencies
60 | .append("C_ncursesw")
61 | #endif
62 |
63 | let package = Package(
64 | name: "SwiftCurses",
65 | products: [
66 | .library(
67 | name: "SwiftCurses",
68 | targets: ["SwiftCurses"]),
69 | .executable(
70 | name: "Examples",
71 | targets: ["Examples"])
72 | ],
73 | dependencies: [],
74 | targets: targets)
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
9 |
10 |

11 |

12 |
13 |
14 | SwiftCurses is a Swifty wrapper for ncurses.
15 |
16 | > ncurses - CRT screen handling and optimization package
17 |
18 | ## Hello World
19 |
20 | ```swift
21 | import SwiftCurses
22 |
23 | try initScreen() { scr in
24 | try scr.print("Hello world !!!")
25 | scr.refresh()
26 | try scr.getChar()
27 | }
28 | ```
29 |
30 | ## Installation
31 |
32 | [ncurses](https://invisible-island.net/ncurses#packages) must be installed on the system.
33 |
34 | In your swift package:
35 |
36 | ```swift
37 | dependencies: [
38 | .package(url: "https://github.com/jomy10/SwiftCurses.git", branch: "master")
39 | ]
40 | ```
41 |
42 | In a swift target:
43 |
44 | ```swift
45 | .target(
46 | name: "MyTarget",
47 | dependencies: ["SwiftCurses"]
48 | )
49 | ```
50 |
51 | ## Documentation / tutorials / links
52 |
53 | There is a great ncurses tutorial you can find [here](https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/),
54 | the [examples in this repository](/Sources/Examples) show the examples seen in the tutorial.
55 |
56 | NCurses documentation can be found [here](https://invisible-island.net/ncurses/man/ncurses.3x.html),
57 | though keep in mind some functions may be missing/have a different name.
58 |
59 | [ncurses info](https://invisible-island.net/ncurses/)
60 |
61 | ### Running the examples
62 |
63 | ```sh
64 | swift run Examples [name of the example (see main.swift)]
65 | ```
66 |
67 | ## Questions
68 |
69 | Feel free to ask any questions.
70 |
71 | ## Contributing
72 |
73 | Always looking at improving this library, feel free to leave suggestions/pull requests.
74 |
75 | ## TODO
76 |
77 | - [x] border: https://invisible-island.net/ncurses/man/curs_border.3x.html
78 | - [x] scr_dump: https://invisible-island.net/ncurses/man/curs_scr_dump.3x.html
79 | - https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/otherlib.html
80 | - fill in the todos
81 |
82 | ## License
83 |
84 | Since the original is licensed under the MIT license, this library is also
85 | licensed under the [MIT license](LICENSE).
86 |
--------------------------------------------------------------------------------
/Sources/C_ncurses/c_ncurses.h:
--------------------------------------------------------------------------------
1 | #define NCURSES_WIDECHAR 1
2 | #include
3 | #include
4 |
--------------------------------------------------------------------------------
/Sources/C_ncurses/module.modulemap:
--------------------------------------------------------------------------------
1 | module ncurses [system] {
2 | header "c_ncurses.h"
3 | link "ncurses"
4 | export *
5 | }
6 |
--------------------------------------------------------------------------------
/Sources/C_ncursesBinds/C_ncursesBinds.c:
--------------------------------------------------------------------------------
1 | #define NCURSES_WIDECHAR 1
2 | #include
3 | #include
4 | #include // Required for wctomb
5 | #include "include/C_ncursesBinds.h"
6 |
7 | #ifndef wget_wch
8 | int wget_wch(WINDOW *win, wint_t *ch);
9 | #endif
10 | #ifndef waddwstr
11 | int waddwstr(WINDOW *win, const wint_t *ch);
12 | #endif
13 |
14 | // Swift does not pick up the `get_wch` method
15 | inline int swift_wget_wch(WINDOW* win, wchar_t* ch) {
16 | return wget_wch(win, ch);
17 | }
18 |
19 | inline int swift_waddwstr(WINDOW* win, const wchar_t* ch) {
20 | return waddwstr(win, ch);
21 | }
22 |
23 | inline struct swift_YX swift_getbegyx(WINDOW* win) {
24 | struct swift_YX yx;
25 | getbegyx(win, yx.y, yx.x);
26 | return yx;
27 | }
28 |
29 | inline struct swift_YX swift_getmaxyx(WINDOW* win) {
30 | struct swift_YX yx;
31 | getmaxyx(win, yx.y, yx.x);
32 | return yx;
33 | }
34 |
35 | inline struct swift_YX swift_getyx(WINDOW* win) {
36 | struct swift_YX yx;
37 | getyx(win, yx.y, yx.x);
38 | return yx;
39 | }
40 |
41 | inline struct swift_YX swift_getparyx(WINDOW* win) {
42 | struct swift_YX yx;
43 | getparyx(win, yx.y, yx.x);
44 | return yx;
45 | }
46 |
47 | //============
48 | // Attributes
49 | //============
50 |
51 | const int swift_A_NORMAL = A_NORMAL;
52 | const int swift_A_STANDOUT = A_STANDOUT;
53 | const int swift_A_UNDERLINE = A_UNDERLINE;
54 | const int swift_A_REVERSE = A_REVERSE;
55 | const int swift_A_BLINK = A_BLINK;
56 | const int swift_A_DIM = A_DIM;
57 | const int swift_A_BOLD = A_BOLD;
58 | const int swift_A_PROTECT = A_PROTECT;
59 | const int swift_A_INVIS = A_INVIS;
60 | const int swift_A_ALTCHARSET = A_ALTCHARSET;
61 | const int swift_A_CHARTEXT = A_CHARTEXT;
62 | inline int swift_COLOR_PAIR(int n) {
63 | return COLOR_PAIR(n);
64 | }
65 | #ifdef A_ITALIC
66 | const int swift_A_ITALIC = A_ITALIC;
67 | #else
68 | const int swift_A_ITALIC = NCURSES_BITS(1U,23);
69 | #endif
70 |
71 | //====================
72 | // Wide char function
73 | //====================
74 |
75 | struct swift_wcharBytesReturnType wcharToBytes(wchar_t wc) {
76 | static char charBuffer[10];
77 | int len = wctomb(charBuffer, wc);
78 | return (struct swift_wcharBytesReturnType) {
79 | (int8_t*) charBuffer,
80 | len
81 | };
82 | }
83 |
84 | //======
85 | // Keys
86 | //======
87 |
88 | // fn keys
89 | inline int swift_key_f(int n) {
90 | return KEY_F(n);
91 | }
92 |
93 | // Mouse events (macros not being picked uup by swift)
94 | const mmask_t swift_button1Pressed = BUTTON1_PRESSED; // mouse button 1 down
95 | const mmask_t swift_button1Released = BUTTON1_RELEASED; // mouse button 1 up
96 | const mmask_t swift_button1Clicked = BUTTON1_CLICKED; // mouse button 1 clicked
97 | const mmask_t swift_button1DoubleClicked = BUTTON1_DOUBLE_CLICKED; // mouse button 1 double clicked
98 | const mmask_t swift_button1TripleClicked = BUTTON1_TRIPLE_CLICKED; // mouse button 1 triple clicked
99 | const mmask_t swift_button2Pressed = BUTTON2_PRESSED; // mouse button 2 down
100 | const mmask_t swift_button2Released = BUTTON2_RELEASED; // mouse button 2 up
101 | const mmask_t swift_button2Clicked = BUTTON2_CLICKED; // mouse button 2 clicked
102 | const mmask_t swift_button2DoubleClicked = BUTTON2_DOUBLE_CLICKED; // mouse button 2 double clicked
103 | const mmask_t swift_button2TripleClicked = BUTTON2_TRIPLE_CLICKED; // mouse button 2 triple clicked
104 | const mmask_t swift_button3Pressed = BUTTON3_PRESSED; // mouse button 3 down
105 | const mmask_t swift_button3Released = BUTTON3_RELEASED; // mouse button 3 up
106 | const mmask_t swift_button3Clicked = BUTTON3_CLICKED; // mouse button 3 clicked
107 | const mmask_t swift_button3DoubleClicked = BUTTON3_DOUBLE_CLICKED; // mouse button 3 double clicked
108 | const mmask_t swift_button3TripleClicked = BUTTON3_TRIPLE_CLICKED; // mouse button 3 triple clicked
109 | const mmask_t swift_button4Pressed = BUTTON4_PRESSED; // mouse button 4 down
110 | const mmask_t swift_button4Released = BUTTON4_RELEASED; // mouse button 4 up
111 | const mmask_t swift_button4Clicked = BUTTON4_CLICKED; // mouse button 4 clicked
112 | const mmask_t swift_button4DoubleClicked = BUTTON4_DOUBLE_CLICKED; // mouse button 4 double clicked
113 | const mmask_t swift_button4TripleClicked = BUTTON4_TRIPLE_CLICKED; // mouse button 4 triple clicked
114 | const mmask_t swift_buttonShift = BUTTON_SHIFT; // shift was down during button state change
115 | const mmask_t swift_buttonCtrl = BUTTON_CTRL; // control was down during button state change
116 | const mmask_t swift_buttonAlt = BUTTON_ALT; // alt was down during button state change
117 | const mmask_t swift_allMouseEvents = ALL_MOUSE_EVENTS; // report all button state changes
118 | const mmask_t swift_reportMousePosition = REPORT_MOUSE_POSITION; // report mouse movement
119 |
120 | // TODO: ACS (https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/misc.html)
121 |
--------------------------------------------------------------------------------
/Sources/C_ncursesBinds/include/C_ncursesBinds.h:
--------------------------------------------------------------------------------
1 | #ifndef _SWIFTCURSES_BINDS
2 | #define _SWIFTCURSES_BINDS
3 |
4 | #define NCURSES_WIDECHAR 1
5 | #include
6 | #include
7 |
8 | int swift_wget_wch(WINDOW* win, wchar_t* ch);
9 | int swift_waddwstr(WINDOW* win, const wchar_t* ch);
10 |
11 | struct swift_YX {
12 | int y, x;
13 | };
14 |
15 | struct swift_YX swift_getbegyx(WINDOW* win);
16 | struct swift_YX swift_getmaxyx(WINDOW* win);
17 | struct swift_YX swift_getyx(WINDOW* win);
18 | struct swift_YX swift_getparyx(WINDOW* win);
19 |
20 | //============
21 | // Attributes
22 | //============
23 |
24 | extern const int swift_A_NORMAL;
25 | extern const int swift_A_STANDOUT;
26 | extern const int swift_A_UNDERLINE;
27 | extern const int swift_A_REVERSE;
28 | extern const int swift_A_BLINK;
29 | extern const int swift_A_DIM;
30 | extern const int swift_A_BOLD;
31 | extern const int swift_A_PROTECT;
32 | extern const int swift_A_INVIS;
33 | extern const int swift_A_ALTCHARSET;
34 | extern const int swift_A_CHARTEXT;
35 | int swift_COLOR_PAIR(int n);
36 | extern const int swift_A_ITALIC;
37 |
38 | //====================
39 | // Wide char function
40 | //====================
41 |
42 | struct swift_wcharBytesReturnType {
43 | const int8_t* bytes;
44 | int len;
45 | };
46 |
47 | struct swift_wcharBytesReturnType wcharToBytes(wchar_t wc);
48 |
49 | //======
50 | // Keys
51 | //======
52 |
53 | int swift_key_f(int n);
54 |
55 | extern const mmask_t swift_button1Pressed; // mouse button 1 down
56 | extern const mmask_t swift_button1Released; // mouse button 1 up
57 | extern const mmask_t swift_button1Clicked; // mouse button 1 clicked
58 | extern const mmask_t swift_button1DoubleClicked; // mouse button 1 double clicked
59 | extern const mmask_t swift_button1TripleClicked; // mouse button 1 triple clicked
60 | extern const mmask_t swift_button2Pressed; // mouse button 2 down
61 | extern const mmask_t swift_button2Released; // mouse button 2 up
62 | extern const mmask_t swift_button2Clicked; // mouse button 2 clicked
63 | extern const mmask_t swift_button2DoubleClicked; // mouse button 2 double clicked
64 | extern const mmask_t swift_button2TripleClicked; // mouse button 2 triple clicked
65 | extern const mmask_t swift_button3Pressed; // mouse button 3 down
66 | extern const mmask_t swift_button3Released; // mouse button 3 up
67 | extern const mmask_t swift_button3Clicked; // mouse button 3 clicked
68 | extern const mmask_t swift_button3DoubleClicked; // mouse button 3 double clicked
69 | extern const mmask_t swift_button3TripleClicked; // mouse button 3 triple clicked
70 | extern const mmask_t swift_button4Pressed; // mouse button 4 down
71 | extern const mmask_t swift_button4Released; // mouse button 4 up
72 | extern const mmask_t swift_button4Clicked; // mouse button 4 clicked
73 | extern const mmask_t swift_button4DoubleClicked; // mouse button 4 double clicked
74 | extern const mmask_t swift_button4TripleClicked; // mouse button 4 triple clicked
75 | extern const mmask_t swift_buttonShift; // shift was down during button state change
76 | extern const mmask_t swift_buttonCtrl; // control was down during button state change
77 | extern const mmask_t swift_buttonAlt; // alt was down during button state change
78 | extern const mmask_t swift_allMouseEvents; // report all button state changes
79 | extern const mmask_t swift_reportMousePosition; // report mouse movement
80 |
81 | #endif
82 |
--------------------------------------------------------------------------------
/Sources/C_ncursesw/module.modulemap:
--------------------------------------------------------------------------------
1 | module ncursesw [system] {
2 | header "ncursesw.h"
3 | link "ncursesw"
4 | export *
5 | }
6 |
--------------------------------------------------------------------------------
/Sources/C_ncursesw/ncursesw.h:
--------------------------------------------------------------------------------
1 | #ifndef CCC__NCURSES_H
2 | #define CCC__NCURSES_H
3 |
4 | #include "ncursesw.h"
5 |
6 | #endif
7 |
--------------------------------------------------------------------------------
/Sources/Examples/01-helloWorld.swift:
--------------------------------------------------------------------------------
1 | //=======================================================================
2 | // Hello World Example
3 | //
4 | // Written by: Jonas Everaert
5 | // Based on: https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/helloworld.html
6 | //=======================================================================
7 |
8 | import SwiftCurses
9 |
10 | /// Example 1: The Hello World !!! Program
11 | func helloWorld() throws {
12 | try initScreen() { scr in
13 | try scr.print("Hello world !!!")
14 | scr.refresh()
15 | try scr.getChar()
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/Examples/02-init.swift:
--------------------------------------------------------------------------------
1 | //=======================================================================
2 | // Init Usage Example
3 | //
4 | // Written by: Jonas Everaert
5 | // Based on: https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/init.html
6 | //=======================================================================
7 |
8 | import SwiftCurses
9 |
10 | /// Example 2. Initialization Function Usage Example
11 | func initUsage() throws {
12 | try initScreen(
13 | settings: [.raw, .noEcho],
14 | windowSettings: [.keypad(true)]
15 | ) { scr in
16 | try scr.print("Type any character to see it in bold\n")
17 | let ch = try scr.getChar()
18 |
19 | switch ch {
20 | case .code(let code):
21 | if code == KeyCode.f(1) {
22 | try scr.print("F1 key pressed")
23 | } else {
24 | try scr.print("Function key \(code) pressed")
25 | }
26 | case .char(let char):
27 | try scr.print("The pressed key is ")
28 | try scr.withAttrs(.bold) {
29 | try scr.print(char)
30 | }
31 | }
32 |
33 | scr.refresh()
34 | try scr.getChar()
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/Examples/03-print.swift:
--------------------------------------------------------------------------------
1 | //=======================================================================
2 | // Print Example
3 | //
4 | // Written by: Jonas Everaert
5 | // Based on: https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/printw.html
6 | //=======================================================================
7 |
8 | import SwiftCurses
9 |
10 | /// Example 3. A Simple printw example
11 | func printExample() throws {
12 | let mesg = "Just a string"
13 |
14 | try initScreen(
15 | settings: [],
16 | windowSettings: []
17 | ) { scr in
18 | // `maxYX` returns a `Coordinate` struct, which can be deconstructed by turning
19 | // it into a tuple using `.tuple`
20 | let (row, col) = scr.maxYX.tuple // Get the number of rows and columns
21 | try scr.print(row: row/2, col: (col - Int32(mesg.count))/2, mesg) // print the message at the center of the screen
22 | try scr.print(row: row-2, col: 0, "This screen has \(row) rows and \(col) columns\n")
23 | try scr.print("Try resizing the window (if possible and then run this program again)")
24 | scr.refresh()
25 | try scr.getChar()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/Examples/04-input.swift:
--------------------------------------------------------------------------------
1 | //=======================================================================
2 | // Input Example
3 | //
4 | // Written by: Jonas Everaert
5 | // Based on: https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/scanw.html
6 | //=======================================================================
7 |
8 | import SwiftCurses
9 |
10 | func inputExample() throws {
11 | let mesg = "Enter a string: "
12 |
13 | try initScreen(
14 | settings: [],
15 | windowSettings: []
16 | ) { scr in
17 | let (row, col) = scr.maxYX.tuple
18 | try scr.print(row: row/2, col: (col - Int32(mesg.count))/2, mesg)
19 |
20 | let str = try scr.getStr()
21 | try scr.print(row: row - 2, col: 0, "You entered: \(str)")
22 | try scr.getChar()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/Examples/05-attributes.swift:
--------------------------------------------------------------------------------
1 | //=======================================================================
2 | // Attributes Example
3 | //
4 | // Written by: Jonas Everaert
5 | // Based on: https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/attrib.html
6 | //=======================================================================
7 |
8 | import SwiftCurses
9 | import Foundation
10 |
11 | /// Example 5. A Simple Attributes example
12 | ///
13 | /// This example will show the contents of a file, and mark any multiline
14 | /// comment (/**/) in bold (passed in as a parameter;
15 | /// `swift run SwiftCursesExamples attributes [filename]`)
16 | func attributesExample() throws {
17 | let arg: String
18 | if CommandLine.argc == 3 {
19 | arg = CommandLine.arguments[2]
20 | } else if CommandLine.argc == 2 {
21 | arg = CommandLine.arguments[1]
22 | } else {
23 | print("Usage: \(CommandLine.arguments[0]) ")
24 | return
25 | }
26 |
27 | let url = URL(fileURLWithPath: arg)
28 | let contents = try String(contentsOf: url)
29 | try initScreen(
30 | settings: [],
31 | windowSettings: []
32 | ) { scr in
33 | let (row, _) = scr.maxYX.tuple // Boundary of the screen
34 | var prev: Character = "\0"
35 |
36 | try contents.forEach { ch in
37 | var (y, x) = scr.yx.tuple // current curser position
38 | if (y == (row - 1)) { // are we at the end of the screen
39 | try scr.print("<-Press Any Key->")
40 | try scr.getChar()
41 | scr.clear() // clear the screen
42 | try scr.move(row: 0, col: 0)// start at the beginning of the screen
43 | }
44 | if prev == "/" && ch == "*" { // if it is / and * then only switch bold on
45 | scr.attrOn(.bold) // turn bold on
46 | (y, x) = scr.yx.tuple
47 | try scr.move(row: y, col: x - 1) // back up ine cursor position
48 | try scr.print("/\(ch)") // the actual printing is done here
49 | } else {
50 | try scr.print(ch)
51 | }
52 |
53 | scr.refresh()
54 | if prev == "*" && ch == "/" {
55 | scr.attrOff(.bold) // switch if off once we got * and then /
56 | }
57 |
58 | prev = ch
59 | }
60 | try scr.getChar()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Sources/Examples/06-chgat.swift:
--------------------------------------------------------------------------------
1 | //=======================================================================
2 | // Chgat Example
3 | //
4 | // Written by: Jonas Everaert
5 | // Based on: https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/attrib.html
6 | //=======================================================================
7 |
8 | import SwiftCurses
9 |
10 | /// Example 6. Chgat() Usage Example
11 | func chgatExample() throws {
12 | try initScreen(
13 | settings: [.colors],
14 | windowSettings: []
15 | ) { scr in
16 | try ColorPair.define(1, fg: Color.cyan, bg: Color.black) // init_pair in ncurses
17 | try scr.print("A Big string which i didn't care to type fully ")
18 | try scr.chgat(row: 0, col: 0, -1, .blink, color: 1)
19 | /*
20 | * First two parameters specify the position at which to start
21 | * Third parameter number of characters to update. -1 means till
22 | * end of line
23 | * Forth parameter is the normal attribute you wanted to give
24 | * to the charcter
25 | * Fifth is the color index. It is the index given during init_pair()
26 | * use 0 if you didn't want color
27 | * Sixth one is always NULL (has a default value in this implementation)
28 | */
29 | scr.refresh()
30 | try scr.getChar()
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/Sources/Examples/07-window.swift:
--------------------------------------------------------------------------------
1 | //=======================================================================
2 | // Window Example
3 | //
4 | // Written by: Jonas Everaert
5 | // Based on: https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/windows.html
6 | //=======================================================================
7 |
8 | import SwiftCurses
9 |
10 | /// Example 7. Window Border example
11 | ///
12 | /// Use arrow keys to move the window, press f1 to exit
13 | /// (simple, unoptimized example)
14 | func windowExample() throws {
15 | try initScreen(
16 | settings: [.cbreak],
17 | windowSettings: [.keypad(true)]
18 | ) { scr in
19 | let height: Int32 = 3
20 | let width: Int32 = 10
21 | let (row, col) = scr.maxYX.tuple
22 | var starty = (row - height) / 2
23 | var startx = (col - width) / 2
24 | try scr.print("Press F1 to exit")
25 | scr.refresh()
26 |
27 | // Create our custom window defined below
28 | var myWin: BorderedWindow? = try BorderedWindow(rows: height, cols: width, begin: (starty, startx), settings: [])
29 | _ = myWin // supress Swift warning
30 | var ch = try scr.getChar()
31 | var keyCode: Int32?
32 | if case .code(let code) = ch {
33 | keyCode = code
34 | }
35 | while (keyCode ?? -1) != KeyCode.f(1) {
36 | switch keyCode ?? -1 {
37 | case KeyCode.left:
38 | startx -= 1
39 | myWin = nil // destroy the old window
40 | myWin = try BorderedWindow(rows: height, cols: width, begin: (starty, startx), settings: [])
41 | case KeyCode.right:
42 | startx += 1
43 | myWin = nil
44 | myWin = try BorderedWindow(rows: height, cols: width, begin: (starty, startx), settings: [])
45 | case KeyCode.up:
46 | starty -= 1
47 | myWin = nil
48 | myWin = try BorderedWindow(rows: height, cols: width, begin: (starty, startx), settings: [])
49 | case KeyCode.down:
50 | starty += 1
51 | myWin = nil
52 | myWin = try BorderedWindow(rows: height, cols: width, begin: (starty, startx), settings: [])
53 | default:
54 | break
55 | }
56 |
57 | ch = try scr.getChar()
58 | if case .code(let code) = ch {
59 | keyCode = code
60 | } else {
61 | keyCode = nil
62 | }
63 | }
64 | }
65 | }
66 |
67 | fileprivate class BorderedWindow: ManagedWindow {
68 | override func onInit() {
69 | self.box() // 0, 0 gives the default characters, or leaving the parameters empty, for the vertical and horizontal lines
70 | self.refresh() // show the box
71 | }
72 |
73 | override func onDeinit() {
74 | // the following code is not necessary as SwiftCurses cleans up windows
75 | // completely, including setting all of its cells to " "
76 | // set all border characters to be a space
77 | // self.border(
78 | // left: " ",
79 | // right: " ",
80 | // top: " ",
81 | // bottom: " ",
82 | // topLeft: " ",
83 | // topRight: " ",
84 | // bottomLeft: " ",
85 | // bottomRight: " "
86 | // )
87 | // self.refresh()
88 | // delwin is done when this object is deallocated
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Sources/Examples/08-more-border-functions.swift:
--------------------------------------------------------------------------------
1 | //=======================================================================
2 | // Border Example
3 | //
4 | // Written by: Jonas Everaert
5 | // Based on: https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/windows.html
6 | //=======================================================================
7 |
8 | import SwiftCurses
9 |
10 | /// Example 8. More border functions
11 | func borderExample() throws {
12 | try initScreen(
13 | settings: [.colors, .cbreak, .noEcho],
14 | windowSettings: [.keypad(true)]
15 | ) { scr in
16 | try ColorPair.define(1, fg: Color.cyan, bg: Color.black)
17 |
18 | let (row, col): (Int32, Int32) = scr.maxYX.tuple
19 | // Initialize the window parameters
20 | var win = Win(row, col)
21 | try win.printParams(scr)
22 |
23 | try scr.withAttrs(.colorPair(1)) {
24 | try scr.print("Press F1 to exit")
25 | scr.refresh()
26 | }
27 |
28 | try win.createBox(scr, flag: true)
29 | var ch = try scr.getChar()
30 | var keyCode: Int32?
31 | if case .code(let code) = ch {
32 | keyCode = code
33 | }
34 | while (keyCode ?? -1) != KeyCode.f(1) {
35 | switch keyCode ?? -1 {
36 | case KeyCode.left:
37 | try win.createBox(scr, flag: false)
38 | win.startx -= 1
39 | try win.createBox(scr, flag: true)
40 | case KeyCode.right:
41 | try win.createBox(scr, flag: false)
42 | win.startx += 1
43 | try win.createBox(scr, flag: true)
44 | case KeyCode.up:
45 | try win.createBox(scr, flag: false)
46 | win.starty -= 1
47 | try win.createBox(scr, flag: true)
48 | case KeyCode.down:
49 | try win.createBox(scr, flag: false)
50 | win.starty += 1
51 | try win.createBox(scr, flag: true)
52 | default:
53 | break
54 | }
55 |
56 | ch = try scr.getChar()
57 | if case .code(let code) = ch {
58 | keyCode = code
59 | } else {
60 | keyCode = nil
61 | }
62 | }
63 | }
64 | }
65 |
66 | fileprivate struct Win {
67 | var startx: Int32
68 | var starty: Int32
69 | var height: Int32
70 | var width: Int32
71 | let border: WinBorder
72 |
73 | init(_ maxLines: Int32, _ maxCols: Int32) {
74 | self.height = 3
75 | self.width = 10
76 | self.starty = (maxLines - self.height) / 2
77 | self.startx = (maxCols - self.width) / 2
78 | self.border = WinBorder()
79 | }
80 |
81 | func printParams(_ win: some WindowProtocol) throws {
82 | #if DEBUG
83 | try win.print(row: 25, col: 0, self.startx, self.starty, self.width, self.height)
84 | win.refresh()
85 | #endif
86 | }
87 |
88 | /// - `win`: the window to draw the box on
89 | func createBox(_ win: some WindowProtocol, flag: Bool) throws {
90 | let x = self.startx
91 | let y = self.starty
92 | let w = self.width
93 | let h = self.height
94 |
95 | if flag == true {
96 | try win.addChar(row: y, col: x, self.border.tl)
97 | try win.addChar(row: y, col: x + w, self.border.tr)
98 | try win.addChar(row: y + h, col: x, self.border.bl)
99 | try win.addChar(row: y + h, col: x + w, self.border.br)
100 | try win.horLine(row: y, col: x + 1, self.border.ts, w - 1)
101 | try win.horLine(row: y + h, col: x + 1, self.border.bs, w - 1)
102 | try win.vertLine(row: y + 1, col: x, self.border.ls, h - 1)
103 | try win.vertLine(row: y + 1, col: x + w, self.border.rs, h - 1)
104 | } else {
105 | for j in y...(y + h) {
106 | for i in x...(x + w) {
107 | try win.addChar(row: j, col: i, " ")
108 | }
109 | }
110 | }
111 | }
112 | }
113 |
114 | fileprivate struct WinBorder {
115 | let ls: Character = "|"
116 | let rs: Character = "|"
117 | let ts: Character = "-"
118 | let bs: Character = "-"
119 | let tl: Character = "+"
120 | let tr: Character = "+"
121 | let bl: Character = "+"
122 | let br: Character = "+"
123 | }
124 |
--------------------------------------------------------------------------------
/Sources/Examples/09-colors.swift:
--------------------------------------------------------------------------------
1 | //=======================================================================
2 | // Colors Example
3 | //
4 | // Written by: Jonas Everaert
5 | // Based on: https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/color.html
6 | //=======================================================================
7 |
8 | import SwiftCurses
9 |
10 | /// Example 9. A Simple Color example
11 | func colorsExample() throws {
12 | try initScreen(
13 | settings: [
14 | .colors // enable colors
15 | ],
16 | windowSettings: []
17 | ) { scr in
18 | let (row, _) = scr.maxYX.tuple
19 | try ColorPair.define(1, fg: Color.red, bg: Color.black)
20 |
21 | try scr.withAttrs(.colorPair(1)) {
22 | try printInMiddle(scr, row / 2, 0, 0, "Voila !!! In color ...")
23 | }
24 | try scr.getChar()
25 | }
26 | }
27 |
28 | fileprivate func printInMiddle(_ win: some WindowProtocol, _ starty: Int32, _ startx: Int32, _ _width: Int32, _ string: String) throws {
29 | var width = _width
30 | var (y, x) = win.yx.tuple
31 | if startx != 0 {
32 | x = startx
33 | }
34 | if starty != 0 {
35 | y = starty
36 | }
37 | if width == 0 {
38 | width = 80
39 | }
40 |
41 | let length = Int32(string.count)
42 | let temp = (width - length) / 2
43 | x = startx + temp
44 | try win.print(row: y, col: x, string)
45 | win.refresh()
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/Examples/10-keybboard.swift:
--------------------------------------------------------------------------------
1 | //=======================================================================
2 | // Keyboard Example
3 | //
4 | // Written by: Jonas Everaert
5 | // Based on: https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/keys.html
6 | //=======================================================================
7 |
8 | import SwiftCurses
9 |
10 | fileprivate let WIDTH: Int32 = 30
11 | fileprivate let HEIGHT: Int32 = 10
12 |
13 | fileprivate nonisolated(unsafe) var startx: Int32 = 0
14 | fileprivate nonisolated(unsafe) var starty: Int32 = 0
15 |
16 | fileprivate let choices = [
17 | "Choice1",
18 | "Choice2",
19 | "Choice3",
20 | "Choice 4",
21 | "Exit",
22 | ]
23 | fileprivate let nChoices = Int32(choices.count)
24 |
25 | /// Example 10. A Simple Key Usage example
26 | func keyboardExample() throws {
27 | let WIDTH: Int32 = 30
28 | let HEIGHT: Int32 = 10
29 |
30 | var startx: Int32 = 0
31 | var starty: Int32 = 0
32 |
33 | var highlight: Int32 = 1
34 | var choice: Int32 = 0
35 |
36 | try initScreen(
37 | settings: [.noEcho, .cbreak],
38 | windowSettings: []
39 | ) { scr in
40 | scr.clear()
41 |
42 | startx = (80 - WIDTH) / 2
43 | starty = (24 - HEIGHT) / 2
44 |
45 | // WindowSettings.default returns [.keypad(true)], so it can be left out
46 | let menuWin = try ManagedWindow(rows: HEIGHT, cols: WIDTH, begin: (starty, startx))
47 | try scr.print(row: 0, col: 0, "Use arrow key to go up and down. Press enter to select a choice.")
48 | scr.refresh()
49 | try printMenu(menuWin, highlight)
50 | loop: while true {
51 | let c = try menuWin.getChar()
52 | // try scr.print(row: 1, col: 0, "[\(c)]")
53 | switch c {
54 | case .code(let code):
55 | switch code {
56 | case KeyCode.up:
57 | if highlight == 1 {
58 | highlight = nChoices
59 | } else {
60 | highlight -= 1
61 | }
62 | case KeyCode.down:
63 | if highlight == nChoices {
64 | highlight = 1
65 | } else {
66 | highlight += 1
67 | }
68 | default:
69 | try scr.move(row: 24, col: 0)
70 | try scr.clear(until: .endOfLine)
71 | try scr.print(row: 24, col: 0, "Function key pressed is \(code)")
72 | scr.refresh()
73 | } // switch code
74 | try printMenu(menuWin, highlight)
75 | case .char(let char):
76 | if char == "\n" { // enter pressed = user choice an option
77 | choice = highlight
78 | break loop
79 | }
80 | try scr.move(row: 24, col: 0)
81 | try scr.clear(until: .endOfLine)
82 | try scr.print(row: 24, col: 0, "Character pressed is \(char)")
83 | scr.refresh()
84 | }
85 | }
86 | try scr.print(row: 23, col: 0, "You chose choice \(choice) with choice string \(choices[Int(choice) - 1])\n")
87 | try scr.clear(until: .endOfLine)
88 | try scr.getChar()
89 | scr.refresh()
90 | }
91 | }
92 |
93 | fileprivate func printMenu(_ menuWin: ManagedWindow, _ highlight: Int32) throws {
94 | let x: Int32 = 2
95 | var y: Int32 = 2
96 | menuWin.box()
97 | for i in 0..(_ win: W, _ highlight: Int) throws {
78 | let x: Int32 = 2
79 | var y: Int32 = 2
80 | win.box(0, 0)
81 | for i in 0..= i && mouseX <= i + Int32(choices[Int(choice)].count) {
102 | if choice == nChoices - 1 {
103 | pChoice = -1
104 | } else {
105 | pChoice = Int(choice + 1)
106 | }
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/Sources/Examples/12-temporarilyLeavingCursesMode.swift:
--------------------------------------------------------------------------------
1 | //=======================================================================
2 | // Temporarily leaving curses Example
3 | //
4 | // Written by: Jonas Everaert
5 | // Based on: https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/misc.html
6 | //=======================================================================
7 |
8 | import SwiftCurses
9 |
10 | /// Example 12. Temporarily Leaving Curses Mode
11 | func temporarilyLeavingCurses() throws {
12 | try initScreen(settings: [], windowSettings: []) { scr in
13 | try scr.print("Hello world !!!\n")
14 | scr.refresh()
15 | shellMode {
16 | // do whatever you want in shell mode
17 | print("Hello from shell mode!")
18 | }
19 | try scr.print("Another String\n")
20 | scr.refresh()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/Examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | The numbers in the names of the examples represent the example number on
4 | [https://tldp.org/HOWTO/NCURSES-Programming-HOWTO](https://tldp.org/HOWTO/NCURSES-Programming-HOWTO).
5 |
6 | ## Running examples
7 |
8 | ```bash
9 | swift run Examples [name]
10 | ```
11 |
--------------------------------------------------------------------------------
/Sources/Examples/main.swift:
--------------------------------------------------------------------------------
1 | //=======================
2 | // Swift Curses Examples
3 | //=======================
4 |
5 |
6 | func main() throws {
7 | if CommandLine.arguments.count <= 1 {
8 | fatalError("Not enough arguments")
9 | // TODO: TUI for examples
10 | }
11 |
12 | let program = CommandLine.arguments[1]
13 |
14 | switch program {
15 | case "helloWorld":
16 | try helloWorld()
17 | case "init":
18 | try initUsage()
19 | case "print":
20 | try printExample()
21 | case "input":
22 | try inputExample()
23 | case "attributes":
24 | try attributesExample()
25 | case "chgat":
26 | try chgatExample()
27 | case "window":
28 | try windowExample()
29 | case "moreBorderFuncs":
30 | try borderExample()
31 | case "colors":
32 | try colorsExample()
33 | case "keyboard":
34 | try keyboardExample()
35 | case "mouse":
36 | try mouse()
37 | case "tempLeaveCurses":
38 | try temporarilyLeavingCurses()
39 | default:
40 | print("Invalid program \(program)")
41 | }
42 | }
43 |
44 | do {
45 | try main()
46 | } catch {
47 | print("Oops, that's an error:")
48 | print(error)
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/Examples/x01-color-definition.swift:
--------------------------------------------------------------------------------
1 | // Change color definition
2 | // Color.forceXTerm256Color() // temporary fix for ncurses not detecting color mode on some terminals even though they support it
3 | // Color.define(Color.red, r: 700, g: 0, b: 0)
4 |
5 | // TODO: actual example
6 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/LinuxCompatibility.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 |
3 | #if SWIFTCURSES_OPAQUE
4 | // We define NCURSES_OPAQUE
5 | public typealias WindowPointer = OpaquePointer
6 | #else
7 | public typealias WindowPointer = UnsafeMutablePointer
8 | #endif
9 |
10 | public typealias wchar_t = Int32
11 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/Window+attributes.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 | import C_ncursesBinds
3 |
4 | public enum Attribute: Sendable, Hashable {
5 | case normal
6 | case standout
7 | case underline
8 | case reverse
9 | case blink
10 | case dim
11 | case bold
12 | case protect
13 | case invisible
14 | case altCharset
15 | case chartext
16 | case colorPair(_ n: Int32)
17 | case italic
18 | }
19 |
20 | extension Attribute {
21 | @inlinable
22 | var value: Int32 {
23 | switch self {
24 | case .normal:
25 | return swift_A_NORMAL
26 | case .standout:
27 | return swift_A_STANDOUT
28 | case .underline:
29 | return swift_A_UNDERLINE
30 | case .reverse:
31 | return swift_A_REVERSE
32 | case .blink:
33 | return swift_A_BLINK
34 | case .dim:
35 | return swift_A_DIM
36 | case .bold:
37 | return swift_A_BOLD
38 | case .protect:
39 | return swift_A_PROTECT
40 | case .invisible:
41 | return swift_A_INVIS
42 | case .altCharset:
43 | return swift_A_ALTCHARSET
44 | case .chartext:
45 | return swift_A_CHARTEXT
46 | case .colorPair(let n):
47 | return swift_COLOR_PAIR(n)
48 | case .italic:
49 | return swift_A_ITALIC
50 | }
51 | }
52 | }
53 |
54 | extension [Attribute] {
55 | @usableFromInline
56 | func combine() -> Int32 {
57 | if self.count == 1 {
58 | return self[0].value
59 | } else {
60 | return self.reduce(0) { $0 | $1.value }
61 | }
62 | }
63 | }
64 |
65 | extension WindowProtocol {
66 | @inlinable
67 | public func attrOn(_ attrs: Attribute...) {
68 | self.attrOn(attrs)
69 | }
70 |
71 | @inlinable
72 | public func attrOn(_ attrs: [Attribute]) {
73 | ncurses.wattron(self.window, attrs.combine())
74 | }
75 |
76 | @inlinable
77 | public func attrSet(_ attrs: Attribute...) {
78 | ncurses.wattrset(self.window, attrs.combine())
79 | }
80 |
81 | @inlinable
82 | public func attrOff(_ attrs: Attribute...) {
83 | self.attrOff(attrs)
84 | }
85 |
86 | @inlinable
87 | public func attrOff(_ attrs: [Attribute]) {
88 | ncurses.wattroff(self.window, attrs.combine())
89 | }
90 |
91 | public func withAttrs(_ attrs: Attribute..., body: () throws -> ()) rethrows {
92 | self.attrOn(attrs)
93 |
94 | try body()
95 |
96 | // TODO: resotre previous intead of turning off
97 | self.attrOff(attrs)
98 | }
99 |
100 | @inlinable
101 | public func standEnd() {
102 | ncurses.wstandend(self.window)
103 | }
104 |
105 | @available(*, unavailable)
106 | public func attrGet() -> Int32 {
107 | // TODO
108 | return 0
109 | }
110 |
111 | @inlinable
112 | func chgat(_ n: Int32, _ attrs: [Attribute], color pair: Int16, opts: UnsafeRawPointer? = nil) {
113 | ncurses.wchgat(self.window, n, attr_t(attrs.combine()), pair, opts)
114 | }
115 |
116 | @inlinable
117 | public func chgat(row: Int32, col: Int32, _ n: Int32, _ attrs: Attribute..., color pair: Int16, opts: UnsafeRawPointer? = nil) throws {
118 | try self.move(row: row, col: col)
119 | self.chgat(n, attrs, color: pair, opts: opts)
120 | }
121 |
122 | /// change the attributes of already printed text
123 | // TODO: better documentation/more expressive
124 | // TODO: consider renaming to changeAttr
125 | @inlinable
126 | public func chgat(_ n: Int32, _ attrs: Attribute..., color pair: Int16, opts: UnsafeRawPointer? = nil) {
127 | self.chgat(n, attrs, color: pair, opts: opts)
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/Window+border.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 | import C_ncursesBinds
3 |
4 | // https://invisible-island.net/ncurses/man/curs_border.3x.html
5 | // X/Open does not define any error conditions. This implementation returns an error if the window pointer is null.
6 | extension WindowProtocol {
7 | //=========
8 | // Borders
9 | //=========
10 |
11 | // TODO: UTF-8 support
12 | @inlinable
13 | public func box(_ vertCh: Character = "\u{0}", _ horCh: Character = "\u{0}") {
14 | // X/Open does not define any error conditions. This implementation returns an error if the window pointer is null.
15 | ncurses.box(self.window, UInt32(vertCh.asciiValue!), UInt32(horCh.asciiValue!))
16 | }
17 |
18 | @inlinable
19 | public func box(_ vertCh: UInt32, _ horCh: UInt32) {
20 | // X/Open does not define any error conditions. This implementation returns an error if the window pointer is null.
21 | ncurses.box(self.window, vertCh, horCh)
22 | }
23 |
24 | // TODO: UTF-8 support
25 | /// - fatal error:
26 | /// - If the character cannot be converted to an ASCII value
27 | @inlinable
28 | public func border(
29 | // sides
30 | left ls: Character = "\u{0}",
31 | right rs: Character = "\u{0}",
32 | top ts: Character = "\u{0}",
33 | bottom bs: Character = "\u{0}",
34 | // corners
35 | topLeft tl: Character = "\u{0}",
36 | topRight tr: Character = "\u{0}",
37 | bottomLeft bl: Character = "\u{0}",
38 | bottomRight br: Character = "\u{0}"
39 | ) {
40 | self.border(
41 | left: UInt32(ls.asciiValue!),
42 | right: UInt32(rs.asciiValue!),
43 | top: UInt32(ts.asciiValue!),
44 | bottom: UInt32(bs.asciiValue!),
45 | topLeft: UInt32(tl.asciiValue!),
46 | topRight: UInt32(tr.asciiValue!),
47 | bottomLeft: UInt32(bl.asciiValue!),
48 | bottomRight: UInt32(br.asciiValue!)
49 | )
50 | }
51 |
52 | /// If any values are zero, then the default values are used
53 | public func border(
54 | // sides
55 | left ls: UInt32,
56 | right rs: UInt32,
57 | top ts: UInt32,
58 | bottom bs: UInt32,
59 | // corners
60 | topLeft tl: UInt32,
61 | topRight tr: UInt32,
62 | bottomLeft bl: UInt32,
63 | bottomRight br: UInt32
64 | ) {
65 | ncurses.wborder(self.window, ls, rs, ts, bs, tl, tr, bl, br)
66 | }
67 |
68 | @inlinable
69 | public func clearBorder() {
70 | self.border(
71 | left: " ",
72 | right: " ",
73 | top: " ",
74 | bottom: " ",
75 | topLeft: " ",
76 | topRight: " ",
77 | bottomLeft: " ",
78 | bottomRight: " "
79 | )
80 | }
81 |
82 | //=======
83 | // Lines
84 | //=======
85 |
86 | @inlinable
87 | public func horLine(_ ch: Character = "\u{0}", _ n: Int32) {
88 | self.horLine(UInt32(ch.asciiValue!), n)
89 | }
90 |
91 | public func horLine(_ ch: UInt32, _ n: Int32) {
92 | ncurses.whline(self.window, ch, n)
93 | }
94 |
95 | @inlinable
96 | public func horLine(row y: Int32, col x: Int32, _ ch: Character = "\u{0}", _ n: Int32) throws {
97 | try self.horLine(row: y, col: x, UInt32(ch.asciiValue!), n)
98 | }
99 |
100 | @inlinable
101 | public func horLine(row y: Int32, col x: Int32, _ ch: UInt32, _ n: Int32) throws {
102 | try self.move(row: y, col: x)
103 | self.horLine(ch, n)
104 | }
105 |
106 | @inlinable
107 | public func vertLine(_ ch: Character = "\u{0}", _ n: Int32) {
108 | self.vertLine(UInt32(ch.asciiValue!), n)
109 | }
110 |
111 | public func vertLine(_ ch: UInt32, _ n: Int32) {
112 | ncurses.wvline(self.window, ch, n)
113 | }
114 |
115 | @inlinable
116 | public func vertLine(row y: Int32, col x: Int32, _ ch: Character = "\u{0}", _ n: Int32) throws {
117 | try self.vertLine(row: y, col: x, UInt32(ch.asciiValue!), n)
118 | }
119 |
120 | @inlinable
121 | public func vertLine(row y: Int32, col x: Int32, _ ch: UInt32, _ n: Int32) throws {
122 | try self.move(row: y, col: x)
123 | self.vertLine(ch, n)
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/Window+clear.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 | import C_ncursesBinds
3 |
4 | /// Clear until option for `WindowProtocol.clear(until: ClearUntil)` method
5 | public enum ClearUntil: Sendable, Hashable {
6 | /// erase the current line to the right of the cursor, inclusive, to the end
7 | /// of the current line.
8 | case endOfLine
9 | /// erase from the cursor to the end of screen. That is, they erase all lines
10 | /// below the cursor in the window. Also, the current line to the right of the
11 | /// cursor, inclusive, is erased.
12 | case endOfScreen
13 | }
14 |
15 | extension WindowProtocol {
16 | /// copy blanks to every position in the window, clearing the screen
17 | @inlinable
18 | public func erase() {
19 | ncurses.werase(self.window)
20 | }
21 |
22 | /// like `erase`, but also calls `clearok`, so that the screen is cleared
23 | /// completely on the next call to `refresh` for the window and repainted
24 | /// from scratch
25 | @inlinable
26 | public func clear() {
27 | ncurses.wclear(self.window)
28 | }
29 |
30 | /// Erases the screen from the position of the current cursor, to `until`
31 | ///
32 | /// when `unitl` is `.endOfLine`, this function can throw if the cursor
33 | /// is about to wrap.
34 | @inlinable
35 | public func clear(until: ClearUntil) throws {
36 | switch until {
37 | case .endOfLine:
38 | if ncurses.wclrtoeol(self.window) == ERR {
39 | throw CursesError(.cursorAboutToWrap)
40 | }
41 | case .endOfScreen:
42 | ncurses.wclrtobot(self.window)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/Window+controls.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 | import C_ncursesBinds
3 |
4 | public enum Direction: Sendable, Hashable {
5 | case up
6 | case down
7 | case left
8 | case right
9 | }
10 |
11 | extension WindowProtocol {
12 | /// move the cursor to `row`th row and `col`th column
13 | @inlinable
14 | public func move(row: Int32, col: Int32) throws {
15 | if ncurses.wmove(self.window, row, col) == ERR {
16 | throw CursesError(.moveOutsideOfWindow)
17 | }
18 | }
19 |
20 | /// Move the cursor `x` step(s) into the specified direction
21 | @inlinable
22 | public func move(_ direction: Direction, times steps: Int32 = 1) throws {
23 | let (y, x) = self.yx.tuple
24 | switch direction {
25 | case .up: try self.move(row: y - steps, col: x)
26 | case .down: try self.move(row: y + steps, col: x)
27 | case .right: try self.move(row: y, col: x + steps)
28 | case .left: try self.move(row: y, col: x - steps)
29 | }
30 | }
31 |
32 | /// Move the cursor to the specified coordinate
33 | @inlinable
34 | public func move(to position: Coordinate) throws {
35 | try self.move(row: position.y, col: position.x)
36 | }
37 |
38 | // TODO: in some implementations, refresh might be a macro
39 | /// Refreshes the screen, drawing anything that hasn't been drawn yet
40 | @inlinable
41 | public func refresh() {
42 | // X/Open does not define any error conditions.
43 | ncurses.wrefresh(self.window)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/Window+dump.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 | import C_ncursesBinds
3 |
4 | #if canImport(Foundation)
5 | import Foundation
6 | #endif
7 |
8 | extension WindowProtocol {
9 | #if canImport(Foundation)
10 |
11 | @inlinable
12 | public func get(file: URL) throws -> WinType {
13 | guard let filePtr = file.withUnsafeFileSystemRepresentation( { fopen($0, "w") } ) else {
14 | throw CursesError(.couldNotOpenFile)
15 | }
16 | return WinType(ncurses.getwin(filePtr))
17 | }
18 |
19 | @inlinable
20 | public func put(file: URL) throws {
21 | guard let filePtr = file.withUnsafeFileSystemRepresentation( { fopen($0, "w") } ) else {
22 | throw CursesError(.couldNotOpenFile)
23 | }
24 | if ncurses.putwin(self.window, filePtr) == ERR {
25 | throw CursesError(.couldNotOpenFile)
26 | }
27 | }
28 |
29 | #else
30 |
31 | @inlinable
32 | public func get(file: UnsafePointer) -> OpaquePointer {
33 | ncurses.getwin(file)
34 | }
35 |
36 | @inlinable
37 | public func put(file: UnsafePoiter) -> Int32 {
38 | ncurses.putwin(self.window, file)
39 | }
40 |
41 | #endif
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/Window+input.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 | import C_ncursesBinds
3 |
4 | #if canImport(Foundation)
5 | import Foundation
6 | #endif
7 |
8 | // Detect system encoding -> used for wide characters
9 | let string32Encoding: String.Encoding = (1.littleEndian == 1) ? .utf32LittleEndian : .utf32BigEndian
10 |
11 | public enum WideChar: Sendable, Hashable {
12 | /// A UTF-8 characer
13 | case char(Character)
14 | /// A KeyCode
15 | case code(Int32)
16 | }
17 |
18 | func wcharToCharacter(_ wc: wchar_t) throws -> Character {
19 | #if canImport(Foundation)
20 | return withUnsafePointer(to: wc) { ptr in
21 | let data = Data(bytes: ptr, count: MemoryLayout.stride)
22 | return String(data: data, encoding: string32Encoding)!.first.unsafelyUnwrapped // should always return a valid character (todo check)
23 | }
24 | #else
25 | guard let bytes = wcharToBytes(wc) else {
26 | throw CursesError(.error)
27 | }
28 |
29 | return String(cString: bytes).first.unsafelyUnwrapped
30 | #endif
31 | }
32 |
33 | extension WindowProtocol {
34 | //=======
35 | // getch
36 | //=======
37 |
38 | /// Get a UTF-8 character from the user.
39 | ///
40 | /// - Returns:
41 | /// - a `WideChar`: this is either a key code or a UTF-8 encoded character
42 | @discardableResult
43 | public func getChar() throws -> WideChar {
44 | var c: wchar_t = wchar_t.init()
45 | return try withUnsafeMutablePointer(to: &c) { (ptr: UnsafeMutablePointer) in
46 | let returnCode = swift_wget_wch(self.window, ptr)
47 | switch (returnCode) {
48 | case ERR:
49 | throw CursesError(.getCharError)
50 | case 256: // function keys
51 | return .code(ptr.pointee)
52 | default: // OK > character
53 | return .char(try wcharToCharacter(ptr.pointee))
54 | }
55 | }
56 | }
57 |
58 | /// Get the ASCII value of the character or keypress (when using keypad mode)
59 | @discardableResult
60 | public func getCharCode() throws -> Int32 {
61 | let c = ncurses.wgetch(self.window)
62 | if c != OK {
63 | if c == ERR {
64 | throw CursesError(.timeoutWithoutData)
65 | } else if c == EINTR {
66 | throw CursesError(.interrupted)
67 | }
68 | }
69 | return c
70 | }
71 |
72 | //=======
73 | // scanw
74 | //=======
75 |
76 | // TODO: fix
77 | @available(*, unavailable)
78 | public func scan(_ format: String, _ args: UnsafeMutableRawPointer...) throws {
79 | if (withVaList(args.map { OpaquePointer($0) as CVarArg }) { valist in
80 | let cString = format.cString(using: .utf8)!
81 | return withUnsafePointer(to: cString[0]) { formatPtr in
82 | let mutFormatPtr = UnsafeMutablePointer(mutating: formatPtr)
83 | return ncurses.vwscanw(self.window, mutFormatPtr, valist)
84 | }
85 | }) == ERR {
86 | throw CursesError(.error)
87 | }
88 | }
89 |
90 | //========
91 | // getstr
92 | //========
93 |
94 | /// Read until a new line or carriage return is encountered.
95 | /// the terminating character is not included in the returned string.
96 | /// The pointer returned should be deallocated manually.
97 | @discardableResult
98 | public func getStrPtr() throws -> UnsafePointer {
99 | let c: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: Int(self.maxYX.x) + 1)
100 | let errno = ncurses.wgetstr(self.window, c)
101 | if errno != OK {
102 | if errno == ERR {
103 | throw CursesError(.timeoutWithoutData)
104 | } else if errno == KEY_RESIZE {
105 | throw CursesError(.SIGWINCH)
106 | }
107 | }
108 | return UnsafePointer(c)
109 | }
110 |
111 | @discardableResult
112 | public func getStr() throws -> String {
113 | let ptr: UnsafePointer = try self.getStrPtr()
114 | let s = String(cString: ptr)
115 | ptr.deallocate()
116 | return s
117 | }
118 |
119 | @discardableResult
120 | public func getStr(terminator: Character) throws -> String {
121 | var c = try self.getChar()
122 | var s = String()
123 | loop: while (true) {
124 | switch c {
125 | case .code(_): continue loop
126 | case .char(let char):
127 | if char == terminator {
128 | break loop
129 | }
130 | s.append(char)
131 | }
132 | c = try self.getChar()
133 | }
134 | return s
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/Window+mouse.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 | import C_ncursesBinds
3 |
4 | public enum TrafoKind: Sendable, Hashable {
5 | /// x and y are window-relative coordinates and will be converted to
6 | /// stdscr-relative coordinates
7 | case toStdScrn
8 | case toWindow
9 |
10 | @usableFromInline
11 | var rawValue: Bool {
12 | switch self {
13 | case .toStdScrn: return false
14 | case .toWindow: return true
15 | }
16 | }
17 | }
18 |
19 | extension WindowProtocol {
20 | /// The trafo function transforms a given pir of coordinates from stdscr-relative
21 | /// coordinates to coordinates relative to the given window or vice versa. The
22 | /// The resulting stdscr-relative coordinates are not always identical to window-relative coordinates due to the mechanism to
23 | /// reserve lines on top or bottom of the screen for other purposes (see the ripoffline and slk_init(3x) calls, for example).
24 | ///
25 | /// - Returns:
26 | /// - false:
27 | /// - when kind `.toStdScrn`; if the location is not inside the window
28 | /// - when kind `.toWindow`; always returns true
29 | @inlinable
30 | public func mouseTrafo(y: inout Int32, x: inout Int32, kind: TrafoKind) -> Bool {
31 | return ncurses.wmouse_trafo(self.window, &y, &x, kind.rawValue)
32 | }
33 |
34 | /// The trafo function transforms a given pir of coordinates from stdscr-relative
35 | /// coordinates to coordinates relative to the given window or vice versa. The
36 | /// The resulting stdscr-relative coordinates are not always identical to window-relative coordinates due to the mechanism to
37 | /// reserve lines on top or bottom of the screen for other purposes (see the ripoffline and slk_init(3x) calls, for example).
38 | ///
39 | /// - Returns:
40 | /// - false:
41 | /// - when kind `.toStdScrn`; if the location is not inside the window
42 | /// - when kind `.toWindow`; always returns true
43 | public func mouseTrafo(y: Int32, x: Int32, kind: TrafoKind) -> (Bool, y: Int32, x: Int32) {
44 | var (lx, ly) = (x, y)
45 | let b = ncurses.wmouse_trafo(self.window, &ly, &lx, kind.rawValue)
46 | return (b, ly, lx)
47 | }
48 |
49 | /// `wenclose`
50 | public func encloses(y: Int32, x: Int32) -> Bool {
51 | ncurses.wenclose(self.window, y, x)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/Window+output.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 | import C_ncursesBinds
3 |
4 | extension WindowProtocol {
5 | //========
6 | // printw
7 | //========
8 |
9 | @inlinable
10 | internal func printArgs(_ items: [Any], _ separator: String) throws {
11 | try self.addStr(items.map { String(describing: $0) }.joined(separator: separator))
12 | }
13 |
14 | public func print(_ items: Any..., separator: String = " ") throws {
15 | try self.printArgs(items, separator)
16 | }
17 |
18 | @inlinable
19 | public func print(row: Int32, col: Int32, _ items: Any..., separator: String = " ") throws {
20 | try self.move(row: row, col: col)
21 | try self.printArgs(items, separator)
22 | }
23 |
24 | @inlinable
25 | public func print(at pos: Coordinate, _ items: Any..., separator: String = " ") throws {
26 | try self.move(row: pos.y, col: pos.x)
27 | try self.printArgs(items, separator)
28 | }
29 |
30 | //========
31 | // addstr
32 | //========
33 |
34 | @inlinable
35 | public func addStr(_ str: String) throws {
36 | if ncurses.waddstr(self.window, str) == ERR {
37 | throw CursesError(.error, help: "https://invisible-island.net/ncurses/man/curs_addstr.3x.html#h2-RETURN-VALUE")
38 | }
39 | }
40 |
41 | @inlinable
42 | public func addStr(row: Int32, col: Int32, _ str: String) throws {
43 | try self.move(row: row, col: col)
44 | try self.addStr(str)
45 | }
46 |
47 | @inlinable
48 | public func addStr(at pos: Coordinate, _ str: String) throws {
49 | try self.addStr(row: pos.y, col: pos.x, str)
50 | }
51 |
52 | //======
53 | // adch
54 | //======
55 |
56 | public func addChar(_ ch: Character) throws {
57 | let u32 = ch.unicodeScalars.map { wchar_t(bitPattern: $0.value) } + [0] // null-terminated UTF-8 character
58 | if swift_waddwstr(
59 | self.window,
60 | u32.withUnsafeBufferPointer { $0.baseAddress! }
61 | ) == ERR {
62 | if !ncurses.is_scrollok(self.window) {
63 | throw CursesError(.unableToAddCompleteCharToScreen, help: "This may be due to adding a character to the bottom right corner: https://invisible-island.net/ncurses/man/curs_add_wch.3x.html#h2-RETURN-VALUE")
64 | } else {
65 | throw CursesError(.unableToAddCompleteCharToScreen, help: "https://invisible-island.net/ncurses/man/curs_add_wch.3x.html#h2-RETURN-VALUE")
66 | }
67 | }
68 | }
69 |
70 | /// - `mvaddch`
71 | @inlinable
72 | public func addChar(row: Int32, col: Int32, _ ch: Character) throws {
73 | try self.move(row: row, col: col)
74 | try self.addChar(ch)
75 | }
76 |
77 | @inlinable
78 | public func addChar(at pos: Coordinate, _ ch: Character) throws {
79 | try self.addChar(row: pos.y, col: pos.x, ch)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/Window+util.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 | import C_ncursesBinds
3 |
4 | extension swift_YX {
5 | @inlinable public var row: Int32 { self.y }
6 | @inlinable public var col: Int32 { self.x }
7 | /// Convert the `Coordinate` struct to a tuple of the form `(y, x)`
8 | @inlinable public var tuple: (Int32, Int32) { (self.y, self.x) }
9 | }
10 |
11 | extension C_ncursesBinds.swift_YX: Swift.Equatable, Swift.Hashable {
12 | public static func ==(lhs: Self, rhs: Self) -> Bool {
13 | lhs.x == rhs.x && lhs.y == rhs.y
14 | }
15 |
16 | public func hash(into hasher: inout Hasher) {
17 | hasher.combine(self.x)
18 | hasher.combine(self.y)
19 | }
20 | }
21 |
22 | extension C_ncursesBinds.swift_YX: Swift.CustomStringConvertible {
23 | public var description: String {
24 | "(row: \(self.row), col: \(self.col))"
25 | }
26 | }
27 |
28 | public typealias Coordinate = swift_YX
29 |
30 | extension WindowProtocol {
31 | /// Beginning coordinates
32 | public var begYX: Coordinate {
33 | return swift_getbegyx(self.window)
34 | }
35 |
36 | /// Size of the window
37 | public var maxYX: Coordinate {
38 | return swift_getmaxyx(self.window)
39 | }
40 |
41 | /// Current cursor position
42 | public var yx: Coordinate {
43 | return swift_getyx(self.window)
44 | }
45 |
46 | /// Get the position of the cursor in the parent window
47 | /// If the window is not a subwindow, nil is returned
48 | public var parentYX: Coordinate? {
49 | let yx: swift_YX = swift_getparyx(self.window)
50 | return yx.y == -1 ? nil : yx
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/Window.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 |
3 | public protocol WindowProtocol {
4 | var window: WindowPointer { get }
5 | }
6 |
7 | public struct Window: WindowProtocol {
8 | public let window: WindowPointer
9 |
10 | init(_ window: WindowPointer) {
11 | // screen is not a null pointer => we can negate some error checks in our functions
12 | self.window = window
13 | }
14 | }
15 |
16 | open class ManagedWindow: WindowProtocol {
17 | public let window: WindowPointer
18 | private var __rows: Int32? = nil
19 | private var __cols: Int32? = nil
20 | private var destroyed = false
21 |
22 | /// will not be known when loading a window from a file
23 | public var rows: Int32? { self.__rows }
24 | /// will not be known when loading a window from a file
25 | public var cols: Int32? { self.__cols }
26 |
27 | public required init(_ window: WindowPointer, rows: Int32? = nil, cols: Int32? = nil) {
28 | self.__rows = rows
29 | self.__cols = cols
30 | self.window = window
31 | self.onInit()
32 | }
33 |
34 | public convenience init(rows: Int32, cols: Int32, begin: (Int32, Int32), settings: [WindowSetting] = WindowSetting.defaultSettings) throws {
35 | let winPtr: WindowPointer = try newWindow(rows: rows, cols: cols, begin: begin, settings: settings)
36 | self.init(winPtr, rows: rows, cols: cols)
37 | }
38 |
39 | open func onInit() {}
40 | open func onDeinit() {}
41 |
42 | private func destroy() {
43 | onDeinit()
44 | if rows != nil {
45 | for r in 0.. WindowPointer {
84 | guard let win = ncurses.newwin(rows, cols, begin.0, begin.1) else {
85 | if begin.0 < 0 || begin.1 < 1 {
86 | throw CursesError(.negativeCoordinate, help: "The beginning coordinates of the window cannot be negative")
87 | } else if rows < 0 {
88 | throw CursesError(.negativeNumber, help: "The lines cannot be negative")
89 | } else if cols < 0 {
90 | throw CursesError(.negativeNumber, help: "The cols cannot be negative")
91 | } else {
92 | throw CursesError(.cannotCreateWindow)
93 | }
94 | }
95 |
96 | settings.forEach { $0.apply(win) }
97 |
98 | return win
99 | }
100 |
101 | /// Create a new window
102 | @inlinable
103 | public func newWindow(rows: Int32, cols: Int32, begin: Coordinate, settings: [WindowSetting] = WindowSetting.defaultSettings, _ body: (inout Window) -> ()) throws {
104 | try newWindow(rows: rows, cols: cols, begin: begin.tuple, settings: settings, body)
105 | }
106 |
107 | /// Create a new window
108 | public func newWindow(rows: Int32, cols: Int32, begin: (Int32, Int32), settings: [WindowSetting] = WindowSetting.defaultSettings, _ body: (inout Window) -> ()) throws {
109 | let win: WindowPointer = try newWindow(rows: rows, cols: cols, begin: begin, settings: settings)
110 | var window = Window(win)
111 | body(&window)
112 | delwin(win)
113 | }
114 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/color.swift:
--------------------------------------------------------------------------------
1 | @preconcurrency import ncurses // NOTE: preconcurrency --> we are importing constants from C, but they are imported as vars, they are concurrency safe, but the swift compiler has no way of knowing this
2 |
3 | public typealias ColorPairId = Int16
4 | public typealias ColorId = Int16
5 | public typealias ColorRGB = Int16
6 |
7 | // TODO: conform to OptionSet
8 | public struct Color: Sendable, Hashable {
9 | /// Whether the terminal can manipulate colors.
10 | ///
11 | /// This routine facilitates writing terminal-independent programs.
12 | /// For example, a programmer can use it to decide whether to use color or
13 | /// some other video attribute.
14 | public static let hasColors: Bool = has_colors()
15 |
16 | public static let black = ColorId(COLOR_BLACK)
17 | public static let red = ColorId(COLOR_RED)
18 | public static let green = ColorId(COLOR_GREEN)
19 | public static let yellow = ColorId(COLOR_YELLOW)
20 | public static let blue = ColorId(COLOR_BLUE)
21 | public static let magenta = ColorId(COLOR_MAGENTA)
22 | public static let cyan = ColorId(COLOR_CYAN)
23 | public static let white = ColorId(COLOR_WHITE)
24 |
25 | /// Whether the terminal supports color and can change their definition.
26 | ///
27 | /// This routine facilitates writing terminal-independent programs.
28 | public static let canChangeColor: Bool = can_change_color()
29 |
30 | public static let maxColors = COLORS
31 |
32 | public static func forceXTerm256Color() {
33 | setenv("TERM", "xterm-256color", 1)
34 | }
35 |
36 | public static func define(_ id: ColorId, r: ColorRGB, g: ColorRGB, b: ColorRGB) throws {
37 | if ncurses.init_color(id, r, g, b) == ERR {
38 | if !(0...1000).contains(r) {
39 | throw CursesError(
40 | .rgbValueOutOfRange,
41 | help: "red value out of range"
42 | )
43 | } else if !(0...1000).contains(g) {
44 | throw CursesError(
45 | .rgbValueOutOfRange,
46 | help: "green value out of range"
47 | )
48 | } else if !(0...1000).contains(b) {
49 | throw CursesError(
50 | .rgbValueOutOfRange,
51 | help: "blue value out of range"
52 | )
53 | } else {
54 | throw CursesError(
55 | .colorUnsupported
56 | )
57 | }
58 | }
59 | }
60 |
61 | public struct Content {
62 | public var r, g, b: ColorRGB
63 | init() {
64 | self.r = -1
65 | self.g = -1
66 | self.b = -1
67 | }
68 | }
69 |
70 | public static func content(for id: ColorId) throws -> Color.Content {
71 | var content = Content()
72 | ncurses.color_content(id, &content.r, &content.g, &content.b)
73 | if [content.r, content.g, content.b].contains(-1) {
74 | throw CursesError(.colorChangeUnsupported)
75 | }
76 | return content
77 | }
78 | }
79 |
80 | public struct ColorPair {
81 | public static let maxPairs = COLOR_PAIRS
82 |
83 | /// changes the definition of a color-pair
84 | ///
85 | /// equal to the `init_pair` subroutine
86 | public static func define(_ id: ColorPairId, fg: ColorId, bg: ColorId) throws {
87 | if ncurses.init_pair(id, fg, bg) == ERR {
88 | if !(0.. ColorPair.Content {
118 | var content = Content()
119 | ncurses.pair_content(id, &content.fg, &content.bg)
120 | if [content.fg, content.bg].contains(-1) {
121 | throw CursesError(.uninitializedColorPair)
122 | }
123 | return content
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/error.swift:
--------------------------------------------------------------------------------
1 | public enum ErrorKind: Sendable, Hashable {
2 | case halfdelayParameterOutsideOfRange
3 | /// Window pointer is NULL
4 | case cannotCreateWindow
5 | case moveOutsideOfWindow
6 | case getCharError
7 | case timeoutWithoutData
8 | case SIGWINCH
9 | case interrupted
10 | case negativeCoordinate
11 | case negativeNumber
12 | case colorTableCannotBeAllocated
13 | case colorPairOutsideOfRange
14 | case colorOutsideOfRange
15 | case rgbValueOutOfRange
16 | case colorUnsupported
17 | case colorChangeUnsupported
18 | case uninitializedColorPair
19 | case cursorAboutToWrap
20 | case mouseEventRegisterError
21 | case queueFull
22 | case couldNotOpenFile
23 | /// Terminal does not support moue as indicated by `MouseEvent.hasMouse`
24 | case noMouseSupport
25 |
26 | // add_wch
27 | case unableToAddCompleteCharToScreen
28 |
29 | /// Generic error
30 | case error
31 |
32 | public var help: String? {
33 | switch self {
34 | case .cannotCreateWindow: return "The window returned was a null pointer"
35 | case .getCharError: return "Timeout expired without having any data, or the eecution was interrupted by a signal (errno will be set to EINTR)"
36 | case .timeoutWithoutData: return "Timeout expired without having any data"
37 | case .SIGWINCH: return "SIGWINCH interrupt"
38 | case .interrupted: return "The execution was interrupted by a signal"
39 | case .colorUnsupported: return "Color.define: the terminal does not support this feature, e.g., the initialize_color capability is absent from the terminal description."
40 | case .colorChangeUnsupported: return "The terminal does not support changing color"
41 | case .uninitializedColorPair: return "The pair was not initialized using `init_pairs`"
42 | default: return nil
43 | }
44 | }
45 | }
46 |
47 | public struct CursesError: Error & CustomDebugStringConvertible, Sendable, Hashable {
48 | public let kind: ErrorKind
49 | public let help: String?
50 |
51 | @usableFromInline
52 | init(_ kind: ErrorKind, help: String? = nil) {
53 | self.kind = kind
54 | self.help = help ?? kind.help
55 | }
56 |
57 | public var debugDescription: String {
58 | "Error: \(self.kind)\n\(self.help == nil ? "" : "Help: \(self.help!)")"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/initScreen.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 | #if os(Linux)
3 | import ncursesw
4 | #endif
5 |
6 | #if canImport(Darwin)
7 | import Darwin
8 | #elseif canImport(Glibc)
9 | import Glibc
10 | #elseif canImport(Musl)
11 | import Musl
12 | #endif
13 |
14 | /// Start a new curses mode terminal
15 | ///
16 | /// - Parameters:
17 | /// - `settings`: NCurses settings
18 | /// - `body`: The paramater passed to the `body` function can be used to call ncures functions on the screen that was created
19 | public func initScreen(
20 | settings: [TermSetting] = TermSetting.defaultSettings,
21 | windowSettings: [WindowSetting] = WindowSetting.defaultSettings,
22 | _ body: (inout Window) throws -> ()
23 | ) throws {
24 | setlocale(LC_ALL, "") // support for wide chars
25 |
26 | guard let _scr = initscr() else { // start curses mode
27 | throw CursesError(.cannotCreateWindow)
28 | }
29 | // Assure cleanup on interrupt
30 | #if !os(Windows)
31 | signal(SIGINT, { _ in
32 | endwin()
33 | exit(0)
34 | })
35 | #endif
36 | defer {
37 | endwin() // end curses mode
38 | }
39 |
40 | try settings.forEach { try $0.apply() }
41 | windowSettings.forEach { $0.apply(_scr) }
42 |
43 | var scr = Window(_scr)
44 | try body(&scr)
45 | }
46 |
47 | #if !os(Windows)
48 | fileprivate nonisolated(unsafe) var originalTermios = termios()
49 | #endif
50 |
51 | /// Start a new curses mode terminal
52 | ///
53 | /// This version of the `initScreen` function supports an asynchronous body function
54 | ///
55 | /// - Parameters:
56 | /// - `settings`: NCurses settings
57 | /// - `body`: The paramater passed to the `body` function can be used to call ncures functions on the screen that was created
58 | @available(macOS 10.15.0, *)
59 | public func initScreenAsync(
60 | settings: [TermSetting] = TermSetting.defaultSettings,
61 | windowSettings: [WindowSetting] = WindowSetting.defaultSettings,
62 | _ body: @Sendable (inout Window) async throws -> ()
63 | ) async throws {
64 | setlocale(LC_ALL, "") // support fr wide chars
65 |
66 | var scr: Window? = nil
67 | #if !os(Windows)
68 | if (tcgetattr(STDIN_FILENO, &originalTermios) == -1) {
69 | // err
70 | }
71 | #endif
72 | guard let _scr = initscr() else { // start curses mode
73 | throw CursesError(.cannotCreateWindow)
74 | }
75 | // Assure cleanup on interrupt
76 | #if !os(Windows)
77 | signal(SIGINT, { _ in
78 | endwin()
79 | if (tcsetattr(STDIN_FILENO, 0, &originalTermios) == -1) {
80 | //print("error restoring termios")
81 | }
82 | exit(0)
83 | })
84 | #endif
85 | defer {
86 | endwin() // end curses mode
87 | }
88 |
89 | try settings.forEach { try $0.apply() }
90 | windowSettings.forEach { $0.apply(_scr) }
91 |
92 | scr = Window(_scr)
93 | try await body(&scr!)
94 | }
95 |
96 | // https://invisible-island.net/ncurses/man/curs_scr_dump.3x.html //
97 |
98 | @inlinable
99 | public func scrDump(filename: String) {
100 | ncurses.scr_dump(filename)
101 | }
102 |
103 | @inlinable
104 | public func scrRestore(filename: String) {
105 | ncurses.scr_restore(filename)
106 | }
107 |
108 | @inlinable
109 | public func scrInit(filename: String) {
110 | ncurses.scr_init(filename)
111 | }
112 |
113 | @inlinable
114 | public func scrSet(filename: String) {
115 | ncurses.scr_set(filename)
116 | }
117 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/kernel.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 |
3 | public enum CursorVisibility: Int32, Sendable, Hashable {
4 | case invisible = 0
5 | case normal = 1
6 | case veryVisible = 2
7 | }
8 |
9 | @inlinable
10 | public func cursorSet(_ visibility: CursorVisibility) {
11 | ncurses.curs_set(visibility.rawValue) // no error, because controlled visibility values
12 | }
13 |
14 | /// Save the program, exit it to return to shell mode. Afterwards, restore the program
15 | public func shellMode(_ body: () throws -> ()) rethrows {
16 | ncurses.def_prog_mode()
17 | ncurses.endwin()
18 | // cooked mode
19 | try body()
20 | ncurses.reset_prog_mode()
21 | ncurses.refresh()
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/keycode.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 | import C_ncursesBinds
3 |
4 | public struct KeyCode: Sendable, Hashable {
5 | /// The type of the KeyCode values
6 | public typealias type = Int32
7 |
8 | public static let `break` = KEY_BREAK // Break key
9 | public static let down = KEY_DOWN // Arrow down
10 | public static let up = KEY_UP // Arrow up
11 | public static let left = KEY_LEFT // Arrow left
12 | public static let right = KEY_RIGHT // Arrow right
13 | public static let home = KEY_HOME // Home key
14 | public static let backspace = KEY_BACKSPACE // Backspace
15 | public static let f0 = KEY_F0 // Function key zero
16 | public static let dl = KEY_DL // Delete line
17 | public static let il = KEY_IL // Insert line
18 | public static let dc = KEY_DC // Delete character
19 | public static let ic = KEY_IC // Insert char or enter insert mode
20 | public static let eic = KEY_EIC // Exit insert char mode
21 | public static let clear = KEY_CLEAR // Clear screen
22 | public static let eos = KEY_EOS // Clear to end of screen
23 | public static let eol = KEY_EOL // Clear to end of line
24 | public static let sf = KEY_SF // Scroll 1 line forward
25 | public static let sr = KEY_SR // Scroll 1 line backward (reverse)
26 | public static let npage = KEY_NPAGE // Next page
27 | public static let ppage = KEY_PPAGE // Previous page
28 | public static let stab = KEY_STAB // Set tab
29 | public static let ctab = KEY_CTAB // Clear tab
30 | public static let catab = KEY_CATAB // Clear all tabs
31 | public static let enter = KEY_ENTER // Enter or send
32 | public static let sreset = KEY_SRESET // Soft (partial) reset
33 | public static let reset = KEY_RESET // Reset or hard reset
34 | public static let print = KEY_PRINT // Print or copy
35 | public static let ll = KEY_LL // Home down or bottom (lower left)
36 | public static let a1 = KEY_A1 // Upper left of keypad
37 | public static let a3 = KEY_A3 // Upper right of keypad
38 | public static let b2 = KEY_B2 // Center of keypad
39 | public static let c1 = KEY_C1 // Lower left of keypad
40 | public static let c3 = KEY_C3 // Lower right of keypad
41 | public static let btab = KEY_BTAB // Back tab key
42 | public static let beg = KEY_BEG // Beg(inning) key
43 | public static let cancel = KEY_CANCEL // Cancel key
44 | public static let close = KEY_CLOSE // Close key
45 | public static let command = KEY_COMMAND // Cmd (command) key
46 | public static let copy = KEY_COPY // Copy key
47 | public static let create = KEY_CREATE // Create key
48 | public static let end = KEY_END // End key
49 | public static let exit = KEY_EXIT // Exit key
50 | public static let find = KEY_FIND // Find key
51 | public static let help = KEY_HELP // Help key
52 | public static let mark = KEY_MARK // Mark key
53 | public static let message = KEY_MESSAGE // Message key
54 |
55 | // Table 5.3: the keypad constants, part 1
56 | public static let mouse = KEY_MOUSE // Mouse event read
57 | public static let move = KEY_MOVE // Move key
58 | public static let next = KEY_NEXT // Next object key
59 | public static let open = KEY_OPEN // Open key
60 | public static let options = KEY_OPTIONS // Options key
61 | public static let previous = KEY_PREVIOUS // Previous object key
62 | public static let redo = KEY_REDO // Redo key
63 | public static let reference = KEY_REFERENCE // Ref(erence) key
64 | public static let refresh = KEY_REFRESH // Refresh key
65 | public static let replace = KEY_REPLACE // Replace key
66 | public static let resize = KEY_RESIZE // Screen resized
67 | public static let restart = KEY_RESTART // Restart key
68 | public static let resume = KEY_RESUME // Resume key
69 | public static let save = KEY_SAVE // Save key
70 | public static let sbeg = KEY_SBEG // Shifted beginning key
71 | public static let scancel = KEY_SCANCEL // Shifted cancel key
72 | public static let scommand = KEY_SCOMMAND // Shifted command key
73 | public static let scopy = KEY_SCOPY // Shifted copy key
74 | public static let screate = KEY_SCREATE // Shifted create key
75 | public static let sdc = KEY_SDC // Shifted delete char key
76 | public static let sdl = KEY_SDL // Shifted delete line key
77 | public static let select = KEY_SELECT // Select key
78 | public static let send = KEY_SEND // Shifted end key
79 | public static let seol = KEY_SEOL // Shifted clear line key
80 | public static let sexit = KEY_SEXIT // Shifted exit key
81 | public static let sfind = KEY_SFIND // Shifted find key
82 | public static let shelp = KEY_SHELP // Shifted help key
83 | public static let shome = KEY_SHOME // Shifted home key
84 | public static let sic = KEY_SIC // Shifted input key
85 | public static let sleft = KEY_SLEFT // Shifted left arrow key
86 | public static let smessage = KEY_SMESSAGE // Shifted message key
87 | public static let smove = KEY_SMOVE // Shifted move key
88 | public static let snext = KEY_SNEXT // Shifted next key
89 | public static let soptions = KEY_SOPTIONS // Shifted options key
90 | public static let sprevious = KEY_SPREVIOUS // Shifted prev key
91 | public static let sprint = KEY_SPRINT // Shifted print key
92 | public static let sredo = KEY_SREDO // Shifted redo key
93 | public static let sreplace = KEY_SREPLACE // Shifted replace key
94 | public static let sright = KEY_SRIGHT // Shifted right arrow
95 | // public static let sresume = KEY_SRESUME // Shifted resume key
96 | public static let ssave = KEY_SSAVE // Shifted save key
97 | public static let ssuspend = KEY_SSUSPEND // Shifted suspend key
98 | public static let sundo = KEY_SUNDO // Shifted undo key
99 | public static let suspend = KEY_SUSPEND // Suspend key
100 | public static let undo = KEY_UNDO // Undo key
101 |
102 | /// Fn keys
103 | public static func f(_ n: Int32) -> Int32 {
104 | return swift_key_f(n)
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/mouseEvent.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 | import C_ncursesBinds
3 |
4 | public enum MouseEventMask: Sendable, Hashable {
5 | case button1Pressed // mouse button 1 down
6 | case button1Released // mouse button 1 up
7 | case button1Clicked // mouse button 1 clicked
8 | case button1DoubleClicked // mouse button 1 double clicked
9 | case button1TripleClicked // mouse button 1 triple clicked
10 | case button2Pressed // mouse button 2 down
11 | case button2Released // mouse button 2 up
12 | case button2Clicked // mouse button 2 clicked
13 | case button2DoubleClicked // mouse button 2 double clicked
14 | case button2TripleClicked // mouse button 2 triple clicked
15 | case button3Pressed // mouse button 3 down
16 | case button3Released // mouse button 3 up
17 | case button3Clicked // mouse button 3 clicked
18 | case button3DoubleClicked // mouse button 3 double clicked
19 | case button3TripleClicked // mouse button 3 triple clicked
20 | case button4Pressed // mouse button 4 down
21 | case button4Released // mouse button 4 up
22 | case button4Clicked // mouse button 4 clicked
23 | case button4DoubleClicked // mouse button 4 double clicked
24 | case button4TripleClicked // mouse button 4 triple clicked
25 | case buttonShift // shift was down during button state change
26 | case buttonCtrl // control was down during button state change
27 | case buttonAlt // alt was down during button state change
28 | case allMouseEvents // report all button state changes
29 | case reportMousePosition // report mouse movement
30 |
31 | var val: mmask_t {
32 | switch self {
33 | case .button1Pressed: return swift_button1Pressed
34 | case .button1Released: return swift_button1Released
35 | case .button1Clicked: return swift_button1Clicked
36 | case .button1DoubleClicked: return swift_button1DoubleClicked
37 | case .button1TripleClicked: return swift_button1TripleClicked
38 | case .button2Pressed: return swift_button2Pressed
39 | case .button2Released: return swift_button2Released
40 | case .button2Clicked: return swift_button2Clicked
41 | case .button2DoubleClicked: return swift_button2DoubleClicked
42 | case .button2TripleClicked: return swift_button2TripleClicked
43 | case .button3Pressed: return swift_button3Pressed
44 | case .button3Released: return swift_button3Released
45 | case .button3Clicked: return swift_button3Clicked
46 | case .button3DoubleClicked: return swift_button3DoubleClicked
47 | case .button3TripleClicked: return swift_button3TripleClicked
48 | case .button4Pressed: return swift_button4Pressed
49 | case .button4Released: return swift_button4Released
50 | case .button4Clicked: return swift_button4Clicked
51 | case .button4DoubleClicked: return swift_button4DoubleClicked
52 | case .button4TripleClicked: return swift_button4TripleClicked
53 | case .buttonShift: return swift_buttonShift
54 | case .buttonCtrl: return swift_buttonCtrl
55 | case .buttonAlt: return swift_buttonAlt
56 | case .allMouseEvents: return swift_allMouseEvents
57 | case .reportMousePosition: return swift_reportMousePosition
58 | }
59 | }
60 | }
61 |
62 | public typealias MouseEvent = ncurses.MEVENT
63 |
64 | public enum MouseButton: Sendable, Hashable {
65 | case button1
66 | case button2
67 | case button3
68 | case button4
69 | }
70 |
71 | extension MouseButton {
72 | @inlinable
73 | func pressed(_ mask: mmask_t) -> Bool {
74 | switch self {
75 | case .button1: return mask & swift_button1Pressed != 0
76 | case .button2: return mask & swift_button2Pressed != 0
77 | case .button3: return mask & swift_button3Pressed != 0
78 | case .button4: return mask & swift_button4Pressed != 0
79 | }
80 | }
81 |
82 | @inlinable
83 | func released(_ mask: mmask_t) -> Bool {
84 | switch self {
85 | case .button1: return mask & swift_button1Released != 0
86 | case .button2: return mask & swift_button2Released != 0
87 | case .button3: return mask & swift_button3Released != 0
88 | case .button4: return mask & swift_button4Released != 0
89 | }
90 | }
91 |
92 | @inlinable
93 | func clicked(_ mask: mmask_t) -> Bool {
94 | switch self {
95 | case .button1: return mask & swift_button1Clicked != 0
96 | case .button2: return mask & swift_button2Clicked != 0
97 | case .button3: return mask & swift_button3Clicked != 0
98 | case .button4: return mask & swift_button4Clicked != 0
99 | }
100 | }
101 |
102 | @inlinable
103 | func doubleClicked(_ mask: mmask_t) -> Bool {
104 | switch self {
105 | case .button1: return mask & swift_button1DoubleClicked != 0
106 | case .button2: return mask & swift_button2DoubleClicked != 0
107 | case .button3: return mask & swift_button3DoubleClicked != 0
108 | case .button4: return mask & swift_button4DoubleClicked != 0
109 | }
110 | }
111 |
112 | @inlinable
113 | func tripleClicked(_ mask: mmask_t) -> Bool {
114 | switch self {
115 | case .button1: return mask & swift_button1TripleClicked != 0
116 | case .button2: return mask & swift_button2TripleClicked != 0
117 | case .button3: return mask & swift_button3TripleClicked != 0
118 | case .button4: return mask & swift_button4TripleClicked != 0
119 | }
120 | }
121 | }
122 |
123 | public enum ModifierKey: Sendable, Hashable {
124 | case shift
125 | case ctrl
126 | case alt
127 | }
128 |
129 | extension ModifierKey {
130 | @inlinable
131 | func active(_ mask: mmask_t) -> Bool {
132 | switch self {
133 | case .shift: return mask & swift_buttonShift != 0
134 | case .ctrl: return mask & swift_buttonCtrl != 0
135 | case .alt: return mask & swift_buttonAlt != 0
136 | }
137 | }
138 | }
139 |
140 | extension MouseEvent {
141 | /// Register the given mouse events
142 | public static func register(_ events: MouseEventMask...) throws {
143 | if ncurses.mousemask(events.reduce(0) { $0 | $1.val }, nil) == 0 && events.count != 0 {
144 | throw CursesError(.mouseEventRegisterError) // TODO: help
145 | }
146 | }
147 |
148 | // @inlinable
149 | // public static var hasMouse: Bool {
150 | // ncurses.swift_has_mouse()
151 | // }
152 |
153 | @inlinable
154 | public static func interval(_ n: Int32) {
155 | ncurses.mouseinterval(n)
156 | }
157 |
158 | /// Once a class of mouse events has been made visible in a window, calling the
159 | /// GetCharCode function on that window may return `KeyEvent.mouse` as an indicator
160 | /// that a mouse event has been queued. To read the event data and op the event
161 | /// off the queue, call `MouseEvent.get()`. This function will a `MouseEvent` if a
162 | /// mouse event is actually visible in the given window, otherwise it might throw
163 | /// an error.
164 | ///
165 | /// When this function returns an event, the `position.x` and `position.y` in the event
166 | /// struct coordinates will be screen-relative character-cell cooridnates. The
167 | /// event will have exactly one `MouseButton` event, which can be got with the `KeyEvent.pressed`
168 | /// and similar functions. The corresponding data in the queue is marked invalid. A
169 | /// subsequent call to `MouseEvent.get()` will retrieve the next older item from
170 | /// the queue.
171 | public static func get() -> MouseEvent? {
172 | // TODO: figure out if err because no mouse-event, or other reason
173 | // if no mouse-event in queue -> return nil instead
174 | var event = MouseEvent()
175 | if getmouse(&event) != OK {
176 | // if !Self.hasMouse {
177 | // throw CursesError(.noMouseSupport)
178 | // }
179 | return nil
180 | }
181 | return event
182 | }
183 |
184 | public mutating func unget() throws {
185 | if ncurses.ungetmouse(&self) == ERR {
186 | throw CursesError(.queueFull)
187 | }
188 | }
189 |
190 | /// button is down
191 | public func isPressed(_ btn: MouseButton) -> Bool {
192 | btn.pressed(self.bstate)
193 | }
194 |
195 | /// button is up
196 | public func isReleased(_ btn: MouseButton) -> Bool {
197 | btn.released(self.bstate)
198 | }
199 |
200 | /// button is clicked
201 | public func isClicked(_ btn: MouseButton) -> Bool {
202 | btn.clicked(self.bstate)
203 | }
204 |
205 | public func isDoubleClicked(_ btn: MouseButton) -> Bool {
206 | btn.doubleClicked(self.bstate)
207 | }
208 |
209 | public func isTripleClicked(_ btn: MouseButton) -> Bool {
210 | btn.tripleClicked(self.bstate)
211 | }
212 |
213 | /// Whether the given modifier key was down during the button state change
214 | public func modifierActive(_ modifier: ModifierKey) -> Bool {
215 | modifier.active(self.bstate)
216 | }
217 |
218 | /// even coordinates
219 | public var position: (x: Int32, y: Int32, z: Int32) {
220 | (self.x, self.y, self.z)
221 | }
222 |
223 | /// device id
224 | public var device: Int16 {
225 | self.id
226 | }
227 |
228 |
229 | }
230 |
--------------------------------------------------------------------------------
/Sources/SwiftCurses/settings.swift:
--------------------------------------------------------------------------------
1 | import ncurses
2 |
3 | // TODO: documentation > https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/init.html
4 | /// https://invisible-island.net/ncurses/man/curs_inopts.3x.html
5 | public enum TermSetting: Sendable, Hashable {
6 | case cbreak
7 | case noCbreak
8 | case echo
9 | case noEcho
10 | case nl
11 | case noNl
12 | /// Raw is similar to `cbreak`, the difference is that in raw mode, the interrupt,
13 | /// quit, suspend and flow control characters are passed through uninterreted instead
14 | /// of generating a signal
15 | case raw
16 | case noRaw
17 | case qiFlush
18 | case noQiFlush
19 | case halfdelay(_ tenths: Int32)
20 | case timeout(_ delay: Int32)
21 | case typeahead(_ fd: Int32)
22 |
23 | /// enable color support (equal to calling `start_color` in ncurses)
24 | case colors
25 | }
26 |
27 | /// https://invisible-island.net/ncurses/man/curs_inopts.3x.html
28 | public enum WindowSetting: Sendable, Hashable {
29 | /// If this option is enabled and an interrupt key is passed on the keyboard (interrupt, break, quit),
30 | /// all output in the tty driver queue will be flushed, giving the effect of faster response to the interrupt,
31 | /// but causing curses to have the wrong idea of what is on the screen. The default setting for this
32 | /// option is inherited from the tty driver settings
33 | case intrflush(Bool)
34 | case keypad(Bool)
35 | case meta(Bool)
36 | case nodelay(Bool)
37 | case notimeout(Bool)
38 | case timeout(_ delay: Int32)
39 | }
40 |
41 | extension TermSetting {
42 | public static let defaultSettings: [TermSetting] = [.cbreak, .noEcho, .colors]
43 | }
44 |
45 | extension WindowSetting {
46 | public static let defaultSettings: [WindowSetting] = [.keypad(true)]
47 | }
48 |
49 | extension TermSetting {
50 | // TODO: error handling of options
51 | @usableFromInline
52 | func apply() throws {
53 | switch self {
54 | case .cbreak: ncurses.cbreak()
55 | case .noCbreak: ncurses.nocbreak()
56 | case .echo: ncurses.echo()
57 | case .noEcho: ncurses.noecho()
58 | case .nl: ncurses.nl()
59 | case .noNl: ncurses.nonl()
60 | case .raw: ncurses.raw()
61 | case .noRaw: ncurses.noraw()
62 | case .qiFlush: ncurses.qiflush()
63 | case .noQiFlush: ncurses.noqiflush()
64 | case .halfdelay(let delay):
65 | if ncurses.halfdelay(delay) == ERR {
66 | throw CursesError(.halfdelayParameterOutsideOfRange)
67 | }
68 | case .timeout(let delay): ncurses.timeout(delay)
69 | case .typeahead(let fd): ncurses.typeahead(fd)
70 | case .colors:
71 | if ncurses.start_color() == ERR {
72 | throw CursesError(.colorTableCannotBeAllocated)
73 | }
74 | }
75 | }
76 | }
77 |
78 | extension WindowSetting {
79 | @usableFromInline
80 | func apply(_ win: WindowPointer) {
81 | switch self {
82 | case .intrflush(let bf): ncurses.intrflush(win, bf)
83 | case .keypad(let bf): ncurses.keypad(win, bf)
84 | case .meta(let bf): ncurses.meta(win, bf)
85 | case .nodelay(let bf): ncurses.nodelay(win, bf)
86 | case .notimeout(let bf): ncurses.notimeout(win, bf)
87 | case .timeout(let delay): ncurses.wtimeout(win, delay)
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Tests/ncursesTests/ncursesTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import ncurses
3 |
4 | final class ncursesTests: XCTestCase {
5 | func testExample() throws {
6 | }
7 | }
8 |
--------------------------------------------------------------------------------