summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs9
-rw-r--r--src/util.rs25
-rw-r--r--src/widgets.rs3
-rw-r--r--src/widgets/log.rs75
-rw-r--r--src/widgets/menu.rs80
-rw-r--r--src/widgets/prompt.rs68
6 files changed, 254 insertions, 6 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 5a86bdf..15cd5ff 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,9 +1,9 @@
use std::char;
-use std::ffi::{self, CStr};
use std::io;
-use libc;
mod bindings;
+mod util;
+pub mod widgets;
// #[cfg(test)]
// mod tests {
@@ -184,9 +184,6 @@ pub fn get_line() -> Option<String> {
if ptr.is_null() {
None
} else {
- let bytes = unsafe { CStr::from_ptr(ptr).to_bytes() };
- let res = Some(String::from_utf8(bytes.to_vec()).unwrap());
- unsafe { libc::free(ptr as *mut ffi::c_void); }
- res
+ unsafe { Some(util::string_from_utf8_charp(ptr)) }
}
}
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..8bcc028
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,25 @@
+use std::ffi::{self, CStr};
+use std::ptr;
+use libc;
+
+/// Consumes and frees the input pointer, and assumes it points to
+/// a nul-terminated C-string that contains valid UTF8.
+pub unsafe fn string_from_utf8_charp(p: *mut i8) -> String {
+ let bytes = CStr::from_ptr(p).to_bytes();
+ let res = String::from_utf8(bytes.to_vec()).unwrap();
+ libc::free(p as *mut ffi::c_void);
+ res
+}
+
+pub fn strdup(s: &str) -> *mut i8 {
+ unsafe {
+ let p: *mut i8 = malloc(s.len() + 1);
+ ptr::copy_nonoverlapping(s.as_ptr(), p as *mut u8, s.len());
+ *p.add(s.len()) = 0;
+ p
+ }
+}
+
+pub unsafe fn malloc<T>(count: usize) -> *mut T {
+ libc::malloc(count * std::mem::size_of::<T>()) as *mut T
+}
diff --git a/src/widgets.rs b/src/widgets.rs
new file mode 100644
index 0000000..d8c8e2e
--- /dev/null
+++ b/src/widgets.rs
@@ -0,0 +1,3 @@
+pub mod log;
+pub mod prompt;
+pub mod menu;
diff --git a/src/widgets/log.rs b/src/widgets/log.rs
new file mode 100644
index 0000000..8bd240a
--- /dev/null
+++ b/src/widgets/log.rs
@@ -0,0 +1,75 @@
+use std::ptr;
+
+use crate::bindings;
+
+pub struct Log {
+ ptr: *mut bindings::Logwidget,
+}
+
+pub struct LogBuilder<'a> {
+ lefttop: (u32, u32),
+ size: (u32, u32),
+ title: Option<&'a str>,
+ timestamps: bool,
+}
+
+impl<'a> LogBuilder<'a> {
+ pub fn add_title<'s: 'a>(&'a mut self, title: &'s str)
+ -> &'a mut LogBuilder<'a> {
+ self.title = Some(title);
+ self
+ }
+
+ pub fn set_timestamps(&'a mut self, timestamps: bool)
+ -> &'a mut LogBuilder<'a> {
+ self.timestamps = timestamps;
+ self
+ }
+
+ pub fn create(&self) -> Log {
+ Log {
+ ptr: unsafe {
+ bindings::lgw_make(
+ self.lefttop.0 as i32, self.lefttop.1 as i32,
+ self.size.0 as i32, self.size.1 as i32,
+ match self.title {
+ None => ptr::null(),
+ Some(s) => s.as_ptr() as *const i8,
+ },
+ self.timestamps)
+ }
+ }
+ }
+}
+
+impl Log {
+ pub fn new<'a>(lefttop: (u32, u32), size: (u32, u32)) -> LogBuilder<'a> {
+ LogBuilder {
+ lefttop, size,
+ title: None,
+ timestamps: false,
+ }
+ }
+
+ pub fn redraw(&self) {
+ unsafe { bindings::lgw_redraw(self.ptr); }
+ }
+
+ pub fn add(&mut self, line: &str) {
+ unsafe { bindings::lgw_add(self.ptr, line.as_ptr() as *const i8); }
+ }
+
+ pub fn clear(&mut self) {
+ unsafe { bindings::lgw_clear(self.ptr); }
+ }
+
+ pub fn change_title(&mut self, title: &str) {
+ unsafe { bindings::lgw_changetitle(self.ptr, title.as_ptr() as *const i8); }
+ }
+}
+
+impl Drop for Log {
+ fn drop(&mut self) {
+ unsafe { bindings::lgw_destroy(self.ptr); }
+ }
+}
diff --git a/src/widgets/menu.rs b/src/widgets/menu.rs
new file mode 100644
index 0000000..fe899e3
--- /dev/null
+++ b/src/widgets/menu.rs
@@ -0,0 +1,80 @@
+use std::ffi::c_void;
+
+use crate::bindings;
+use crate::util;
+
+pub struct Menu {
+ ptr: *mut bindings::Menuwidget,
+ data: *mut bindings::Menudata,
+}
+
+pub struct Data {
+ items: Vec<Item>,
+}
+
+pub struct Item {
+ text: String,
+ hotkey: i32,
+ func: Option<unsafe extern "C" fn(index: i32)>,
+}
+
+pub enum KeyResult {
+ Handled, Ignored, Quit, Called,
+}
+
+impl Menu {
+ pub fn new(lefttop: (u32, u32), data: Data) -> Menu {
+ unsafe {
+ let mdata = util::malloc::<bindings::Menudata>(1);
+ let mitems = util::malloc::<bindings::Menuitem>(data.items.len());
+
+ (*mdata).nitems = data.items.len() as i32;
+ (*mdata).items = mitems;
+
+ for i in 0..data.items.len() {
+ (*mitems.add(i)).text = util::strdup(&data.items[i].text);
+ (*mitems.add(i)).hotkey = data.items[i].hotkey;
+ (*mitems.add(i)).func = data.items[i].func;
+ }
+
+ Menu {
+ ptr: bindings::menu_make(
+ lefttop.0 as i32, lefttop.1 as i32,
+ mdata),
+ data: mdata,
+ }
+ }
+ }
+
+ pub fn redraw(&self) {
+ unsafe { bindings::menu_redraw(self.ptr); }
+ }
+
+ pub fn handle_key(&mut self, key: i32) -> KeyResult {
+ match unsafe { bindings::menu_handlekey(self.ptr, key) } {
+ bindings::Menukey_MENUKEY_HANDLED => KeyResult::Handled,
+ bindings::Menukey_MENUKEY_IGNORED => KeyResult::Ignored,
+ bindings::Menukey_MENUKEY_QUIT => KeyResult::Quit,
+ bindings::Menukey_MENUKEY_CALLED => KeyResult::Called,
+ _ => panic!("Invalid Menukey received from termio"),
+ }
+ }
+}
+
+impl Drop for Menu {
+ fn drop(&mut self) {
+ unsafe {
+ bindings::menu_destroy(self.ptr);
+
+ let nitems = (*self.data).nitems as usize;
+ let mitems = (*self.data).items;
+
+ for i in 0..nitems {
+ libc::free((*mitems.add(i)).text as *mut c_void);
+ }
+
+ libc::free(mitems as *mut c_void);
+ libc::free(self.data as *mut c_void);
+ }
+ }
+}
diff --git a/src/widgets/prompt.rs b/src/widgets/prompt.rs
new file mode 100644
index 0000000..0690840
--- /dev/null
+++ b/src/widgets/prompt.rs
@@ -0,0 +1,68 @@
+use std::ptr;
+
+use crate::bindings;
+use crate::util;
+
+pub struct Prompt {
+ ptr: *mut bindings::Promptwidget,
+}
+
+pub struct PromptBuilder<'a> {
+ lefttop: (u32, u32),
+ width: u32,
+ title: Option<&'a str>,
+}
+
+impl<'a> PromptBuilder<'a> {
+ pub fn add_title<'s: 'a>(&'a mut self, title: &'s str)
+ -> &'a mut PromptBuilder<'a> {
+ self.title = Some(title);
+ self
+ }
+
+ pub fn create(&self) -> Prompt {
+ Prompt {
+ ptr: unsafe {
+ bindings::prw_make(
+ self.lefttop.0 as i32, self.lefttop.1 as i32,
+ self.width as i32,
+ match self.title {
+ None => ptr::null(),
+ Some(s) => s.as_ptr() as *const i8,
+ })
+ }
+ }
+ }
+}
+
+impl Prompt {
+ pub fn new<'a>(lefttop: (u32, u32), width: u32) -> PromptBuilder<'a> {
+ PromptBuilder {
+ lefttop, width,
+ title: None,
+ }
+ }
+
+ pub fn redraw(&self) {
+ unsafe { bindings::prw_redraw(self.ptr); }
+ }
+
+ pub fn handle_key(&mut self, key: i32) -> Option<String> {
+ let ptr = unsafe { bindings::prw_handlekey(self.ptr, key) };
+ if ptr.is_null() {
+ None
+ } else {
+ unsafe { Some(util::string_from_utf8_charp(ptr)) }
+ }
+ }
+
+ pub fn change_title(&mut self, title: &str) {
+ unsafe { bindings::prw_changetitle(self.ptr, title.as_ptr() as *const i8); }
+ }
+}
+
+impl Drop for Prompt {
+ fn drop(&mut self) {
+ unsafe { bindings::prw_destroy(self.ptr); }
+ }
+}