diff options
-rw-r--r-- | build.rs | 4 | ||||
-rw-r--r-- | src/components.rs | 91 | ||||
-rw-r--r-- | src/components/kernel.rs (renamed from src/ui/components/kernel.rs) | 39 | ||||
-rw-r--r-- | src/components/processes.rs (renamed from src/ui/components/processes.rs) | 71 | ||||
-rw-r--r-- | src/components/utilities.rs (renamed from src/ui/components/utilities.rs) | 0 | ||||
-rw-r--r-- | src/lib.rs | 20 | ||||
-rw-r--r-- | src/main.rs | 113 | ||||
-rw-r--r-- | src/state.rs (renamed from src/ui/state.rs) | 12 | ||||
-rw-r--r-- | src/terminal.rs (renamed from src/ui/terminal.rs) | 0 | ||||
-rw-r--r-- | src/terminal/cells.rs | 1060 | ||||
-rw-r--r-- | src/terminal/keys.rs (renamed from src/ui/terminal/keys.rs) | 0 | ||||
-rw-r--r-- | src/terminal/position.rs (renamed from src/ui/terminal/position.rs) | 87 | ||||
-rw-r--r-- | src/text_processing.rs (renamed from src/ui/text_processing.rs) | 0 | ||||
-rw-r--r-- | src/text_processing/grapheme_clusters.rs (renamed from src/ui/text_processing/grapheme_clusters.rs) | 2 | ||||
-rw-r--r-- | src/text_processing/line_break.rs (renamed from src/ui/text_processing/line_break.rs) | 4 | ||||
-rw-r--r-- | src/text_processing/tables.rs (renamed from src/ui/text_processing/tables.rs) | 4 | ||||
-rw-r--r-- | src/text_processing/types.rs (renamed from src/ui/text_processing/types.rs) | 0 | ||||
-rw-r--r-- | src/text_processing/wcwidth.rs (renamed from src/ui/text_processing/wcwidth.rs) | 0 | ||||
-rw-r--r-- | src/types.rs (renamed from src/ui/types.rs) | 2 | ||||
-rw-r--r-- | src/ui.rs | 123 | ||||
-rw-r--r-- | src/ui/components.rs | 356 | ||||
-rw-r--r-- | src/ui/terminal/cells.rs | 657 |
22 files changed, 1334 insertions, 1311 deletions
@@ -5,10 +5,10 @@ use std::io::BufReader; use std::path::PathBuf; use std::process::Command; -include!("src/ui/text_processing/types.rs"); +include!("src/text_processing/types.rs"); fn main() -> Result<(), std::io::Error> { - let mod_path = PathBuf::from("src/ui/text_processing/tables.rs"); + let mod_path = PathBuf::from("src/text_processing/tables.rs"); if mod_path.exists() { eprintln!( "{} already exists, delete it if you want to replace it.", diff --git a/src/components.rs b/src/components.rs new file mode 100644 index 0000000..efb500a --- /dev/null +++ b/src/components.rs @@ -0,0 +1,91 @@ +/* + * bb + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of bb. + * + * bb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with bb. If not, see <http://www.gnu.org/licenses/>. + */ + +/*! +Components are ways to handle application data. They can draw on the terminal and receive events, but also do other stuff as well. (For example, see the `notifications` module.) + +See the `Component` Trait for more details. +*/ + +use crate::state::*; +use crate::terminal::*; +mod utilities; +pub use utilities::*; + +mod kernel; +pub use kernel::*; +pub mod processes; +pub use processes::*; + +use std::collections::{HashMap, VecDeque}; +use std::fmt; +use std::fmt::{Debug, Display}; + +use super::{Key, UIEvent}; +// The upper and lower boundary char. const HORZ_BOUNDARY: char = '─'; +/// The left and right boundary char. const VERT_BOUNDARY: char = '│'; + +pub type ShortcutMap = HashMap<&'static str, Key>; +pub type ShortcutMaps = HashMap<String, ShortcutMap>; + +/// Types implementing this Trait can draw on the terminal and receive events. +/// If a type wants to skip drawing if it has not changed anything, it can hold some flag in its +/// fields (eg self.dirty = false) and act upon that in their `draw` implementation. +pub trait Component: Display + Debug + Send { + fn draw( + &mut self, + grid: &mut CellBuffer, + area: Area, + dirty_areas: &mut VecDeque<Area>, + tick: bool, + ); + fn process_event(&mut self, event: &mut UIEvent, ui_mode: &mut UIMode); + fn is_dirty(&self) -> bool { + true + } + fn set_dirty(&mut self); + fn get_shortcuts(&self) -> ShortcutMaps { + Default::default() + } +} + +pub fn create_box(grid: &mut CellBuffer, area: Area) { + if !is_valid_area!(area) { + return; + } + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + + /*for x in get_x(upper_left)..=get_x(bottom_right) { + grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY); + grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY); + grid[(x, get_y(bottom_right))].set_ch('▒'); + grid[(x, get_y(bottom_right))].set_fg(Color::Byte(240)); + } + */ + + for y in get_y(upper_left)..=get_y(bottom_right) { + //grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY); + grid[(get_x(bottom_right), y)] + .set_ch('▒') + .set_fg(Color::Byte(240)); + } +} diff --git a/src/ui/components/kernel.rs b/src/components/kernel.rs index 90f05a4..21748d9 100644 --- a/src/ui/components/kernel.rs +++ b/src/components/kernel.rs @@ -129,7 +129,7 @@ impl KernelMetrics { pos_inc(upper_left, (x_offset, 2 + (i % MAX_CPU_ROWS))), bottom_right, ), - false, + None, ) } else { /* add padding */ @@ -143,7 +143,7 @@ impl KernelMetrics { pos_inc(upper_left, (x_offset, 2 + i % MAX_CPU_ROWS)), bottom_right, ), - false, + None, ); write_string_to_grid( "CPU", @@ -152,17 +152,12 @@ impl KernelMetrics { Color::Default, Attr::Bold, ((x, y), bottom_right), - false, + None, ) }; x += 2; /* Calculate percentages for the cpu usage bar */ - let busy_length = (cpu_stat.user_time + cpu_stat.system_time) - .saturating_sub(self.cpu_stat[i].user_time + self.cpu_stat[i].system_time); - let iowait_length = cpu_stat - .iowait_time - .saturating_sub(self.cpu_stat[i].iowait_time); let bar_length: usize = ((cpu_stat .busy_time() .saturating_sub(self.cpu_stat[i].busy_time()) @@ -185,7 +180,7 @@ impl KernelMetrics { Color::Byte(240), Attr::Default, ((x + _x_offset, y), bottom_right), - false, + None, ); _x_offset += 1; } @@ -198,7 +193,7 @@ impl KernelMetrics { Color::Byte(235), Attr::Default, ((x + _x_offset, y), bottom_right), - false, + None, ); _x_offset += 1; @@ -287,7 +282,7 @@ impl KernelMetrics { Color::Byte(240), Attr::Default, (pos_inc(upper_left, (x + 2, y_offset)), bottom_right), - false, + None, ); x += cutoff; } else { @@ -298,7 +293,7 @@ impl KernelMetrics { Color::Byte(235), Attr::Default, (pos_inc(upper_left, (x + 2, y_offset)), bottom_right), - false, + None, ); x += 1; } @@ -311,7 +306,7 @@ impl KernelMetrics { Color::Byte(235), Attr::Default, (pos_inc(upper_left, (x + 2, y_offset)), bottom_right), - false, + None, ); } } @@ -342,7 +337,7 @@ impl Component for KernelMetrics { Color::Default, Attr::Bold, area, - false, + None, ); let (x, y) = write_string_to_grid( &self.os_type, @@ -351,7 +346,7 @@ impl Component for KernelMetrics { Color::Default, Attr::Default, ((x + 2, y), bottom_right), - false, + None, ); write_string_to_grid( &self.kernel, @@ -360,7 +355,7 @@ impl Component for KernelMetrics { Color::Default, Attr::Default, ((x + 2, y), bottom_right), - false, + None, ); self.dirty = false; } @@ -394,7 +389,7 @@ impl Component for KernelMetrics { (get_x(bottom_right) - uptime.len(), get_y(upper_left)), bottom_right, ), - false, + None, ); if !tick { @@ -434,7 +429,7 @@ impl Component for KernelMetrics { Color::Default, Attr::Bold, (upper_left, bottom_right), - false, + None, ); for (i, (tag, s, fg_color, bg_color)) in get_cpu_times(&old_cpu_stat, &self.cpu_stat[0]) @@ -448,7 +443,7 @@ impl Component for KernelMetrics { Color::Default, Attr::Default, (pos_inc(upper_left, (0, i + 1)), bottom_right), - false, + None, ); let padding = 6 - s.len(); @@ -461,7 +456,7 @@ impl Component for KernelMetrics { bg_color, Attr::Default, ((x + 2 + padding, y), bottom_right), - false, + None, ); cpu_column_width = std::cmp::max(tag.len() + s.len() + 4, cpu_column_width); } @@ -478,7 +473,7 @@ impl Component for KernelMetrics { Color::Default, Attr::Bold, (upper_left, bottom_right), - false, + None, ); let loadavgs = get_loadavg(); for (i, avg) in loadavgs.into_iter().enumerate() { @@ -497,7 +492,7 @@ impl Component for KernelMetrics { Color::Default, Attr::Default, (pos_inc(upper_left, (0, i + 1)), bottom_right), - false, + None, ); grid[pos_inc(upper_left, (0, i + 1))].set_attrs(Attr::Bold); grid[pos_inc(upper_left, (1, i + 1))].set_attrs(Attr::Bold); diff --git a/src/ui/components/processes.rs b/src/components/processes.rs index b290ba2..93cc835 100644 --- a/src/ui/components/processes.rs +++ b/src/components/processes.rs @@ -389,7 +389,7 @@ impl ProcessList { pos_inc(upper_left!(box_area), (2, 4 + y)), bottom_right!(box_area), ), - false, + None, ); y += 1; } @@ -582,8 +582,7 @@ impl ProcessList { bg_color, Attr::Default, (pos_inc(upper_left, (0, y_offset + 2)), bottom_right), - false, - ); + None,); if p.state == State::Running { grid[pos_inc( upper_left, @@ -610,7 +609,7 @@ impl ProcessList { bg_color, Attr::Default, (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), - false, + None, ); let (x, _) = write_string_to_grid( bin, @@ -623,7 +622,7 @@ impl ProcessList { bg_color, Attr::Default, (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), - false, + None, ); write_string_to_grid( rest, @@ -632,7 +631,7 @@ impl ProcessList { bg_color, Attr::Default, (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), - false, + None, ); change_colors( grid, @@ -667,8 +666,7 @@ impl ProcessList { bg_color, Attr::Default, (pos_inc(upper_left, (0, y_offset + 2)), bottom_right), - false, - ); + None,); if p.state == State::Running { grid[pos_inc( upper_left, @@ -700,7 +698,7 @@ impl ProcessList { bg_color, Attr::Default, (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), - false, + None, ); write_string_to_grid( rest, @@ -709,7 +707,7 @@ impl ProcessList { bg_color, Attr::Default, (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), - false, + None, ); change_colors( grid, @@ -832,7 +830,7 @@ impl Component for ProcessList { ), bottom_right!(area), ), - false, + None, ); upper_left = pos_inc(upper_left, (0, 2)); } @@ -852,7 +850,7 @@ impl Component for ProcessList { ), bottom_right!(area), ), - false, + None, ); upper_left = pos_inc(upper_left, (0, 2)); } @@ -893,8 +891,7 @@ impl Component for ProcessList { Color::White, Attr::Default, (pos_inc(upper_left, (0, 1)), bottom_right), - false, - ); + None,); if self.mode.is_active(SEARCH_ACTIVE | FILTER_ACTIVE) && self.mode.input == Active { grid[(x - 1, y)].set_fg(Color::White); grid[(x - 1, y)].set_bg(Color::Black); @@ -917,7 +914,7 @@ impl Component for ProcessList { Color::Byte(26), // DodgerBlue3 Attr::Bold, ((x, y), pos_inc(bottom_right, (0, 1))), - false, + None, ); x = _x; } @@ -930,7 +927,7 @@ impl Component for ProcessList { Color::Byte(88), // DarkRed Attr::Bold, ((x, y), pos_inc(bottom_right, (0, 1))), - false, + None, ); x = _x; } @@ -942,7 +939,7 @@ impl Component for ProcessList { Color::Byte(172), // Orange3 Attr::Bold, ((x, y), pos_inc(bottom_right, (0, 1))), - false, + None, ); x = _x; } @@ -954,7 +951,7 @@ impl Component for ProcessList { Color::Green, Attr::Bold, ((x, y), pos_inc(bottom_right, (0, 1))), - false, + None, ); x = _x; } @@ -966,7 +963,7 @@ impl Component for ProcessList { Color::Byte(8), // Grey Attr::Bold, ((x, y), pos_inc(bottom_right, (0, 1))), - false, + None, ); x = _x; } @@ -979,7 +976,7 @@ impl Component for ProcessList { Color::Byte(13), // Fuschia Attr::Bold, ((x, y), pos_inc(bottom_right, (0, 1))), - false, + None, ); } } @@ -1073,8 +1070,7 @@ impl Component for ProcessList { bg_color, Attr::Default, (pos_inc(upper_left, (0, y_offset + 2)), bottom_right), - false, - ); + None,); if p.state == State::Running { grid[pos_inc( upper_left, @@ -1101,7 +1097,7 @@ impl Component for ProcessList { bg_color, Attr::Default, (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), - false, + None, ); let (x, _) = write_string_to_grid( bin, @@ -1114,7 +1110,7 @@ impl Component for ProcessList { bg_color, Attr::Default, (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), - false, + None, ); write_string_to_grid( rest, @@ -1123,7 +1119,7 @@ impl Component for ProcessList { bg_color, Attr::Default, (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), - false, + None, ); change_colors( grid, @@ -1157,8 +1153,7 @@ impl Component for ProcessList { bg_color, Attr::Default, (pos_inc(upper_left, (0, y_offset + 2)), bottom_right), - false, - ); + None,); if p.state == State::Running { grid[pos_inc( upper_left, @@ -1190,7 +1185,7 @@ impl Component for ProcessList { bg_color, Attr::Default, (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), - false, + None, ); write_string_to_grid( rest, @@ -1199,7 +1194,7 @@ impl Component for ProcessList { bg_color, Attr::Default, (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), - false, + None, ); change_colors( grid, @@ -1232,7 +1227,7 @@ impl Component for ProcessList { ), bottom_right!(area), ), - false, + None, ); dirty_areas.push_back(( pos_inc( @@ -1259,7 +1254,7 @@ impl Component for ProcessList { ), bottom_right!(area), ), - false, + None, ); dirty_areas.push_back(( pos_inc( @@ -1317,7 +1312,7 @@ impl Component for ProcessList { ), bottom_right!(area), ), - false, + None, ); dirty_areas.push_back((pos_inc(upper_left, (0, 1)), set_y(bottom_right, y))); } else if self.mode.is_active(LOCATE_ACTIVE) { @@ -1336,7 +1331,7 @@ impl Component for ProcessList { ), bottom_right!(area), ), - false, + None, ); dirty_areas.push_back((pos_inc(upper_left, (0, 1)), set_y(bottom_right, y))); } @@ -1364,7 +1359,7 @@ impl Component for ProcessList { Color::Default, Attr::Default, (pos_inc(upper_left!(box_area), (x, 1 + y)), bottom_right), - false, + None, ); y += 1; } @@ -1404,7 +1399,7 @@ impl Component for ProcessList { pos_inc(upper_left!(box_area), (1, 1)), bottom_right!(box_area), ), - false, + None, ); write_string_to_grid( &format!("send {signal}", signal = signal_fmt,), @@ -1416,7 +1411,7 @@ impl Component for ProcessList { pos_inc(upper_left!(box_area), (1, 2)), bottom_right!(box_area), ), - false, + None, ); } @@ -1857,7 +1852,7 @@ fn get(data: &mut ProcessData, follow_pid: Option<Pid>, sort: Sort) -> Vec<Proce rtime: process.rtime, state: process.state, cmd_line: CmdLineString(process.cmd_line), - username: UserString(crate::ui::username(process.uid)), + username: UserString(crate::username(process.uid)), is_thread: false, }; @@ -1895,7 +1890,7 @@ fn get(data: &mut ProcessData, follow_pid: Option<Pid>, sort: Sort) -> Vec<Proce rtime: thread.rtime, state: thread.state, cmd_line: CmdLineString(thread.cmd_line), - username: UserString(crate::ui::username(thread.uid)), + username: UserString(crate::username(thread.uid)), is_thread: true, }; diff --git a/src/ui/components/utilities.rs b/src/components/utilities.rs index bb124cb..bb124cb 100644 --- a/src/ui/components/utilities.rs +++ b/src/components/utilities.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..434f703 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,20 @@ +/* + * bb + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of bb. + * + * bb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with bb. If not, see <http://www.gnu.org/licenses/>. + */ diff --git a/src/main.rs b/src/main.rs index ef3e467..f26f23b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,14 +17,6 @@ * along with bb. If not, see <http://www.gnu.org/licenses/>. */ -//! -//! This crate contains the frontend stuff of the application. The application entry way on -//! `src/bin.rs` creates an event loop and passes input to the `ui` module. -//! -//! The mail handling stuff is done in the `bb` crate which includes all backend needs. The -//! split is done to theoretically be able to create different frontends with the same innards. -//! - extern crate crossbeam; extern crate nix; extern crate signal_hook; @@ -36,8 +28,101 @@ use libc::c_int; use std::io::Error; use std::time::Duration; -mod ui; -use ui::*; +//#[allow(dead_code)] +mod text_processing; +pub use crate::text_processing::*; +#[macro_use] +mod types; +pub use crate::types::*; + +#[macro_use] +mod terminal; +pub use crate::terminal::*; + +pub mod state; +pub use crate::state::*; + +pub mod components; +pub use crate::components::*; +pub use crate::username::*; +pub mod username { + use libc; + use std::ptr::null_mut; + /* taken from whoami-0.1.1 */ + fn getpwuid(pw_uid: u32, buffer: &mut [i8; 16384]) -> Option<libc::passwd> { + let mut pwentp = null_mut(); + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd" + ))] + { + let mut pwent = libc::passwd { + pw_name: null_mut(), + pw_passwd: null_mut(), + pw_uid, + pw_gid: 0, + pw_change: 0, + pw_class: null_mut(), + pw_gecos: null_mut(), + pw_dir: null_mut(), + pw_shell: null_mut(), + pw_expire: 0, + }; + unsafe { + libc::getpwuid_r(pw_uid, &mut pwent, buffer, 16384, &mut pwentp); + } + + if pwentp.is_null() { + None + } else { + Some(pwent) + } + } + #[cfg(target_os = "linux")] + { + let mut pwent = libc::passwd { + pw_name: null_mut(), + pw_passwd: null_mut(), + pw_uid, + pw_gid: 0, + pw_gecos: null_mut(), + pw_dir: null_mut(), + pw_shell: null_mut(), + }; + + unsafe { + libc::getpwuid_r(pw_uid, &mut pwent, buffer.as_mut_ptr(), 16384, &mut pwentp); + } + if pwentp.is_null() { + None + } else { + Some(pwent) + } + } + } + + pub fn username(uid: u32) -> String { + let mut buffer = [0i8; 16384]; // from the man page + let pwent = getpwuid(uid, &mut buffer); + + let string; + unsafe { + string = match pwent { + None => uid.to_string(), + Some(p) => ::std::ffi::CStr::from_ptr(p.pw_name) + .to_str() + .unwrap_or_else(|_| "") + .to_string(), + } + } + + string + } +} fn notify(signals: &[c_int]) -> Result<crossbeam::channel::Receiver<c_int>, Error> { let (s, r) = bounded(100); @@ -65,19 +150,19 @@ fn main() -> Result<(), Error> { let signal_recvr = notify(signals)?; /* Create the application State */ - let mut state = State::new(); + let mut state = UIState::new(); let receiver = state.receiver(); let window = Box::new(Window::new( - Box::new(ui::components::KernelMetrics::new()), - Box::new(ui::components::ProcessList::new()), + Box::new(components::KernelMetrics::new()), + Box::new(components::ProcessList::new()), )); state.register_component(window); state.render(); state.redraw(true); - /* Keep track of the input mode. See ui::UIMode for details */ + /* Keep track of the input mode. See UIMode for details */ 'main: loop { /* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */ select! { diff --git a/src/ui/state.rs b/src/state.rs index c1ee2d6..dbce7b9 100644 --- a/src/ui/state.rs +++ b/src/state.rs @@ -43,7 +43,7 @@ pub enum UIMode { Input, } -type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>; +pub type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>; struct InputHandler { rx: Receiver<bool>, @@ -74,7 +74,7 @@ impl InputHandler { /// A State object to manage and own components and components of the UI. `State` is responsible for /// managing the terminal -pub struct State { +pub struct UIState { cols: usize, rows: usize, @@ -88,20 +88,20 @@ pub struct State { pub mode: UIMode, } -impl Drop for State { +impl Drop for UIState { fn drop(&mut self) { // When done, restore the defaults to avoid messing with the terminal. self.switch_to_main_screen(); } } -impl Default for State { +impl Default for UIState { fn default() -> Self { Self::new() } } -impl State { +impl UIState { pub fn new() -> Self { /* Create a channel to communicate with other threads. The main process is the sole receiver. * */ @@ -122,7 +122,7 @@ impl State { _stdout.lock(); let stdout = AlternateScreen::from(_stdout.into_raw_mode().unwrap()); - let mut s = State { + let mut s = UIState { cols, rows, grid: CellBuffer::new(cols, rows, Cell::with_char(' ')), diff --git a/src/ui/terminal.rs b/src/terminal.rs index df95db1..df95db1 100644 --- a/src/ui/terminal.rs +++ b/src/terminal.rs diff --git a/src/terminal/cells.rs b/src/terminal/cells.rs new file mode 100644 index 0000000..4d40aab --- /dev/null +++ b/src/terminal/cells.rs @@ -0,0 +1,1060 @@ +/* + * bb + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of bb. + * + * bb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License |