diff --git a/Cargo.lock b/Cargo.lock index 52d2612cda..bfd6bd39ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -381,6 +381,7 @@ dependencies = [ "no_drop", "ota_update_client", "page_attribute_table", + "porthole", "scheduler", "simd_personality", "spawn", @@ -389,7 +390,6 @@ dependencies = [ "task_fs", "tlb_shootdown", "tsc", - "window_manager", ] [[package]] @@ -463,14 +463,6 @@ dependencies = [ name = "color" version = "0.1.0" -[[package]] -name = "compositor" -version = "0.1.0" -dependencies = [ - "framebuffer", - "shapes", -] - [[package]] name = "console" version = "0.1.0" @@ -882,16 +874,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "displayable" -version = "0.1.0" -dependencies = [ - "color", - "framebuffer", - "shapes", - "spin 0.9.4", -] - [[package]] name = "dmar" version = "0.1.0" @@ -1124,56 +1106,42 @@ dependencies = [ ] [[package]] -name = "frame_allocator" -version = "0.1.0" -dependencies = [ - "intrusive-collections", - "kernel_config", - "log", - "memory_structs", - "spin 0.9.4", - "static_assertions", -] - -[[package]] -name = "framebuffer" +name = "fps_counter" version = "0.1.0" dependencies = [ - "color", + "core2", + "device_manager", + "event_types", + "font", + "hpet", + "keycodes_ascii", "log", "memory", + "memory_structs", + "mouse", + "mouse_data", + "mpmc", "multicore_bringup", "page_attribute_table", - "shapes", + "porthole", + "scheduler", + "spawn", + "spin 0.9.4", + "stdio", + "task", "zerocopy", ] [[package]] -name = "framebuffer_compositor" +name = "frame_allocator" version = "0.1.0" dependencies = [ - "compositor", - "framebuffer", - "hashbrown", - "shapes", + "intrusive-collections", + "kernel_config", + "log", + "memory_structs", "spin 0.9.4", -] - -[[package]] -name = "framebuffer_drawer" -version = "0.1.0" -dependencies = [ - "framebuffer", - "shapes", -] - -[[package]] -name = "framebuffer_printer" -version = "0.1.0" -dependencies = [ - "font", - "framebuffer", - "shapes", + "static_assertions", ] [[package]] @@ -1682,20 +1650,14 @@ version = "0.1.0" dependencies = [ "color", "dfqueue", - "displayable", "environment", "event_types", "font", - "framebuffer", - "framebuffer_drawer", - "framebuffer_printer", "log", + "porthole", "root", - "shapes", - "text_display", + "spin 0.9.4", "tsc", - "window", - "window_manager", ] [[package]] @@ -2634,6 +2596,32 @@ dependencies = [ name = "port_io" version = "0.2.1" +[[package]] +name = "porthole" +version = "0.1.0" +dependencies = [ + "core2", + "device_manager", + "event_types", + "font", + "hpet", + "keycodes_ascii", + "log", + "memory", + "memory_structs", + "mouse", + "mouse_data", + "mpmc", + "multicore_bringup", + "page_attribute_table", + "scheduler", + "spawn", + "spin 0.9.4", + "stdio", + "task", + "zerocopy", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -3249,7 +3237,6 @@ dependencies = [ "spin 0.9.4", "stdio", "task", - "window_manager", ] [[package]] @@ -3664,8 +3651,6 @@ dependencies = [ "async_channel", "color", "cpu", - "framebuffer", - "framebuffer_drawer", "getopts", "hpet", "log", @@ -3677,7 +3662,6 @@ dependencies = [ "spin 0.9.4", "task", "unified_channel", - "window", ] [[package]] @@ -3833,19 +3817,6 @@ dependencies = [ "wasmtime", ] -[[package]] -name = "text_display" -version = "0.1.0" -dependencies = [ - "color", - "displayable", - "font", - "framebuffer", - "framebuffer_printer", - "shapes", - "spin 0.9.4", -] - [[package]] name = "text_terminal" version = "0.1.0" @@ -4453,61 +4424,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "window" -version = "0.1.0" -dependencies = [ - "color", - "dereffer", - "event_types", - "framebuffer", - "framebuffer_drawer", - "log", - "mouse", - "mpmc", - "path", - "shapes", - "spawn", - "spin 0.9.4", - "window_inner", - "window_manager", -] - -[[package]] -name = "window_inner" -version = "0.1.0" -dependencies = [ - "event_types", - "framebuffer", - "mpmc", - "shapes", -] - -[[package]] -name = "window_manager" -version = "0.1.0" -dependencies = [ - "color", - "compositor", - "event_types", - "font", - "framebuffer", - "framebuffer_compositor", - "framebuffer_drawer", - "keycodes_ascii", - "lazy_static", - "log", - "mod_mgmt", - "mouse_data", - "mpmc", - "path", - "scheduler", - "shapes", - "spawn", - "spin 0.9.4", - "window_inner", -] - [[package]] name = "x86_64" version = "0.14.9" diff --git a/applications/fps_counter/Cargo.toml b/applications/fps_counter/Cargo.toml new file mode 100644 index 0000000000..b7ca5691d0 --- /dev/null +++ b/applications/fps_counter/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "fps_counter" +version = "0.1.0" + +[dependencies] +mpmc = "0.1.6" +spin = "0.9.0" +zerocopy = "0.5.0" +core2 = { version = "0.4.0", default-features = false, features = ["alloc", "nightly"] } + +[dependencies.porthole] +path = "../../kernel/porthole" + +[dependencies.log] +version = "0.4.8" + +[dependencies.memory] +path = "../../kernel/memory" + +[dependencies.memory_structs] +path = "../../kernel/memory_structs" + +[target.'cfg(target_arch = "x86_64")'.dependencies] +page_attribute_table = { path = "../../kernel/page_attribute_table" } + +[dependencies.multicore_bringup] +path = "../../kernel/multicore_bringup" + +[dependencies.spawn] +path = "../../kernel/spawn" + +[dependencies.task] +path = "../../kernel/task" + +[dependencies.scheduler] +path = "../../kernel/scheduler" + +[dependencies.stdio] +path = "../../libs/stdio" + +[dependencies.hpet] +path = "../../kernel/acpi/hpet" + +[dependencies.mouse_data] +path = "../../libs/mouse_data" + +[dependencies.mouse] +path = "../../kernel/mouse" + +[dependencies.event_types] +path = "../../kernel/event_types" + +[dependencies.keycodes_ascii] +path = "../../libs/keycodes_ascii" + +[dependencies.font] +path = "../../kernel/font" + +[dependencies.device_manager] +path = "../../kernel/device_manager" diff --git a/applications/fps_counter/src/lib.rs b/applications/fps_counter/src/lib.rs new file mode 100644 index 0000000000..920fee220f --- /dev/null +++ b/applications/fps_counter/src/lib.rs @@ -0,0 +1,184 @@ + +#![no_std] +extern crate porthole; +extern crate spin; +extern crate alloc; +extern crate font; + +use alloc::vec::Vec; +use font::{CHARACTER_HEIGHT, CHARACTER_WIDTH}; +use porthole::units::Rect; +use porthole::{window, DEFAULT_WINDOW_COLOR, DEFAULT_TEXT_COLOR, DEFAULT_BORDER_COLOR, WINDOW_MANAGER}; +use spin::{Mutex,RwLockReadGuard}; +use alloc::format; +use alloc::sync::Arc; + +extern crate device_manager; +extern crate hpet; +extern crate memory; +extern crate mouse; +extern crate mouse_data; +extern crate multicore_bringup; +extern crate scheduler; +extern crate task; + + +use alloc::string::{String, ToString}; +use hpet::{get_hpet, Hpet}; +use memory::{BorrowedMappedPages, Mutable}; +use window::*; +// Useful toy application that shows the real time performance +pub struct FpsCounter { + window: Arc>, + hpet: RwLockReadGuard<'static, BorrowedMappedPages>, + time_it_took_to_render: u64, + timer_freq: u64, + total_frames: u64, + total_time: u64, + avg_fps: u64, + avg_time: u64, + avg_fps_str: String, + avg_time_str: String, +} + +impl FpsCounter { + pub fn new() -> Result { + let window = WINDOW_MANAGER.get().ok_or("Unable to get WINDOW_MANAGER")?.lock().new_window(&Rect::new(360,360,0,0), Some(format!("FpsCounter")))?; + let hpet = get_hpet().ok_or("Unable to get hpet")?; + let time_it_took_to_render = hpet.get_counter(); + let timer_freq = hpet.counter_period_femtoseconds() as u64; + window.lock().fill(DEFAULT_WINDOW_COLOR)?; + Ok(Self { + window, + hpet, + time_it_took_to_render, + timer_freq, + total_frames: 0, + total_time: 0, + avg_fps: 0, + avg_time: 0, + avg_fps_str: format!("Frames per second:"), + avg_time_str: format!("Median frame time in micro seconds:"), + }) + } + + fn calculate_next_frame_time(&mut self) { + let time = self.hpet.get_counter(); + let diff = (time - self.time_it_took_to_render) * self.timer_freq / 1_000_000_000; + self.total_time += diff; + self.time_it_took_to_render = time; + self.total_frames += 1; + } + + fn reset_counters(&mut self) { + // this equals to a second + if self.total_time >= 1_000_000 { + self.avg_fps = self.total_frames; + self.avg_time = self.total_time / self.total_frames; + self.total_time = 0; + self.total_frames = 0; + } + } + + pub fn run(&mut self) -> Result<(), &'static str> { + let mut counter = 0; + self.window.lock().fill(DEFAULT_WINDOW_COLOR)?; + loop{ + self.calculate_next_frame_time(); + self.reset_counters(); + if counter == 1{ + counter = 0; + //self.draw()?; + continue; + }else{ + scheduler::schedule(); + counter +=1; + } + } + Ok(()) + } + + fn draw(&mut self) -> Result<(), &'static str> { + + self.window + .lock() + .display_window_title(DEFAULT_TEXT_COLOR, DEFAULT_BORDER_COLOR)?; + self.print_avg_fps()?; + self.print_avg_time()?; + Ok(()) + } + + fn print_avg_fps(&mut self) -> Result<(), &'static str> { + let mut drawable_area = self.window.lock().drawable_area().to_relative_pos(); + let avg_fps = self.avg_fps.to_string(); + + self.window.lock().print_string_line( + &drawable_area, + &self.avg_fps_str, + 0xF8FF0E, + DEFAULT_WINDOW_COLOR, + )?; + + drawable_area.x = (CHARACTER_WIDTH * self.avg_fps_str.len()) as u32; + self.window.lock().print_string_line( + &drawable_area, + &avg_fps, + 0x20F065, + DEFAULT_WINDOW_COLOR, + )?; + Ok(()) + } + + fn print_avg_time(&mut self) -> Result<(), &'static str> { + let mut drawable_area = self.window.lock().drawable_area().to_relative_pos(); + drawable_area.y += (CHARACTER_HEIGHT + 1) as u32; + let avg_time = self.avg_time.to_string(); + // Prints default text for `avg_time_str` + self.window.lock().print_string_line( + &drawable_area, + &self.avg_time_str, + 0xF8FF0E, + DEFAULT_WINDOW_COLOR, + )?; + + drawable_area.x = (CHARACTER_WIDTH * self.avg_time_str.len()) as u32; + // Prints current avg time + self.window.lock().print_string_line( + &drawable_area, + &avg_time, + 0x20F065, + DEFAULT_WINDOW_COLOR, + )?; + + Ok(()) + } +} + +pub fn main(_args: Vec) -> isize{ + { + let _task_ref = match spawn::new_task_builder(fps_counter_loop, ()) + .name("fps_counter_loop".to_string()) + .spawn() { + Ok(task_ref) => { task_ref } + Err(err) => { + log::error!("{}", err); + log::error!("failed to spawn shell"); + return -1; + } + }; + } + + // block this task, because it never needs to actually run again + task::with_current_task(|t| t.block()) + .expect("shell::main(): failed to get current task") + .expect("shell:main(): failed to block the main shell task"); + scheduler::schedule(); + + loop { + // log:: warn!("BUG: blocked shell task was scheduled in unexpectedly"); + } +} +pub fn fps_counter_loop(mut _dummy: ()) -> Result<(), &'static str> { + FpsCounter::new()?.run()?; + Ok(()) +} \ No newline at end of file diff --git a/applications/shell/Cargo.toml b/applications/shell/Cargo.toml index 8304f5ddc8..1cea9fc88f 100644 --- a/applications/shell/Cargo.toml +++ b/applications/shell/Cargo.toml @@ -31,9 +31,6 @@ path = "../../kernel/task" [dependencies.runqueue] path = "../../kernel/runqueue" -[dependencies.window_manager] -path = "../../kernel/window_manager" - [dependencies.environment] path = "../../kernel/environment" diff --git a/applications/shell/src/lib.rs b/applications/shell/src/lib.rs index 2dc5a0c6b4..e9a70c3a50 100644 --- a/applications/shell/src/lib.rs +++ b/applications/shell/src/lib.rs @@ -12,7 +12,6 @@ extern crate spawn; extern crate task; extern crate runqueue; extern crate event_types; -extern crate window_manager; extern crate path; extern crate root; extern crate scheduler; @@ -328,6 +327,7 @@ impl Shell { /// Update the position of cursor. `offset_from_end` specifies the position relative to the end of the text in number of characters. fn update_cursor_pos(&mut self, offset_from_end: usize) -> Result<(), &'static str> { let mut terminal = self.terminal.lock(); + // This is here to eliminate visual bugs terminal.cursor.disable(); terminal.display_cursor()?; if offset_from_end == 0 { @@ -1256,7 +1256,7 @@ impl Shell { let mut need_prompt = false; self.redisplay_prompt(); self.terminal.lock().refresh_display()?; - + loop { // If there is anything from running applications to be printed, it printed on the screen and then // return true, so that the loop continues, otherwise nothing happens and we keep on going with the @@ -1265,17 +1265,17 @@ impl Shell { need_refresh = true; continue; } - + // Handles the cleanup of any application task that has finished running, returns whether we need // a new prompt or need to refresh the screen. let (need_refresh_on_task_event, need_prompt_on_task_event) = self.task_handler()?; - + // Print prompt or refresh the screen based on needs. if need_prompt || need_prompt_on_task_event { self.redisplay_prompt(); need_prompt = false; } - + // Handle all available events from the terminal's (its window's) event queue. while let Some(ev) = { // this weird syntax ensures the terminal lock is dropped before entering the loop body @@ -1288,36 +1288,38 @@ impl Shell { trace!("exited terminal"); return Ok(()); } - - Event::WindowResizeEvent(new_position) => { - self.terminal.lock().resize(new_position)?; - // the above function also refreshes the terminal display - } - + // Handles ordinary keypresses Event::KeyboardEvent(ref input_event) => { - self.key_event_producer.write_one(input_event.key_event); + if !self.terminal.lock().window.lock().resizing { + self.key_event_producer.write_one(input_event.key_event); + } } - - _unhandled => { + + _unhandled => { // trace!("Shell is ignoring unhandled event: {:?}", _unhandled); } }; - } + } + if self.terminal.lock().window.lock().resized() { + self.terminal + .lock() + .window + .lock() + .should_resize_framebuffer()?; + self.terminal.lock().resize()?; + } if need_refresh || need_refresh_on_task_event { // update if there are outputs from applications self.terminal.lock().refresh_display()?; } - - let is_active = { - let term = self.terminal.lock(); - term.window.is_active() - }; - - if is_active { + + let is_active = { self.terminal.lock().window.lock().active() }; + + if is_active && !self.terminal.lock().window.lock().resizing { self.terminal.lock().display_cursor()?; } - + // handle inputs need_refresh = false; loop { @@ -1325,14 +1327,21 @@ impl Shell { if let Some(ref key_event_consumer) = locked_consumer.deref() { if let Some(key_event) = key_event_consumer.read_one() { mem::drop(locked_consumer); // drop the lock so that we can invoke the method on the next line - if let Err(e) = self.handle_key_event(key_event) { - error!("{}", e); + // We don't want user to be able to write to terminal while Window is in the process of resizing. + if !self.terminal.lock().window.lock().resizing { + if let Err(e) = self.handle_key_event(key_event) { + error!("{}", e); + } } - if key_event.action == KeyAction::Pressed { need_refresh = true; } - } else { // currently the key event queue is empty, break the loop + if key_event.action == KeyAction::Pressed { + need_refresh = true; + } + } else { + // currently the key event queue is empty, break the loop break; } - } else { // currently the key event queue is taken by an application + } else { + // currently the key event queue is taken by an application break; } } @@ -1344,6 +1353,7 @@ impl Shell { } } } + } /// Shell internal command related methods. @@ -1382,7 +1392,7 @@ impl Shell { } fn execute_internal_clear(&mut self) -> Result<(), &'static str> { - self.terminal.lock().clear(); + self.terminal.lock().clear()?; self.clear_cmdline(false)?; self.redisplay_prompt(); Ok(()) diff --git a/applications/test_downtime/Cargo.toml b/applications/test_downtime/Cargo.toml index ab3bf6f8be..4b9aa7138f 100644 --- a/applications/test_downtime/Cargo.toml +++ b/applications/test_downtime/Cargo.toml @@ -34,8 +34,6 @@ path = "../../kernel/async_channel" # for WM -[dependencies.window] -path = "../../kernel/window" [dependencies.shapes] path = "../../kernel/shapes" @@ -43,12 +41,6 @@ path = "../../kernel/shapes" [dependencies.color] path = "../../kernel/color" -[dependencies.framebuffer] -path = "../../kernel/framebuffer" - -[dependencies.framebuffer_drawer] -path = "../../kernel/framebuffer_drawer" - [dependencies.runqueue] path = "../../kernel/runqueue" diff --git a/applications/test_downtime/src/lib.rs b/applications/test_downtime/src/lib.rs index 92dc1165a0..e4907bcd3f 100644 --- a/applications/test_downtime/src/lib.rs +++ b/applications/test_downtime/src/lib.rs @@ -12,9 +12,6 @@ extern crate rendezvous; extern crate async_channel; extern crate cpu; extern crate runqueue; -extern crate window; -extern crate framebuffer; -extern crate framebuffer_drawer; extern crate color; extern crate shapes; extern crate hpet; @@ -164,6 +161,7 @@ fn graphics_print_stats(vec: Vec, aggregted_count: u64, aggregted_total :u6 (count,total) } +/* /// The restartable task which runs into fault when it tries to draw a circle at (200,200) location. /// This task is not suppose to exit (It fails due to the injected fault) fn fault_graphics_task(_arg_val: usize) -> Result<(), &'static str>{ @@ -239,6 +237,7 @@ fn fault_graphics_task(_arg_val: usize) -> Result<(), &'static str>{ } } } +*/ // ------------------------------------------------------------------------------------------------- // ------------------------- IPC fault injection section ------------------------------------------- diff --git a/book/src/subsystems/display/display.md b/book/src/subsystems/display/display.md deleted file mode 100644 index c83c4e0b8a..0000000000 --- a/book/src/subsystems/display/display.md +++ /dev/null @@ -1,3 +0,0 @@ -# Display Subsystem - -*Warning:* the display subsystem in Theseus is in need of complete redesign. It is inefficient and poorly implemented, as it was simply a means to the end of being able to interact with the system, and unfortunately has not been a focus of any significant effort. \ No newline at end of file diff --git a/book/src/subsystems/display/porthole.md b/book/src/subsystems/display/porthole.md new file mode 100644 index 0000000000..0775a3a49f --- /dev/null +++ b/book/src/subsystems/display/porthole.md @@ -0,0 +1,66 @@ +# Porthole + +Theseus display and window management are done by `Porthole` easy to use, a performant graphical subsystem. + +## Design +```mermaid +stateDiagram-v2 + Application --> AppRender + AppRender --> Window + Window --> VirtualFrameBuffer + VirtualFrameBuffer --> WindowManager + Renderloop --> WindowManager + WindowManager --> Renderloop + Renderloop --> BackBuffer(VirtualFrameBuffer) + BackBuffer(VirtualFrameBuffer) --> + FrontBuffer(PhysicalFrameBuffer) +``` + +The core design principle of the `Porthole` is removing complexity from an end user while providing good performance. + +Before moving on to details we should give a bit more detail about the diagram above. In a typical application, the app will read some data, do operations, and render its content onto `Window`, `Window` will write all of the data that the application wants to render into its framebuffer, in the main loop of `Porthole` `WindowManager` will copy all the data from all the `Windows` into backbuffer, then it will copy all the data into frontbuffer which will update the screen. + +### Framebuffer + +In Porthole, there are two types of buffers that are crucial to the rendering process: the `VirtualFrameBuffer` and the `PhysicalBuffer`. + +The `VirtualFrameBuffer` acts as a backbuffer for the `WindowManager` and for windows it acts as temporary buffer that is to be transferred to main backbuffer.The `PhysicalBuffer` is directly mapped to the pixels on the screen. + +Ideally the end user will not have to interact with the `VirtualFrameBuffer` or `PhysicalFrameBuffer` directly. The `Window` and `WindowManager` are responsible for managing these buffers. The goal is to provide a smooth and seamless experience for the user. + + +### Window + +`Window` in a basic sense is a drawable object, both `WindowManager` and the application holds `Arc>`, it contains information about the position, size, whether or not it's the active window, whether or not it's resized, title, etc... + +As expected from most window managers `Window` can be partially moved outside the screen, and resized, it has a title and title bar. + +Every `Window` has its own `VirtualFrameBuffer`, which has the same size as the window, when a window is resized window also changes `VirtualFrameBuffer`'s size to match the new size. +Window's `VirtualFramebuffer` always sits at the top-left (x:0,y:0) of the screen. + +The window provides various methods to draw and print things, the window only provides these methods to write into its framebuffer which is not directly mapped to actual pixels on the physical screen, but is used by `WindowManager` to write into the backbuffer, the reason for this is quite simple, we want to avoid dealing with complex lifetime related issues that would arise from having multiple window's accessing different parts of a single backbuffer, which also adds lessens our chance of having deadlock. + + +### WindowManager + +The `WindowManager` is relatively straightforward it holds windows via `Vec>`, it keep their rendering order via `window_rendering_order`. +It has a `VirtualFrameBuffer` that acts as a backbuffer and `PhysicalFrameBuffer` that acts like frontbuffer. It also controls the mouse. + +We hold `WindowManager` via `Once>` and it's initalized once by `captain`, after that whenever we need it we can get it by calling `WINDOW_MANAGER.get()`. + +Main loop of the `WindowManager` is called `port_loop` at the start of the function we get window manager, after that `loop` starts by getting event from the keyboard and mouse and handling them, be it dragging the window or moving the mouse, we se also send these events to active window via `set_window_event`. + +After handling keyboard and mouse events we move on to rendering part of the loop. We start the rendering process by calling `window_manager.lock().update()`. Inside the `update` function we first clear the whole screen by calling `blank()` then, we call `draw_windows` which iterates through the `window_rendering_order` and using that index we get a window from `windows` copy that in to backbuffer repeat until we exhaust the iteration. + +We then call `render` function which copies backbuffer and then we finish the loop. + + +### How Rendering Works, Iterator Case Study + +Rust, provides many safety mechanisms to prevent common programming pitfalls, such as dangling pointers, use after frees, and type errors. However, these safety mechanisms come at a cost. For example each time you access an element in a collection that implements `Index`, the runtime performs a bounds check to ensure that you're accessing a valid index. This cost can quickly add up when you're accessing a large collection in a loop that runs as fast as possible. + +To address this issue, Rust provides the `Iterator` trait, which allows you to process a collection in a lazy, chunk-by-chunk manner. In the context of rendering, this can lead to significant performance improvements. + +Consider a case study where our window has a size of `width: 80, height: 80` and is located at `x: 0, y: 0`. Instead of accessing each pixel individually, we can create an iterator for the `VirtualFrameBuffer` that will allow us to access the data in larger chunks. For example, we can take a row of pixels starting from `0` to `80` from both the window's framebuffer and the backbuffer. Then, we can simply call `copy_from_slice` to copy the data, move to the next row, and repeat. + +This approach significantly increased performance compared to accessing each pixel individually. It also makes the code more flexible and easier to maintain. This case study demonstrates the power of Iterators in Rust and how they are used to improve the performance of our rendering operations. For more information on the design and implementation of the Iterator-based rendering, please refer to the file framebuffer.rs. \ No newline at end of file diff --git a/book/src/subsystems/display/window_manager.md b/book/src/subsystems/display/window_manager.md deleted file mode 100644 index 2df0a1d818..0000000000 --- a/book/src/subsystems/display/window_manager.md +++ /dev/null @@ -1,45 +0,0 @@ -# How the Window Manager works - -## Design - -Typically, both the application that owns/creates a window and the window manager that controls that window need to access it jointly. The application needs to display its content into the main part of the window, and the window manager needs information about the location and depth ordering of all windows to render them. - -To share a window between an application and the window manager, the application holds a strong reference (`Arc`) to the window, while the window manager holds a weak reference (`Weak`) to that same window. This allows the window manager to control an manage a window without conceptually owning it. - -We use a `Mutex` to wrap each window to allow the application task and window manager task to safely access it jointly. However, `Mutex` introduces the possibility of deadlock: when an application wants to access its window, it must acquire the `Mutex` lock, operate on the window, and then release the lock. If the application doesn't release the lock on its window, the window manager will be forced to block until the lock is released, preventing it from performing typical operations like switching between windows, delivering events, or deleting windows. - -To solve this problem, we define two structures: `Window` and `WindowInner`. `WindowInner` only contains the information required by the window manager. The window manager holds a list of references to `WindowInner` objects, while only the application owns the outer `Window` object (which itself does contain a reference to the underlying WM-owned `WindowInner` object. The `Window` struct also contains other application-relevant states that describe the window. - -## The `WindowInner` structure - -The `window_inner` crate defines a `WindowInner` structure. It has states and methods of displaying the window on the screen. - -A `WindowInner` has a framebuffer to which it can display the content of the window. The framebuffer takes a type parameter of pixels it consists of. When the window is rendered to the screen, a compositor may composite every pixel with different principles according to the type. Currently, we have implemented a normal RGB pixel and a pixel of an alpha channel. - -Both an application's window and the window manager has a reference to the same `WindowInner` object. The application can configure and draw in the framebuffer and the manager can display and composite the window with others. - -This structure also has an event producer. The window manager gets events from I/O devices such as keyboards and push them to the corresponding producer. - - -## Window - -A `Window` object represents a window and is owned by an application. It contains its profile, a title, a consumer and a list of displayables. The consumer can get events pushed to the producer in its profile by the manager. - -A `Window` provides methods to display the displayables in it and render itself to the screen. The window manager is responsible for compositing it with other windows through a framebuffer compositor. - -## Displayables - -The `displayable` crate defines a `Displayable` trait. A `Displayable` is an item which can display itself onto a framebuffer. It usually consists of basic graphs and acts as a component of a window such as a button or a text box. Currently, we have implemented a `TextDisplay` which is a block of text. In the future, we will implement other kinds of displayables. - -An application can own multiple displayables and display any type of `Displayable` in its window. - -## The WindowManager - -The `window_manager` crate defines a `WindowManager` structure. This structure consists of the profiles of an active window, a list of shown windows and a list of hidden windows. The hidden ones are totally overlapped by others. The structure implements basic methods to manipulate the list such as adding or deleting a window. - -The `WindowManager` structure contains a bottom framebuffer which represents the background image and a final framebuffer of a floating window border and a mouse arrow. In refreshing an area, it renders the framebuffers in order background -> hidden list -> shown list -> active -> top. It provides several methods to update a rectangle area or several pixels for better performance. - -The structure defines a loop for generic events, a loop for keyboard events and a loop for mouse events. Theseus will initialize them as tasks to handle inputs. The window manager structure provides methods to operate on the window list as reactions to these inputs. It can move a window when we drag it with mouse or pass other events to the active window. The owner application of the active window can handle these events. - -The `window_manager` crate owns a `WINDOW_MANAGER` instance which contains all the existing windows. It invokes the methods of `WindowManager` to manage these windows. - diff --git a/book/src/subsystems/display/window_tutorial.md b/book/src/subsystems/display/window_tutorial.md deleted file mode 100644 index 747c34962a..0000000000 --- a/book/src/subsystems/display/window_tutorial.md +++ /dev/null @@ -1,14 +0,0 @@ -# How to Create Windows and Display Content - -## Create a Window - -An application invokes the `Window::new()` function in the `window` crate to create a new window. The function would create a new `Window` object and add a weak reference of its `WindowInner` to the `WINDOW_MANAGER` instance in `window_manager`. It then returns the window to the application. Once the application terminates, the window it owns would be dropped automatically, and the weak reference in the window manager would be deleted. - -## Display in a Window - -An application can create a `Displayable` and invoke `Window.display()` to display it. This method is generic and works for all kinds of displayables. - -After display a displayable in its framebuffer, the window would invoke its `render()` method to render the updates to the screen. A framebuffer compositor will composite a list of framebuffers and forward the result to a final framebuffer which is mapped to the screen. - -## Handle Key Inputs -An application invokes `Window.handle_event()` to handle the events sent to it. For example, an active window will receive all the key input events. An application can invoke `Window.handle_event()` in a loop to handle these inputs from the keyboard. diff --git a/kernel/captain/Cargo.toml b/kernel/captain/Cargo.toml index 1e1b15fed1..105e4898fd 100644 --- a/kernel/captain/Cargo.toml +++ b/kernel/captain/Cargo.toml @@ -22,8 +22,8 @@ path = "../kernel_config" [dependencies.logger] path = "../logger" -[dependencies.window_manager] -path = "../window_manager" +[dependencies.porthole] +path = "../porthole" [dependencies.first_application] path = "../first_application" diff --git a/kernel/captain/src/lib.rs b/kernel/captain/src/lib.rs index 91d13e4f75..1b54c93c5f 100644 --- a/kernel/captain/src/lib.rs +++ b/kernel/captain/src/lib.rs @@ -43,7 +43,7 @@ extern crate scheduler; extern crate first_application; extern crate exceptions_full; extern crate network_manager; -extern crate window_manager; +extern crate porthole; extern crate tlb_shootdown; extern crate multiple_heaps; extern crate console; @@ -155,7 +155,7 @@ pub fn init( if page_attribute_table::init().is_err() { error!("This CPU does not support the Page Attribute Table"); } - let (key_producer, mouse_producer) = window_manager::init()?; + let (key_producer, mouse_producer) = porthole::WindowManager::init()?; // initialize the rest of our drivers device_manager::init(key_producer, mouse_producer)?; diff --git a/kernel/compositor/Cargo.toml b/kernel/compositor/Cargo.toml deleted file mode 100644 index b3bc97bd1c..0000000000 --- a/kernel/compositor/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "compositor" -version = "0.1.0" -authors = ["Wenqiu Yu "] -description = "a compositor trait which composites a list of buffers to a single buffer" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.shapes] -path = "../shapes" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/compositor/src/lib.rs b/kernel/compositor/src/lib.rs deleted file mode 100644 index 592f596dde..0000000000 --- a/kernel/compositor/src/lib.rs +++ /dev/null @@ -1,198 +0,0 @@ -//! This crate defines a trait of `Compositor` . -//! A compositor composites a list of sources buffers to a single destination buffer. - -#![no_std] - -extern crate framebuffer; -extern crate shapes; - -use core::iter::IntoIterator; - -use framebuffer::{Framebuffer, Pixel}; -use shapes::{Coord, Rectangle}; -use core::ops::Range; - -/// A compositor composites (combines or blends) a series of "source" framebuffers onto a single "destination" framebuffer. -/// The type parameter `B` allows a compositor to support multiple types of regions or "bounding boxes", -/// given by the trait bound `CompositableRegion`. -pub trait Compositor { - /// Composites the framebuffers in the list of source framebuffers `src_fbs` onto the destination framebuffer `dest_fb`. - /// - /// # Arguments - /// * `src_fbs`: an iterator over the source framebuffers to be composited, - /// along with where in the `dest_fb` they should be composited. - /// * `dest_fb`: the destination framebuffer that will hold the source framebuffers to be composited. - /// * `dest_bounding_boxes`: an iterator over bounding boxes that specify which regions - /// in the destination framebuffer should be updated. - /// For each source framebuffer in `src_fbs`, the compositor will iterate over every bounding box - /// and find the corresponding region in that source framebuffer and then blend that region into the destination. - /// - /// For example, if the window manager wants to draw a new partially-transparent window, - /// it will pass the framebuffers for all existing windows plus the new window (in bottom-to-top order) - /// to the compositor, in the argument `src_fbs`. - /// The `dest_fb` would be the final framebuffer mapped to the display device (screen memory), - /// and the `bounding_boxes` would be an iterator over just a single region in the final framebuffer - /// where that new window will be located. - /// When the source framebuffers are composited from bottom to top, the compositor will redraw the region of every source framebuffer - /// that intersects with that bounding box. - /// - /// For another example, suppose the window manager wants to draw a transparent mouse pointer on top of all windows. - /// It will pass the framebuffers of existing windows as well as a top framebuffer that contains the mouse pointer image. - /// In this case, the `bounding_boxes` could be the coordinates of all individual pixels in the mouse pointer image - /// (expressed as coordinates in the final framebuffer). - fn composite<'a, B: CompositableRegion + Clone, P: 'a + Pixel>( - &mut self, - src_fbs: impl IntoIterator>, - dest_fb: &mut Framebuffer

, - dest_bounding_boxes: impl IntoIterator + Clone, - ) -> Result<(), &'static str>; -} - - -/// A source framebuffer to be composited, along with its target position. -pub struct FramebufferUpdates<'a, P: Pixel> { - /// The source framebuffer to be composited. - pub src_framebuffer: &'a Framebuffer

, - /// The coordinate in the destination framebuffer where the source `framebuffer` - /// should be composited. - /// This coordinate is expressed relative to the top-left corner of the destination framebuffer. - pub coordinate_in_dest_framebuffer: Coord, -} - -/// A `CompositableRegion` is an abstract region (i.e., a bounding box) -/// that can optimize the compositing (blending) of one framebuffer into another framebuffer -/// according to the specifics of the region's shape. -/// For example, a single 2-D point (`Coord`) offers no real room for optimization -/// because only one pixel will be composited, -/// but a rectangle **does** allow for optimization, as a large chunk of pixels can be composited all at once. -/// -/// In addition, a `CompositableRegion` makes it easier for a compositor to only composite pixels in a subset of a given source framebuffer -/// rather than forcing it to composite the whole framebuffer, which vastly improves performance. -pub trait CompositableRegion { - /// Returns the number of pixels in the region. - fn size(&self) -> usize; - - /// Returns the range of rows covered by this region, - /// given as row indices where row `0` is the top row in the region. - fn row_range(&self) -> Range; - - /// Blends the pixels in the source framebuffer `src_fb` within the range of rows (`src_fb_row_range`) - /// into the pixels in the destination framebuffer `dest_fb`. - /// The `dest_coord` is the coordinate in the destination buffer (relative to its top-left corner) - /// where the `src_fb` will be composited (starting at the `src_fb`'s top-left corner). - /// `src_fb_row_range` is the index range of rows in the source framebuffer to blend. - fn blend_buffers( - &self, - src_fb: &Framebuffer

, - dest_fb: &mut Framebuffer

, - dest_coord: Coord, - src_fb_row_range: Range - ) -> Result<(), &'static str>; -} - -impl CompositableRegion for Coord { - #[inline] - fn row_range(&self) -> Range { - self.y..self.y + 1 - } - - #[inline] - fn size(&self) -> usize { - 1 - } - - fn blend_buffers( - &self, - src_fb: &Framebuffer

, - dest_fb: &mut Framebuffer

, - dest_coord: Coord, - _src_fb_row_range: Range, - ) -> Result<(), &'static str>{ - let relative_coord = self.clone() - dest_coord; - if let Some(pixel) = src_fb.get_pixel(relative_coord) { - dest_fb.draw_pixel(self.clone(), pixel); - } - Ok(()) - } -} - -impl CompositableRegion for Rectangle { - #[inline] - fn row_range(&self) -> Range { - self.top_left.y..self.bottom_right.y - } - - #[inline] - fn size(&self) -> usize { - (self.bottom_right.x - self.top_left.x) as usize * (self.bottom_right.y - self.top_left.y) as usize - } - - fn blend_buffers( - &self, - src_fb: &Framebuffer

, - dest_fb: &mut Framebuffer

, - dest_coord: Coord, - src_fb_row_range: Range, - ) -> Result<(), &'static str> { - let (dest_width, dest_height) = dest_fb.get_size(); - let (src_width, src_height) = src_fb.get_size(); - - let start_y = core::cmp::max(src_fb_row_range.start as isize + dest_coord.y, self.top_left.y); - let end_y = core::cmp::min(src_fb_row_range.end as isize + dest_coord.y, self.bottom_right.y); - - // skip if the updated part is not in the dest framebuffer - let dest_start = Coord::new( - core::cmp::max(0, self.top_left.x), - core::cmp::max(0, start_y) - ); - - let dest_end = Coord::new( - core::cmp::min(dest_width as isize, self.bottom_right.x), - core::cmp::min(dest_height as isize, end_y) - ); - if dest_end.x < 0 - || dest_start.x > dest_width as isize - || dest_end.y < 0 - || dest_start.y > dest_height as isize - { - return Ok(()); - } - - // skip if the updated part is not in the source framebuffer - let coordinate_start = dest_start - dest_coord; - let coordinate_end = dest_end - dest_coord; - if coordinate_end.x < 0 - || coordinate_start.x > src_width as isize - || coordinate_end.y < 0 - || coordinate_start.y > src_height as isize - { - return Ok(()); - } - - let src_x_start = core::cmp::max(0, coordinate_start.x) as usize; - let src_y_start = core::cmp::max(0, coordinate_start.y) as usize; - - // draw only the part within the dest buffer - let width = core::cmp::min(coordinate_end.x as usize, src_width) - src_x_start; - let height = core::cmp::min(coordinate_end.y as usize, src_height) - src_y_start; - - // copy every line of the block to the dest framebuffer. - let src_buffer = &src_fb.buffer(); - for i in 0..height { - let src_start = Coord::new(src_x_start as isize, (src_y_start + i) as isize); - let src_start_index = match src_fb.index_of(src_start) { - Some(index) => index, - None => {continue;} - }; - let src_end_index = src_start_index + width; - let dest_start = src_start + dest_coord; - let dest_start_index = match dest_fb.index_of(dest_start) { - Some(index) => index, - None => {continue;} - }; - dest_fb.composite_buffer(&(src_buffer[src_start_index..src_end_index]), dest_start_index as usize); - } - - Ok(()) - } -} diff --git a/kernel/displayable/Cargo.toml b/kernel/displayable/Cargo.toml deleted file mode 100644 index 995e93c492..0000000000 --- a/kernel/displayable/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "displayable" -version = "0.1.0" -authors = ["Wenqiu Yu "] -description = "provides a Displayable trait, which abstracts objects that can display themselves onto a framebuffer" - -[dependencies] -spin = "0.9.4" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.color] -path = "../color" - -[dependencies.shapes] -path = "../shapes" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/displayable/src/lib.rs b/kernel/displayable/src/lib.rs deleted file mode 100644 index c1282dc6ab..0000000000 --- a/kernel/displayable/src/lib.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! This crate defines a trait of `Displayable`. -//! A displayable is a block of content. It can display itself onto a framebuffer. -//! The coordinate of a displayable represents its origin (top-left point). - -#![no_std] - -extern crate framebuffer; -extern crate shapes; -extern crate color; - -use framebuffer::{Framebuffer, Pixel}; -use shapes::{Coord, Rectangle}; -use color::Color; - -/// The `Displayable` trait is an abstraction for any object that can display itself onto a framebuffer. -/// Examples include a text box, button, window border, etc. -pub trait Displayable { - /// Displays this `Displayable`'s content in the given framebuffer. - /// # Arguments - /// * `coordinate`: the coordinate within the given `framebuffer` where this displayable should render itself. - /// The `coordinate` is relative to the top-left point of the `framebuffer`. - /// * `framebuffer`: the framebuffer to display onto. - /// - /// Returns a rectangle that represents the region of the framebuffer that was updated. - fn display>( - &mut self, - coordinate: Coord, - framebuffer: &mut Framebuffer

, - ) -> Result; - - /// Resizes the displayable area, but does not automatically refresh its display. - fn set_size(&mut self, width: usize, height: usize); - - /// Gets the size of the area occupied by the displayable. - fn get_size(&self) -> (usize, usize); -} diff --git a/kernel/displayable/text_display/Cargo.toml b/kernel/displayable/text_display/Cargo.toml deleted file mode 100644 index 1b0f075881..0000000000 --- a/kernel/displayable/text_display/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "text_display" -version = "0.1.0" -authors = ["Wenqiu Yu "] -description = "a text display is a block of text which can display onto a framebuffer" - -[dependencies] -spin = "0.9.4" - -[dependencies.framebuffer] -path = "../../framebuffer" - -[dependencies.displayable] -path = "../" - -[dependencies.framebuffer_printer] -path = "../../framebuffer_printer" - -[dependencies.font] -path = "../../font" - -[dependencies.shapes] -path = "../../shapes" - -[dependencies.color] -path = "../../color" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/displayable/text_display/src/lib.rs b/kernel/displayable/text_display/src/lib.rs deleted file mode 100644 index cab71f702f..0000000000 --- a/kernel/displayable/text_display/src/lib.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! This crate defines a text displayable. -//! A text displayable profiles a block of text to be displayed onto a framebuffer. - -#![no_std] - -extern crate alloc; -extern crate displayable; -extern crate font; -extern crate framebuffer; -extern crate framebuffer_printer; -extern crate shapes; -extern crate color; - -use alloc::string::String; -use displayable::{Displayable}; -use font::{CHARACTER_HEIGHT, CHARACTER_WIDTH}; -use framebuffer::{Pixel, Framebuffer}; -use color::Color; -use shapes::{Coord, Rectangle}; - - -/// A text displayable profiles the size and color of a block of text. It can display in a framebuffer. -#[derive(Debug)] -pub struct TextDisplay { - width: usize, - height: usize, - /// The position where the next character will be displayed. - /// This is updated after each `display()` invocation, and is useful for optimization. - next_col: usize, - next_line: usize, - text: String, - fg_color: Color, - bg_color: Color, - /// The cache of the text that was last displayed. - cache: String, -} - -impl Displayable for TextDisplay { - fn display> ( - &mut self, - coordinate: Coord, - framebuffer: &mut Framebuffer

, - ) -> Result { - let (string, col, line) = if self.cache.len() > 0 && self.text.starts_with(self.cache.as_str()) { - ( - &self.text.as_str()[self.cache.len()..self.text.len()], - self.next_col, - self.next_line, - ) - } else { - (self.text.as_str(), 0, 0) - }; - - let (next_col, next_line, mut bounding_box) = framebuffer_printer::print_string( - framebuffer, - coordinate, - self.width, - self.height, - string, - self.fg_color.into(), - self.bg_color.into(), - col, - line, - ); - - if next_line < self.next_line { - bounding_box.bottom_right.y = ((self.next_line + 1 ) * CHARACTER_HEIGHT) as isize - } - - self.next_col = next_col; - self.next_line = next_line; - self.cache = self.text.clone(); - - return Ok(bounding_box + coordinate); - } - - fn set_size(&mut self, width: usize, height: usize) { - self.width = width; - self.height = height; - } - - fn get_size(&self) -> (usize, usize) { - (self.width, self.height) - } -} - -impl TextDisplay { - /// Creates a new text displayable. - /// # Arguments - /// * `width`, `height`: the dimensions of the text area, in number of characters. - /// * `fg_color`, `bg_color`: the color of the text and the background behind the text, respectively. - pub fn new( - width: usize, - height: usize, - fg_color: Color, - bg_color: Color, - ) -> Result { - Ok(TextDisplay { - width: width, - height: height, - next_col: 0, - next_line: 0, - text: String::new(), - fg_color: fg_color, - bg_color: bg_color, - cache: String::new(), - }) - } - - /// Gets the background color of the text area - pub fn get_bg_color(&self) -> Color { - self.bg_color - } - - /// Clear the cache of the text displayable. - pub fn reset_cache(&mut self) { - self.cache = String::new(); - } - - /// Translate the index of a character in the text to the location of the text displayable. Return (column, line). - pub fn get_location(&self, index: usize) -> (usize, usize) { - let text_width = self.width / CHARACTER_WIDTH; - (index % text_width, index / text_width) - } - - /// Translate the location of a character to its index in the text. - pub fn get_index(&self, column: usize, line: usize) -> usize { - let text_width = self.width / CHARACTER_WIDTH; - line * text_width + column - } - - /// Gets the size of a text displayable in number of characters. - pub fn get_dimensions(&self) -> (usize, usize) { - (self.width / CHARACTER_WIDTH, self.height / CHARACTER_HEIGHT) - } - - /// Gets the index of next character to be displayabled. It is the position next to existing printed characters in the text displayable. - pub fn get_next_index(&self) -> usize { - let col_num = self.width / CHARACTER_WIDTH; - self.next_line * col_num + self.next_col - } - - /// Sets the text of the text displayable - pub fn set_text(&mut self, text: &str) { - self.text = String::from(text); - } -} diff --git a/kernel/framebuffer/Cargo.toml b/kernel/framebuffer/Cargo.toml deleted file mode 100644 index b9b048512f..0000000000 --- a/kernel/framebuffer/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "framebuffer" -version = "0.1.0" -authors = ["Kevin Boos ", "Wenqiu Yu "] -description = "a framebuffer is a buffer of pixels which can be composited to another framebuffer or be mapped to some physical memory" -edition = "2021" - -[dependencies] -log = "0.4.8" -zerocopy = "0.5.0" - -[target.'cfg(target_arch = "x86_64")'.dependencies] -page_attribute_table = { path = "../page_attribute_table" } - -[dependencies.memory] -path = "../memory" - -[dependencies.multicore_bringup] -path = "../multicore_bringup" - -[dependencies.shapes] -path = "../shapes" - -[dependencies.color] -path = "../color" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/framebuffer/src/lib.rs b/kernel/framebuffer/src/lib.rs deleted file mode 100644 index 109430d33e..0000000000 --- a/kernel/framebuffer/src/lib.rs +++ /dev/null @@ -1,205 +0,0 @@ -//! This crate defines a `Framebuffer` structure, which is effectively a region of memory -//! that is interpreted as a 2-D array of pixels. - -#![no_std] - -pub mod pixel; -use core::{ops::{DerefMut, Deref}, hash::{Hash, Hasher}}; -use log::{info, debug}; -use memory::{PteFlags, PteFlagsArch, PhysicalAddress, Mutable, BorrowedSliceMappedPages}; -use shapes::Coord; -pub use pixel::*; - -/// Initializes the final framebuffer based on VESA graphics mode information obtained during boot. -/// -/// The final framebuffer represents the actual pixel content displayed on screen -/// because its memory is directly mapped to the VESA display device's underlying physical memory. -pub fn init() -> Result, &'static str> { - // get the graphic mode information - let graphic_info = multicore_bringup::get_graphic_info() - .ok_or("Failed to get graphic mode information!")?; - - let vesa_display_phys_start = PhysicalAddress::new(graphic_info.physical_address() as usize) - .ok_or("Graphic mode physical address was invalid")?; - let buffer_width = graphic_info.width() as usize; - let buffer_height = graphic_info.height() as usize; - info!("Graphical framebuffer info: {} x {}, at paddr {:#X}", - graphic_info.width(), - graphic_info.height(), - graphic_info.physical_address(), - ); - - // create and return the final framebuffer - Framebuffer::new( - buffer_width, - buffer_height, - Some(vesa_display_phys_start), - ) -} - -/// A framebuffer is a region of memory interpreted as a 2-D array of pixels. -/// The memory buffer is a rectangular region with a width and height. -pub struct Framebuffer { - width: usize, - height: usize, - buffer: BorrowedSliceMappedPages, -} -impl Hash for Framebuffer

{ - fn hash(&self, state: &mut H) { - self.width.hash(state); - self.height.hash(state); - self.buffer.deref().hash(state); - } -} - -impl Framebuffer

{ - /// Creates a new framebuffer with rectangular dimensions of `width * height`, - /// specified in number of pixels. - /// - /// If `physical_address` is `Some`, the returned framebuffer will be a real physical one, - /// i.e., mapped to the physical memory at that address, which is typically hardware graphics memory. - /// In this case, we attempt to map the memory as "write-combining", which only works - /// on x86 if the Page Attribute Table feature is enabled. - /// Otherwise, we map the real physical framebuffer memory with all caching disabled. - /// - /// If `physical_address` is `None`, the returned framebuffer is a "virtual" one - /// that renders to a randomly-allocated chunk of memory. - pub fn new( - width: usize, - height: usize, - physical_address: Option, - ) -> Result, &'static str> { - let kernel_mmi_ref = memory::get_kernel_mmi_ref().ok_or("KERNEL_MMI was not yet initialized!")?; - let size = width * height * core::mem::size_of::

(); - let pages = memory::allocate_pages_by_bytes(size) - .ok_or("could not allocate pages for a new framebuffer")?; - - let mapped_framebuffer = if let Some(address) = physical_address { - // For best performance, we map the real physical framebuffer memory - // as write-combining using the PAT (on x86 only). - // If PAT isn't available, fall back to disabling caching altogether. - let mut flags: PteFlagsArch = PteFlags::new() - .valid(true) - .writable(true) - .into(); - - #[cfg(target_arch = "x86_64")] { - if page_attribute_table::is_supported() { - flags = flags.pat_index( - page_attribute_table::MemoryCachingType::WriteCombining.pat_slot_index() - ); - info!("Using PAT write-combining mapping for real physical framebuffer memory"); - } else { - flags = flags.device_memory(true); - info!("Falling back to cache-disable mapping for real physical framebuffer memory"); - } - } - #[cfg(not(target_arch = "x86_64"))] { - flags = flags.device_memory(true); - } - - let frames = memory::allocate_frames_by_bytes_at(address, size) - .map_err(|_e| "Couldn't allocate frames for the final framebuffer")?; - let fb_mp = kernel_mmi_ref.lock().page_table.map_allocated_pages_to( - pages, - frames, - flags, - )?; - debug!("Mapped real physical framebuffer: {fb_mp:?}"); - fb_mp - } else { - kernel_mmi_ref.lock().page_table.map_allocated_pages( - pages, - PteFlags::new().valid(true).writable(true), - )? - }; - - Ok(Framebuffer { - width, - height, - buffer: mapped_framebuffer.into_borrowed_slice_mut(0, width * height) - .map_err(|(|_mp, s)| s)?, - }) - } - - /// Returns a mutable reference to this framebuffer's memory as a slice of pixels. - pub fn buffer_mut(&mut self) -> &mut [P] { - &mut self.buffer - } - - /// Returns a reference to this framebuffer's memory as a slice of pixels. - pub fn buffer(&self) -> &[P] { - &self.buffer - } - - /// Returns the `(width, height)` of this framebuffer. - pub fn get_size(&self) -> (usize, usize) { - (self.width, self.height) - } - - /// Composites `src` to the buffer starting from `index`. - pub fn composite_buffer(&mut self, src: &[P], index: usize) { - let len = src.len(); - let dest_end = index + len; - Pixel::composite_buffer(src, &mut self.buffer_mut()[index..dest_end]); - } - - /// Draw a pixel at the given coordinate. - /// The `pixel` will be blended with the existing pixel value - /// at that `coordinate` in this framebuffer. - pub fn draw_pixel(&mut self, coordinate: Coord, pixel: P) { - if let Some(index) = self.index_of(coordinate) { - self.buffer[index] = pixel.blend(self.buffer[index]).into(); - } - } - - /// Overwites a pixel at the given coordinate in this framebuffer - /// instead of blending it like [`draw_pixel`](#method.draw_pixel). - pub fn overwrite_pixel(&mut self, coordinate: Coord, pixel: P) { - self.draw_pixel(coordinate, pixel) - } - - /// Returns the pixel value at the given `coordinate` in this framebuffer. - pub fn get_pixel(&self, coordinate: Coord) -> Option

{ - self.index_of(coordinate).map(|i| self.buffer[i]) - } - - /// Fills (overwrites) the entire framebuffer with the given `pixel` value. - pub fn fill(&mut self, pixel: P) { - for p in self.buffer.deref_mut() { - *p = pixel; - } - } - - /// Returns the index of the given `coordinate` in this framebuffer, - /// if this framebuffer [`contains`](#method.contains) the `coordinate` within its bounds. - pub fn index_of(&self, coordinate: Coord) -> Option { - if self.contains(coordinate) { - Some((self.width * coordinate.y as usize) + coordinate.x as usize) - } else { - None - } - } - - /// Checks if the given `coordinate` is within the framebuffer's bounds. - /// The `coordinate` is relative to the origin coordinate of `(0, 0)` being the top-left point of the framebuffer. - pub fn contains(&self, coordinate: Coord) -> bool { - coordinate.x >= 0 - && coordinate.x < (self.width as isize) - && coordinate.y >= 0 - && coordinate.y < (self.height as isize) - } - - /// Checks if a framebuffer overlaps with an area. - /// # Arguments - /// * `coordinate`: the top-left corner of the area relative to the origin(top-left point) of the framebuffer. - /// * `width`: the width of the area in number of pixels. - /// * `height`: the height of the area in number of pixels. - pub fn overlaps_with(&mut self, coordinate: Coord, width: usize, height: usize) -> bool { - coordinate.x < self.width as isize - && coordinate.x + width as isize >= 0 - && coordinate.y < self.height as isize - && coordinate.y + height as isize >= 0 - } - -} diff --git a/kernel/framebuffer/src/pixel.rs b/kernel/framebuffer/src/pixel.rs deleted file mode 100644 index b6e8567be2..0000000000 --- a/kernel/framebuffer/src/pixel.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! Defines the `Pixel` trait as well as basic pixel formats, like RBG/RBGA. - -use core::hash::Hash; -use color::Color; -use zerocopy::FromBytes; - -/// A pixel provides methods to blend with others. -pub trait Pixel: Copy + Hash + FromBytes { - /// Composites the `src` pixel slice to the `dest` pixel slice. - fn composite_buffer(src: &[Self], dest: &mut[Self]); - - /// blend with another pixel considering their extra channel. - fn blend(self, other: Self) -> Self; - - /// Blend two pixels linearly with weights, as `blend` for `origin` and (1-`blend`) for `other`. - fn weight_blend(origin: Self, other: Self, blend: f32) -> Self; -} - - -#[derive(Hash, Debug, Clone, Copy, FromBytes)] -/// An RGB Pixel is a pixel with no extra channel. -pub struct RGBPixel { - pub blue: u8, - pub green: u8, - pub red: u8, - _channel: u8, -} - -#[derive(Hash, Debug, Clone, Copy, FromBytes)] -/// An Alpha Pixel is a pixel with an alpha channel -pub struct AlphaPixel { - pub blue: u8, - pub green: u8, - pub red: u8, - pub alpha: u8 -} - -impl Pixel for RGBPixel { - #[inline] - fn composite_buffer(src: &[Self], dest: &mut[Self]) { - dest.copy_from_slice(src) - } - - #[inline] - fn blend(self, _other: Self) -> Self { - self - } - - fn weight_blend(origin: Self, other: Self, blend: f32) -> Self { - let blend = if blend < 0f32 { - 0f32 - } else if blend > 1f32 { - 1f32 - } else { - blend - }; - - let new_red = - ((origin.red as f32) * blend + (other.red as f32) * (1f32 - blend)) as u8; - let new_green = - ((origin.green as f32) * blend + (other.green as f32) * (1f32 - blend)) as u8; - let new_blue = - ((origin.blue as f32) * blend + (other.blue as f32) * (1f32 - blend)) as u8; - - RGBPixel{ - _channel: 0, - red: new_red, - green: new_green, - blue: new_blue - } - } -} - -impl From for RGBPixel { - fn from(color: Color) -> Self { - RGBPixel { - _channel: 0, - red: color.red(), - green: color.green(), - blue: color.blue(), - } - } -} - -impl Pixel for AlphaPixel { - fn composite_buffer(src: &[Self], dest: &mut[Self]) { - for i in 0..src.len() { - dest[i] = AlphaPixel::from(src[i]).blend(AlphaPixel::from(dest[i])).into(); - } - } - - fn blend(self, other: Self) -> Self { - let alpha = self.alpha as u16; - let red = self.red; - let green = self.green; - let blue = self.blue; - // let ori_alpha = other.alpha; - let ori_red = other.red; - let ori_green = other.green; - let ori_blue = other.blue; - // let new_alpha = (((alpha as u16) * (255 - alpha) + (ori_alpha as u16) * alpha) / 255) as u8; - let new_red = (((red as u16) * (255 - alpha) + (ori_red as u16) * alpha) / 255) as u8; - let new_green = (((green as u16) * (255 - alpha) + (ori_green as u16) * alpha) / 255) as u8; - let new_blue = (((blue as u16) * (255 - alpha) + (ori_blue as u16) * alpha) / 255) as u8; - AlphaPixel { - alpha: alpha as u8, - red: new_red, - green: new_green, - blue: new_blue - } - } - - fn weight_blend(origin: Self, other: Self, blend: f32) -> Self { - let blend = if blend < 0f32 { - 0f32 - } else if blend > 1f32 { - 1f32 - } else { - blend - }; - - let new_channel = - ((origin.alpha as f32) * blend + (other.alpha as f32) * (1f32 - blend)) as u8; - let new_red = - ((origin.red as f32) * blend + (other.red as f32) * (1f32 - blend)) as u8; - let new_green = - ((origin.green as f32) * blend + (other.green as f32) * (1f32 - blend)) as u8; - let new_blue = - ((origin.blue as f32) * blend + (other.blue as f32) * (1f32 - blend)) as u8; - AlphaPixel { - alpha: new_channel, - red: new_red, - green: new_green, - blue: new_blue - } - } -} - -impl From for AlphaPixel { - fn from(color: Color) -> Self { - AlphaPixel { - alpha: color.transparency(), - red: color.red(), - green: color.green(), - blue: color.blue(), - } - } -} diff --git a/kernel/framebuffer_compositor/Cargo.toml b/kernel/framebuffer_compositor/Cargo.toml deleted file mode 100644 index e2e86228be..0000000000 --- a/kernel/framebuffer_compositor/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "framebuffer_compositor" -version = "0.1.0" -authors = ["Wenqiu Yu "] -description = "the framebuffer compositor composites multiple source framebuffers into one destination framebuffer" - -[dependencies] -spin = "0.9.4" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.shapes] -path = "../shapes" - -[dependencies.compositor] -path = "../compositor" - -[dependencies.hashbrown] -version = "0.11.2" -features = ["nightly"] - -[lib] -crate-type = ["rlib"] diff --git a/kernel/framebuffer_compositor/src/lib.rs b/kernel/framebuffer_compositor/src/lib.rs deleted file mode 100644 index 102f86a15b..0000000000 --- a/kernel/framebuffer_compositor/src/lib.rs +++ /dev/null @@ -1,275 +0,0 @@ -//! This crate defines a framebuffer compositor. -//! -//! A framebuffer compositor composites a list of framebuffers into a single destination framebuffer. -//! The coordinate system within a framebuffer is expressed relative to its origin, i.e., the top-left point. -//! -//! # Cache -//! The compositor caches groups of framebuffer rows for better performance. -//! -//! First, it divides each framebuffer into ranges of rows called "blocks" which are `CACHE_BLOCK_HEIGHT` rows in height, -//! and deals with these row ranges one by one. -//! The pixels in each block's row range are a contiguous array of length `CACHE_BLOCK_HEIGHT * framebuffer_width`, -//! and the cache key is the hash value of that pixel array. -//! -//! In the next step, for every `CACHE_BLOCK_HEIGHT` rows, the compositor checks if the pixel array is are already cached. -//! It ignores row ranges that do not overlap with the given bounding box to be updated. -//! If a pixel array is not cached, the compositor will refresh the pixels within the bounding box and cache those `CACHE_BLOCK_HEIGHT` rows. -//! -//! In order to cache a range of rows from the source framebuffer, the compositor needs to cache its contents, its location in the destination framebuffer, and its size. -//! The cache is basically a rectangular region in the destination framebuffer, and we define the structure `CacheBlock` to represent that cached region. - -#![no_std] - -extern crate alloc; -extern crate compositor; -extern crate framebuffer; -extern crate spin; -extern crate hashbrown; -extern crate shapes; - -use alloc::collections::BTreeMap; -use alloc::vec::{Vec}; -use core::hash::{Hash, Hasher, BuildHasher}; -use hashbrown::hash_map::{DefaultHashBuilder}; -use compositor::{Compositor, FramebufferUpdates, CompositableRegion}; -use framebuffer::{Framebuffer, Pixel}; -use shapes::{Coord, Rectangle}; -use spin::Mutex; -use core::ops::Range; - -/// The height of a cache block. In every iteration the compositor will deal with groups of 16 rows and cache them. -pub const CACHE_BLOCK_HEIGHT: usize = 16; - -/// The instance of the framebuffer compositor. -pub static FRAME_COMPOSITOR: Mutex = Mutex::new( - FrameCompositor{ - caches: BTreeMap::new() - } -); - -/// A `CacheBlock` represents the cached (previously-composited) content of a range of rows in the source framebuffer. -/// It specifies the rectangular region in the destination framebuffer and the hash. -/// Once cached, a `CacheBlock` block is independent of the source framebuffer it came from. -/// `content_hash` is the hash value of the actual pixel contents in the cached block. A cache block is identical to some new framebuffer rows to be updated if they share the same `content_hash`, location and width. -pub struct CacheBlock { - /// The rectanglular region in the destination framebuffer occupied by the cached rows in the source framebuffer. - /// We need this information because if an old cache block overlaps with some new framebuffer rows to be updated, - /// the compositor should remove the old one since part of that region will change. - block: Rectangle, - /// The hash value of the actual pixel contents in the cached block. - content_hash: u64, -} - -impl CacheBlock { - /// Checks if a cache block overlaps with another one - pub fn overlaps_with(&self, cache: &CacheBlock) -> bool { - self.contains_corner(cache) || cache.contains_corner(self) - } - - /// checks if the coordinate is within the block - fn contains(&self, coordinate: Coord) -> bool { - return coordinate.x >= self.block.top_left.x - && coordinate.x < self.block.bottom_right.x - && coordinate.y >= self.block.top_left.y - && coordinate.y < self.block.bottom_right.y - } - - /// checks if this block contains any of the four corners of another cache block. - fn contains_corner(&self, cache: &CacheBlock) -> bool { - self.contains(cache.block.top_left) - || self.contains(cache.block.top_left + (cache.block.bottom_right.x - cache.block.top_left.x - 1, 0)) - || self.contains(cache.block.top_left + (0, cache.block.bottom_right.y - cache.block.top_left.y - 1)) - || self.contains(cache.block.bottom_right - (1, 1)) - } -} - -/// The framebuffer compositor structure. -/// It caches framebuffer rows since last update as soft states for better performance. -pub struct FrameCompositor { - // Cache of updated framebuffers before - caches: BTreeMap, -} - -impl FrameCompositor { - /// Checks if some rows of a framebuffer are cached. - /// # Arguments - /// * `row_pixels`: the continuous pixels in the rows. - /// * `dest_coord`: the location of the first pixel in the destination framebuffer. - /// * `width`: the width of the rows - /// - fn is_cached(&self, row_pixels: &[P], dest_coord: &Coord, width: usize) -> bool { - match self.caches.get(dest_coord) { - Some(cache) => { - // The same hash and width means the cache block is identical to the row pixels. - // We do not check the height because if the hashes are the same, the number of pixels, namely `width * height` must be the same. - return cache.content_hash == hash(row_pixels) && (cache.block.bottom_right.x - cache.block.top_left.x) as usize == width - } - None => return false, - } - } - - /// This function will return true if several continuous rows in the framebuffer are cached. - /// If false, i.e. the given `row_range` is not in the cache, this function will remove - /// the old cached blocks that overlap with the rows in the given `src_fb_row_range` and cache those rows as a new cache block. - /// # Arguments - /// * `src_fb`: the updated source framebuffer. - /// * `dest_coord`: the position of the source framebuffer (its top-left corner) relative to the destination framebuffer's top-left corner. - /// * `src_fb_row_range`: the range of rows in the source framebuffer to check and cache. - fn check_and_cache( - &mut self, - src_fb: &Framebuffer

, - dest_coord: Coord, - src_fb_row_range: &Range, - ) -> Result { - let (src_width, src_height) = src_fb.get_size(); - let src_buffer_len = src_width * src_height; - - // The start pixel of the rows - let start_index = src_width * src_fb_row_range.start; - let coordinate_start = dest_coord + (0, src_fb_row_range.start as isize); - - // The end pixel of the rows - let end_index = src_width * src_fb_row_range.end; - - let pixel_slice = &src_fb.buffer()[start_index..core::cmp::min(end_index, src_buffer_len)]; - - // Skip if the rows are already cached - if self.is_cached(&pixel_slice, &coordinate_start, src_width) { - return Ok(true); - } - - // remove overlapped caches - let new_cache = CacheBlock { - block: Rectangle { - top_left: coordinate_start, - bottom_right: coordinate_start + (src_width as isize, (pixel_slice.len() / src_width) as isize) - }, - content_hash: hash(pixel_slice), - }; - let keys: Vec<_> = self.caches.keys().cloned().collect(); - for key in keys { - if let Some(cache) = self.caches.get_mut(&key) { - if cache.overlaps_with(&new_cache) { - self.caches.remove(&key); - } - }; - } - - self.caches.insert(coordinate_start, new_cache); - Ok(false) - } - - /// Returns the range of rows in the source framebuffer that were (1) previously cached as cache blocks - /// and (2) overlap with the given `dest_bounding_box`. - /// This methods extends the row range of the given bounding box because the compositor deals with chunks of `CACHE_BLOCK_HEIGHT` rows. - /// # Arguments - /// * `dest_coord`: the position in the destination framebuffer (relative to its top-left corner) - /// to where the source framebuffer will be composited. - /// * `dest_bounding_box`: the region of the destination framebuffer that should be composited. - /// * `src_fb_height`: the height of the source framebuffer. - fn get_cache_row_range( - &self, - dest_coord: Coord, - dest_bounding_box: &B, - src_fb_height: usize, - ) -> Range { - let abs_row_range = dest_bounding_box.row_range(); - let mut relative_row_start = abs_row_range.start - dest_coord.y; - let mut relative_row_end = abs_row_range.end - dest_coord.y; - - relative_row_start = core::cmp::max(relative_row_start, 0); - relative_row_end = core::cmp::min(relative_row_end, src_fb_height as isize); - - if relative_row_start >= relative_row_end { - return 0..0; - } - - let cache_row_start = relative_row_start as usize / CACHE_BLOCK_HEIGHT * CACHE_BLOCK_HEIGHT; - let mut cache_row_end = ((relative_row_end - 1) as usize / CACHE_BLOCK_HEIGHT + 1) * CACHE_BLOCK_HEIGHT; - - cache_row_end = core::cmp::min(cache_row_end, src_fb_height); - - return cache_row_start..cache_row_end - } - -} - -impl Compositor for FrameCompositor { - fn composite<'a, B: CompositableRegion + Clone, P: 'a + Pixel>( - &mut self, - src_fbs: impl IntoIterator>, - dest_fb: &mut Framebuffer

, - dest_bounding_boxes: impl IntoIterator + Clone, - ) -> Result<(), &'static str> { - let mut box_iter = dest_bounding_boxes.clone().into_iter(); - if box_iter.next().is_none() { - for framebuffer_updates in src_fbs.into_iter() { - let src_fb = framebuffer_updates.src_framebuffer; - let coordinate = framebuffer_updates.coordinate_in_dest_framebuffer; - // Update the whole screen if the caller does not specify the blocks - let (src_width, src_height) = framebuffer_updates.src_framebuffer.get_size(); - // let block_number = (src_height - 1) / CACHE_BLOCK_HEIGHT + 1; - let area = Rectangle { - top_left: coordinate, - bottom_right: coordinate + (src_width as isize, src_height as isize) - }; - let mut row_start = 0; - loop { - if row_start >= src_height { - break; - } - let cache_range = row_start..(row_start + CACHE_BLOCK_HEIGHT); - if !self.check_and_cache(src_fb, coordinate, &cache_range)? { - area.blend_buffers( - src_fb, - dest_fb, - coordinate, - cache_range, - )?; - } - row_start += CACHE_BLOCK_HEIGHT; - } - } - } else { - for framebuffer_updates in src_fbs.into_iter() { - //let mut updated_blocks = Vec::new(); - for bounding_box in dest_bounding_boxes.clone() { - let src_fb = framebuffer_updates.src_framebuffer; - let coordinate = framebuffer_updates.coordinate_in_dest_framebuffer; - let (_, height) = src_fb.get_size(); - let mut row_range = self.get_cache_row_range(coordinate, &bounding_box, height); - // let cache_block_size = CACHE_BLOCK_HEIGHT * width; - - loop { - if row_range.start >= row_range.end { - break; - } - let cache_range = row_range.start..(row_range.start + CACHE_BLOCK_HEIGHT); - // check cache if the bounding box is not a single pixel - if bounding_box.size() > 1 && self.check_and_cache(src_fb, coordinate, &cache_range)? { - row_range.start += CACHE_BLOCK_HEIGHT; - continue; - }; - bounding_box.blend_buffers( - src_fb, - dest_fb, - coordinate, - cache_range, - )?; - row_range.start += CACHE_BLOCK_HEIGHT; - } - } - } - } - - Ok(()) - } -} - -/// Gets the hash of an item -fn hash(item: T) -> u64 { - let builder = DefaultHashBuilder::default(); - let mut hasher = builder.build_hasher(); - item.hash(&mut hasher); - hasher.finish() -} diff --git a/kernel/framebuffer_drawer/Cargo.toml b/kernel/framebuffer_drawer/Cargo.toml deleted file mode 100644 index 605475e224..0000000000 --- a/kernel/framebuffer_drawer/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "framebuffer_drawer" -version = "0.1.0" -authors = ["Wenqiu Yu "] -description = "basic draw interfaces" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.shapes] -path = "../shapes" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/framebuffer_drawer/src/lib.rs b/kernel/framebuffer_drawer/src/lib.rs deleted file mode 100644 index 52f1629030..0000000000 --- a/kernel/framebuffer_drawer/src/lib.rs +++ /dev/null @@ -1,193 +0,0 @@ -//! This crate contains a series of basic draw functions to draw onto a framebuffer. -//! Displayables invoke these basic functions to display themselves onto a framebuffer. -//! The coordinate in these interfaces is relative to the origin(top-left point) of the framebuffer. - -#![no_std] - -extern crate framebuffer; -extern crate shapes; - -use framebuffer::{Framebuffer, Pixel}; -use shapes::Coord; - -/// Draws a line in a framebuffer. The part exceeding the boundary of the framebuffer will be ignored. -/// # Arguments -/// * `framebuffer`: the framebuffer to draw in. -/// * `start`: the start coordinate of the line relative to the origin(top-left point) of the framebuffer. -/// * `end`: the end coordinate of the line relative to the origin(top-left point) of the framebuffer. -/// * `color`: the color of the line. -pub fn draw_line( - framebuffer: &mut Framebuffer

, - start: Coord, - end: Coord, - pixel: P, -) { - let width: isize = end.x - start.x; - let height: isize = end.y - start.y; - - let mut line_in_buffer = false; - - // compare the x distance and y distance. Increase/Decrease the longer one at every step. - if width.abs() > height.abs() { - let mut y; - let mut x = start.x; - - // if the end.x is larger than start.x, increase x in the loop. Otherwise decrease it. - let step = if width > 0 { 1 } else { -1 }; - loop { - if x == end.x { - break; - } - y = (x - start.x) * height / width + start.y; - let coordinate = Coord::new(x, y); - if framebuffer.contains(coordinate) { - line_in_buffer = true; - framebuffer.draw_pixel(coordinate, pixel); - } else if line_in_buffer { - // the part exceeds the buffer will be ignored - break; - } - x += step; - } - } else { - let mut x; - let mut y = start.y; - let step = if height > 0 { 1 } else { -1 }; - loop { - if y == end.y { - break; - } - x = (y - start.y) * width / height + start.x; - let coordinate = Coord::new(x, y); - if framebuffer.contains(coordinate) { - line_in_buffer = true; - framebuffer.draw_pixel(coordinate, pixel); - } else if line_in_buffer { - // the part exceeds the buffer will be ignored - break; - } - y += step; - } - } -} - -/// Draws a rectangle in a framebuffer. -/// The part exceeding the boundary of the framebuffer will be ignored. -/// # Arguments -/// * `framebuffer`: the framebuffer to draw in. -/// * `coordinate`: the left top coordinate of the rectangle relative to the origin(top-left point) of the framebuffer. -/// * `width`: the width of the rectangle in number of pixels. -/// * `height`: the height of the rectangle in number of pixels. -/// * `color`: the color of the rectangle's border. -pub fn draw_rectangle( - framebuffer: &mut Framebuffer

, - coordinate: Coord, - width: usize, - height: usize, - pixel: P, -) { - let (buffer_width, buffer_height) = framebuffer.get_size(); - - // return if the rectangle is not within the framebuffer - if !framebuffer.overlaps_with(coordinate, width, height){ - return - } - - // draw the part within the framebuffer - let start_x = core::cmp::max(coordinate.x, 0); - let start_y = core::cmp::max(coordinate.y, 0); - let end_x = core::cmp::min(coordinate.x + width as isize, buffer_width as isize); - let end_y = core::cmp::min(coordinate.y + height as isize, buffer_height as isize); - - // draw the four lines of the rectangle. - let mut top = Coord::new(start_x, start_y); - let end_y_offset = end_y - start_y - 1; - loop { - if top.x == end_x { - break; - } - if coordinate.y >= 0 { - framebuffer.draw_pixel(top, pixel); - } - if (coordinate.y + height as isize) < buffer_height as isize { - framebuffer.draw_pixel(top + (0, end_y_offset), pixel); - } - top.x += 1; - } - - let mut left = Coord::new(start_x, start_y); - let end_x_offset = end_x - start_x - 1; - loop { - if left.y == end_y { - break; - } - if coordinate.x >= 0 { - framebuffer.draw_pixel(left, pixel); - } - if (coordinate.x + width as isize) < buffer_width as isize { - framebuffer.draw_pixel(left + (end_x_offset, 0), pixel); - } - left.y += 1; - } -} - -/// Fills a rectangle in a framebuffer with color. -/// The part exceeding the boundary of the framebuffer will be ignored. -/// # Arguments -/// * `framebuffer`: the framebuffer to draw in. -/// * `coordinate`: the left top coordinate of the retangle relative to the origin(top-left point) of the framebuffer. -/// * `width`: the width of the rectangle in number of pixels. -/// * `height`: the height of the rectangle in number of pixels. -/// * `pixel`: the value of pixels in the rectangle. -pub fn fill_rectangle( - framebuffer: &mut Framebuffer

, - coordinate: Coord, - width: usize, - height: usize, - pixel: P, -) { - let (buffer_width, buffer_height) = framebuffer.get_size(); - // return if the rectangle is not within the framebuffer - if !framebuffer.overlaps_with(coordinate, width, height){ - return - } - - // draw the part within the framebuffer - let start_x = core::cmp::max(coordinate.x, 0); - let start_y = core::cmp::max(coordinate.y, 0); - let end_x = core::cmp::min(coordinate.x + width as isize, buffer_width as isize); - let end_y = core::cmp::min(coordinate.y + height as isize, buffer_height as isize); - - // draw every pixel line by line - let mut coordinate = Coord::new(start_x, start_y); - loop { - loop { - framebuffer.draw_pixel(coordinate, pixel); - coordinate.x += 1; - if coordinate.x == end_x { - break; - } - } - coordinate.y += 1; - if coordinate.y == end_y { - break; - } - coordinate.x = start_x; - } -} - -/// Draw a circle in the framebuffer. `coordinate` is the position of the center of the circle relative to the top-left corner of the framebuffer and `r` is the radius -pub fn draw_circle(framebuffer: &mut Framebuffer

, center: Coord, r: usize, pixel: P) { - let r2 = (r * r) as isize; - for y in center.y - r as isize..center.y + r as isize { - for x in center.x - r as isize..center.x + r as isize { - let coordinate = Coord::new(x, y); - if framebuffer.contains(coordinate) { - let d = coordinate - center; - if d.x * d.x + d.y * d.y <= r2 { - framebuffer.draw_pixel(coordinate, pixel); - } - } - } - } -} \ No newline at end of file diff --git a/kernel/framebuffer_printer/Cargo.toml b/kernel/framebuffer_printer/Cargo.toml deleted file mode 100644 index 779225c625..0000000000 --- a/kernel/framebuffer_printer/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "framebuffer_printer" -version = "0.1.0" -authors = ["Wenqiu Yu "] -description = "print a string in a framebuffer" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.font] -path = "../font" - -[dependencies.shapes] -path = "../shapes" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/framebuffer_printer/src/lib.rs b/kernel/framebuffer_printer/src/lib.rs deleted file mode 100644 index 79e0516d6a..0000000000 --- a/kernel/framebuffer_printer/src/lib.rs +++ /dev/null @@ -1,234 +0,0 @@ -//! This crate contains functions to print strings in a framebuffer. -//! The coordinate in these functions is relative to the origin(top-left point) of the framebuffer. - -#![no_std] - -extern crate alloc; -extern crate font; -extern crate framebuffer; -extern crate shapes; - -use alloc::vec; -use font::{CHARACTER_HEIGHT, CHARACTER_WIDTH}; -use framebuffer::{Framebuffer, Pixel}; -use shapes::{Coord, Rectangle}; - - -type ASCII = u8; - -/// Prints a string in a framebuffer. -/// Returns (column, line, rectangle), i.e. the position of the next symbol and an rectangle which covers the updated area. -/// A block item (index, width) represents the index of line number and the width of charaters in this line as pixels. It can be viewed as a framebuffer block which is described in the `framebuffer_compositor` crate. -/// # Arguments -/// * `framebuffer`: the framebuffer to display in. -/// * `coordinate`: the left top coordinate of the text block relative to the origin(top-left point) of the framebuffer. -/// * `width`, `height`: the size of the text block in number of pixels. -/// * `slice`: the string to display. -/// * `fg_pixel`: the value of pixels in the foreground. -/// * `bg_pixel` the value of pixels in the background. -/// * `column`, `line`: the location of the text in the text block in number of characters. -pub fn print_string( - framebuffer: &mut Framebuffer

, - coordinate: Coord, - width: usize, - height: usize, - slice: &str, - fg_pixel: P, - bg_pixel: P, - column: usize, - line: usize, -) -> (usize, usize, Rectangle) { - let buffer_width = width / CHARACTER_WIDTH; - let buffer_height = height / CHARACTER_HEIGHT; - let (x, y) = (coordinate.x, coordinate.y); - - let mut curr_line = line; - let mut curr_column = column; - - let top_left = Coord::new(0, (curr_line * CHARACTER_HEIGHT) as isize); - - for byte in slice.bytes() { - if byte == b'\n' { - let mut blank = Rectangle { - top_left: Coord::new( - coordinate.x + (curr_column * CHARACTER_WIDTH) as isize, - coordinate.y + (curr_line * CHARACTER_HEIGHT) as isize, - ), - bottom_right: Coord::new( - coordinate.x + width as isize, - coordinate.y + ((curr_line + 1) * CHARACTER_HEIGHT) as isize, - ) - }; - // fill the remaining blank of current line and go to the next line - fill_blank( - framebuffer, - &mut blank, - bg_pixel, - ); - curr_column = 0; - curr_line += 1; - if curr_line == buffer_height { - break; - } - } else { - if curr_column == buffer_width { - curr_column = 0; - curr_line += 1; - if curr_line == buffer_height { - break; - } - } - // print the next character - print_ascii_character( - framebuffer, - byte, - fg_pixel, - bg_pixel, - coordinate, - curr_column, - curr_line, - ); - curr_column += 1; - } - } - - let mut blank = Rectangle { - top_left: Coord::new( - x + (curr_column * CHARACTER_WIDTH) as isize, - y + (curr_line * CHARACTER_HEIGHT) as isize, - ), - bottom_right: Coord::new( - x + width as isize, - y + ((curr_line + 1) * CHARACTER_HEIGHT) as isize, - ) - }; - // fill the blank of the last line - fill_blank( - framebuffer, - &mut blank, - bg_pixel, - ); - - let bottom_right = Coord::new( - (buffer_width * CHARACTER_WIDTH) as isize, - ((curr_line + 1) * CHARACTER_HEIGHT) as isize - ); - - let update_area = Rectangle { - top_left: top_left, - bottom_right: bottom_right, - }; - - // fill the blank of the remaining part - blank = Rectangle { - top_left: Coord::new( - x, - y + ((curr_line + 1) * CHARACTER_HEIGHT) as isize, - ), - bottom_right: Coord::new( - x + width as isize, - y + height as isize, - ) - }; - fill_blank( - framebuffer, - &mut blank, - bg_pixel, - ); - - // return the position of next symbol and updated blocks. - (curr_column, curr_line, update_area) -} - -/// Prints a character to the framebuffer at position (line, column) of all characters in the text area. -/// # Arguments -/// * `framebuffer`: the framebuffer to display in. -/// * `character`: the ASCII code of the character to display. -/// * `fg_pixel`: the value of every pixel in the character. -/// * `bg_color`: the value of every pixel in the background. -/// * `coordinate`: the left top coordinate of the text block relative to the origin(top-left point) of the framebuffer. -/// * `column`, `line`: the location of the character in the text block as symbols. -pub fn print_ascii_character( - framebuffer: &mut Framebuffer

, - character: ASCII, - fg_pixel: P, - bg_pixel: P, - coordinate: Coord, - column: usize, - line: usize, -) { - let start = coordinate + ((column * CHARACTER_WIDTH) as isize, (line * CHARACTER_HEIGHT) as isize); - if !framebuffer.overlaps_with(start, CHARACTER_WIDTH, CHARACTER_HEIGHT) { - return - } - // print from the offset within the framebuffer - let (buffer_width, buffer_height) = framebuffer.get_size(); - let off_set_x: usize = if start.x < 0 { -(start.x) as usize } else { 0 }; - let off_set_y: usize = if start.y < 0 { -(start.y) as usize } else { 0 }; - let mut j = off_set_x; - let mut i = off_set_y; - loop { - let coordinate = start + (j as isize, i as isize); - if framebuffer.contains(coordinate) { - let pixel = if j >= 1 { - // leave 1 pixel gap between two characters - let index = j - 1; - let char_font = font::FONT_BASIC[character as usize][i]; - if get_bit(char_font, index) != 0 { - fg_pixel - } else { - bg_pixel - } - } else { - bg_pixel - }; - framebuffer.draw_pixel(coordinate, pixel); - } - j += 1; - if j == CHARACTER_WIDTH || start.x + j as isize == buffer_width as isize { - i += 1; - if i == CHARACTER_HEIGHT || start.y + i as isize == buffer_height as isize { - return - } - j = off_set_x; - } - } -} - -/// Fill a blank text area (left, top, right, bottom) with color. The tuple specifies the location of the area relative to the origin(top-left point) of the framebuffer. -pub fn fill_blank( - framebuffer: &mut Framebuffer

, - blank: &mut Rectangle, - pixel: P, -) { - - let (width, height) = framebuffer.get_size(); - // fill the part within the framebuffer - blank.top_left.x = core::cmp::max(0, blank.top_left.x); - blank.top_left.y = core::cmp::max(0, blank.top_left.y); - blank.bottom_right.x = core::cmp::min(blank.bottom_right.x, width as isize); - blank.bottom_right.y = core::cmp::min(blank.bottom_right.y, height as isize); - - if blank.top_left.x >= blank.bottom_right.x || - blank.top_left.y >= blank.bottom_right.y { - return - } - - let fill = vec![pixel; (blank.bottom_right.x - blank.top_left.x) as usize]; - let mut coordinate = blank.top_left; - loop { - if coordinate.y == blank.bottom_right.y { - return - } - if let Some(start) = framebuffer.index_of(coordinate) { - framebuffer.composite_buffer(&fill, start); - } - coordinate.y += 1; - } -} - -/// Gets the i_th most significant bit of `char_font`. The returned value is `1` or `0`. -fn get_bit(char_font: u8, i: usize) -> u8 { - char_font & (0x80 >> i) -} - diff --git a/kernel/libterm/Cargo.toml b/kernel/libterm/Cargo.toml index c41be99b8c..15524c7b2f 100644 --- a/kernel/libterm/Cargo.toml +++ b/kernel/libterm/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Andrew Pham ", "Zhiyao Ma "] [dependencies] - +spin = "0.9.0" [dependencies.log] version = "0.4.8" @@ -24,29 +24,8 @@ path = "../color" [dependencies.event_types] path = "../event_types" -[dependencies.text_display] -path = "../displayable/text_display" - -[dependencies.displayable] -path = "../displayable" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.shapes] -path = "../shapes" - -[dependencies.framebuffer_drawer] -path = "../framebuffer_drawer" - -[dependencies.framebuffer_printer] -path = "../framebuffer_printer" - -[dependencies.window_manager] -path = "../window_manager" - -[dependencies.window] -path = "../window" +[dependencies.porthole] +path = "../porthole" [dependencies.tsc] path = "../tsc" diff --git a/kernel/libterm/src/cursor.rs b/kernel/libterm/src/cursor.rs index 1a13c7fd4a..dbc552fdff 100644 --- a/kernel/libterm/src/cursor.rs +++ b/kernel/libterm/src/cursor.rs @@ -1,3 +1,4 @@ + use super::*; /// The cursor structure used in the terminal. @@ -69,51 +70,31 @@ impl Cursor { /// * `framebuffer`: the framebuffer to display the cursor in. /// /// Returns a bounding box which wraps the cursor. - pub fn display( + pub fn display( &mut self, - coordinate: Coord, + relative_pos: RelativePos, column: usize, line: usize, - framebuffer: &mut Framebuffer

, - ) -> Result where Color: Into

{ + window: &mut Window, + ) -> Result<(), &'static str> { if self.blink() { + let mut relative_pos = relative_pos; + relative_pos.x += (column * CHARACTER_WIDTH) as u32; + relative_pos.y += (line * CHARACTER_HEIGHT) as u32; if self.show() { - framebuffer_drawer::fill_rectangle( - framebuffer, - coordinate - + ( - (column * CHARACTER_WIDTH) as isize, - (line * CHARACTER_HEIGHT) as isize, - ) - + (0, 1), - CHARACTER_WIDTH, - CHARACTER_HEIGHT - 2, - self.color.into(), - ); + let mut rect = + Rect::new(CHARACTER_WIDTH, CHARACTER_HEIGHT - 1, relative_pos.x as isize, relative_pos.y as isize); + window.fill_rectangle(&mut rect, 0xF4F333); } else { - framebuffer_printer::print_ascii_character( - framebuffer, - self.underlying_char, - FONT_FOREGROUND_COLOR.into(), - FONT_BACKGROUND_COLOR.into(), - coordinate, - column, - line, - ) + window.print_string_line( + &relative_pos, + (self.underlying_char as char).to_string().as_str(), + 0xFBF1C7, + 0x3C3836, + )?; } } - - let top_left = coordinate - + ( - (column * CHARACTER_WIDTH) as isize, - (line * CHARACTER_HEIGHT) as isize, - ); - let bounding_box = Rectangle { - top_left: top_left, - bottom_right: top_left + (CHARACTER_WIDTH as isize, CHARACTER_HEIGHT as isize), - }; - - Ok(bounding_box) + Ok(()) } /// Sets the position of the cursor relative to the end of the command @@ -137,7 +118,7 @@ impl Cursor { } } -impl Default for Cursor { +impl Default for Cursor { fn default() -> Self { Cursor { enabled: true, @@ -150,4 +131,3 @@ impl Default for Cursor { } } } - diff --git a/kernel/libterm/src/lib.rs b/kernel/libterm/src/lib.rs index 7d032e25b9..cbcc9b30db 100644 --- a/kernel/libterm/src/lib.rs +++ b/kernel/libterm/src/lib.rs @@ -1,6 +1,6 @@ //! A basic terminal emulator library. //! -//! The terminal has several main responsibilities: +//! The terminal has several main responsibilities: //! * Managing the scrollback buffer, a string of characters that should be printed to the screen. //! * Determining which parts of that buffer should be displayed and using the window manager to do so. //! * Handling the command line user input. @@ -9,36 +9,32 @@ #![no_std] -#[macro_use] extern crate alloc; -#[macro_use] extern crate log; +#[macro_use] +extern crate alloc; +#[macro_use] +extern crate log; +extern crate color; extern crate dfqueue; extern crate environment; extern crate event_types; -extern crate displayable; extern crate font; -extern crate framebuffer; -extern crate framebuffer_drawer; -extern crate framebuffer_printer; +extern crate spin; extern crate tsc; -extern crate window_manager; -extern crate window; -extern crate text_display; -extern crate shapes; -extern crate color; +extern crate porthole; -use core::ops::DerefMut; use alloc::string::{String, ToString}; +use alloc::sync::Arc; use alloc::vec::Vec; +use color::Color; +use core::ops::DerefMut; use cursor::*; -use text_display::TextDisplay; -use displayable::Displayable; use event_types::Event; use font::{CHARACTER_HEIGHT, CHARACTER_WIDTH}; -use framebuffer::{Framebuffer, Pixel}; -use color::{Color}; -use shapes::{Coord, Rectangle}; +use spin::Mutex; +use porthole::units::*; +use porthole::window::*; + use tsc::{tsc_ticks, TscTicks}; -use window::Window; pub mod cursor; @@ -49,15 +45,15 @@ const DEFAULT_CURSOR_FREQ: u128 = 400000000; /// Error type for tracking different scroll errors that a terminal /// application could encounter. pub enum ScrollError { - /// Occurs when a index-calculation returns an index that is outside of the + /// Occurs when a index-calculation returns an index that is outside of the /// bounds of the scroll buffer - OffEndBound + OffEndBound, } /// An instance of a graphical terminal emulator. pub struct Terminal { /// The terminal's own window. - pub window: Window, + pub window: Arc>, /// The terminal's scrollback buffer which stores a string to be displayed by the text display scrollback_buffer: String, /// Indicates whether the text display is displaying the last part of the scrollback buffer slice @@ -65,7 +61,7 @@ pub struct Terminal { /// The starting index of the scrollback buffer string slice that is currently being displayed on the text display scroll_start_idx: usize, /// The text displayable which the terminal prints to. - text_display: TextDisplay, + text_display: TextDisplayInfo, /// The cursor of the terminal. pub cursor: Cursor, } @@ -74,16 +70,22 @@ pub struct Terminal { impl Terminal { /// Gets the width and height of the text displayable in number of characters. pub fn get_text_dimensions(&self) -> (usize, usize) { - self.text_display.get_dimensions() + let drawable_area = self.window.lock().drawable_area(); + let width = drawable_area.width; + let height = drawable_area.height; + ( + (width) / CHARACTER_WIDTH, + (height ) / CHARACTER_HEIGHT, + ) } /// This function takes in the end index of some index in the scrollback buffer and calculates the starting index of the - /// scrollback buffer so that a slice containing the starting and ending index would perfectly fit inside the dimensions of - /// text display. - /// If the text display's first line will display a continuation of a syntactical line in the scrollback buffer, this function + /// scrollback buffer so that a slice containing the starting and ending index would perfectly fit inside the dimensions of + /// text display. + /// If the text display's first line will display a continuation of a syntactical line in the scrollback buffer, this function /// calculates the starting index so that when displayed on the text display, it preserves that line so that it looks the same /// as if the whole physical line is displayed on the buffer - /// + /// /// Return: starting index of the string and the cursor position(with respect to position on the screen, not in the scrollback buffer) in that order fn calc_start_idx(&self, end_idx: usize) -> (usize, usize) { let (buffer_width, buffer_height) = self.get_text_dimensions(); @@ -91,7 +93,9 @@ impl Terminal { let result; // Grabs a max-size slice of the scrollback buffer (usually does not totally fit because of newlines) if end_idx > buffer_width * buffer_height { - result = self.scrollback_buffer.get(end_idx - buffer_width * buffer_height..end_idx); + result = self + .scrollback_buffer + .get(end_idx - buffer_width * buffer_height..end_idx); } else { result = self.scrollback_buffer.get(0..end_idx); } @@ -106,44 +110,46 @@ impl Terminal { return (0, end_idx); } else { start_idx -= buffer_height * buffer_width; // text with no newlines will fill the entire buffer - return (start_idx, buffer_height * buffer_width -1); + return (start_idx, buffer_height * buffer_width - 1); } } let mut last_line_chars = 0; // Case where the last newline does not occur at the end of the slice if new_line_indices[0].0 != slice.len() - 1 { - start_idx -= slice.len() -1 - new_line_indices[0].0; - total_lines += (slice.len()-1 - new_line_indices[0].0)/buffer_width + 1; - last_line_chars = (slice.len() -1 - new_line_indices[0].0) % buffer_width; // fix: account for more than one line - } - else { + start_idx -= slice.len() - 1 - new_line_indices[0].0; + total_lines += (slice.len() - 1 - new_line_indices[0].0) / buffer_width + 1; + last_line_chars = (slice.len() - 1 - new_line_indices[0].0) % buffer_width; + // fix: account for more than one line + } else { start_idx -= 1; total_lines += 1; } // covers everything *up to* the characters between the beginning of the slice and the first new line character - for i in 0..new_line_indices.len()-1 { + for i in 0..new_line_indices.len() - 1 { if total_lines >= buffer_height { break; } - let num_chars = new_line_indices[i].0 - new_line_indices[i+1].0; - let num_lines = if (num_chars-1)%buffer_width != 0 || (num_chars -1) == 0 { - (num_chars-1) / buffer_width + 1 - } else { - (num_chars-1)/buffer_width}; // using (num_chars -1) because that's the number of characters that actually show up on the screen - if num_chars > start_idx { // prevents subtraction overflow + let num_chars = new_line_indices[i].0 - new_line_indices[i + 1].0; + let num_lines = if (num_chars - 1) % buffer_width != 0 || (num_chars - 1) == 0 { + (num_chars - 1) / buffer_width + 1 + } else { + (num_chars - 1) / buffer_width + }; // using (num_chars -1) because that's the number of characters that actually show up on the screen + if num_chars > start_idx { + // prevents subtraction overflow return (0, total_lines * buffer_width + last_line_chars); - } + } start_idx -= num_chars; total_lines += num_lines; } // tracks the characters between the beginning of the slice and the first new line character - let first_chars = new_line_indices[new_line_indices.len() -1].0; - let first_chars_lines = first_chars/buffer_width + 1; + let first_chars = new_line_indices[new_line_indices.len() - 1].0; + let first_chars_lines = first_chars / buffer_width + 1; - // covers the case where the text inside the new_lines_indices array overflow the text buffer + // covers the case where the text inside the new_lines_indices array overflow the text buffer if total_lines > buffer_height { start_idx += (total_lines - buffer_height) * buffer_width; // adds back the overcounted lines to the starting index total_lines = buffer_height; @@ -162,16 +168,18 @@ impl Terminal { } // If the previous loop overcounted, this cuts off the excess string from string. Happens when there are many charcters between newlines at the beginning of the slice - return (start_idx, (total_lines - 1) * buffer_width + last_line_chars); - + return ( + start_idx, + (total_lines - 1) * buffer_width + last_line_chars, + ); } else { - return (0,0); /* WARNING: should change to Option<> rather than returning (0, 0) */ - } + return (0, 0); /* WARNING: should change to Option<> rather than returning (0, 0) */ + } } /// This function takes in the start index of some index in the scrollback buffer and calculates the end index of the - /// scrollback buffer so that a slice containing the starting and ending index would perfectly fit inside the dimensions of - /// text display. + /// scrollback buffer so that a slice containing the starting and ending index would perfectly fit inside the dimensions of + /// text display. fn calc_end_idx(&self, start_idx: usize) -> Result { let (buffer_width, buffer_height) = self.get_text_dimensions(); let scrollback_buffer_len = self.scrollback_buffer.len(); @@ -179,9 +187,13 @@ impl Terminal { let result; // Grabs a max-size slice of the scrollback buffer (usually does not totally fit because of newlines) if start_idx + buffer_width * buffer_height > scrollback_buffer_len { - result = self.scrollback_buffer.get(start_idx..scrollback_buffer_len-1); + result = self + .scrollback_buffer + .get(start_idx..scrollback_buffer_len - 1); } else { - result = self.scrollback_buffer.get(start_idx..start_idx + buffer_width * buffer_height); + result = self + .scrollback_buffer + .get(start_idx..start_idx + buffer_width * buffer_height); } // calculate the starting index for the slice @@ -193,36 +205,36 @@ impl Terminal { if new_line_indices.len() == 0 { // indicates that the text is just one continuous string with no newlines and will therefore fill the buffer completely end_idx += buffer_height * buffer_width; - if end_idx <= self.scrollback_buffer.len() -1 { - return Ok(end_idx); + if end_idx <= self.scrollback_buffer.len() - 1 { + return Ok(end_idx); } else { return Err(ScrollError::OffEndBound); } } let mut counter = 0; - // Covers the case where the start idx argument corresponds to a string that does not start on a newline + // Covers the case where the start idx argument corresponds to a string that does not start on a newline if new_line_indices[0].0 != 0 { end_idx += new_line_indices[0].0; - total_lines += new_line_indices[0].0/buffer_width + 1; + total_lines += new_line_indices[0].0 / buffer_width + 1; } // the characters between the last newline and the end of the slice - let last_line_chars = slice.len() -1 - new_line_indices[new_line_indices.len() -1].0; - let num_last_lines = last_line_chars%buffer_width + 1; // +1 to account for the physical line that the last characters will take up + let last_line_chars = slice.len() - 1 - new_line_indices[new_line_indices.len() - 1].0; + let num_last_lines = last_line_chars % buffer_width + 1; // +1 to account for the physical line that the last characters will take up - for i in 0..new_line_indices.len()-1 { + for i in 0..new_line_indices.len() - 1 { if total_lines >= buffer_height { break; } - let num_chars = new_line_indices[i+1].0 - new_line_indices[i].0; - let num_lines = num_chars/buffer_width + 1; + let num_chars = new_line_indices[i + 1].0 - new_line_indices[i].0; + let num_lines = num_chars / buffer_width + 1; end_idx += num_chars; total_lines += num_lines; counter += 1; } - // covers the case where the text inside the new_line_indices array overflows the text buffer capacity + // covers the case where the text inside the new_line_indices array overflows the text buffer capacity if total_lines > buffer_height { - let num_chars = new_line_indices[counter].0 - new_line_indices[counter -1].0; + let num_chars = new_line_indices[counter].0 - new_line_indices[counter - 1].0; end_idx -= num_chars; end_idx += buffer_width; // covers the case where the characters between the last newline and the end of the slice overflow the text buffer capacity @@ -234,13 +246,13 @@ impl Terminal { end_idx += last_line_chars; } - if end_idx <= self.scrollback_buffer.len() -1 { - return Ok(end_idx); + if end_idx <= self.scrollback_buffer.len() - 1 { + return Ok(end_idx); } else { return Err(ScrollError::OffEndBound); } } else { - return Ok(self.scrollback_buffer.len() - 1) /* WARNING: maybe should return Error? */ + return Ok(self.scrollback_buffer.len() - 1); /* WARNING: maybe should return Error? */ } } @@ -250,7 +262,7 @@ impl Terminal { let mut start_idx = self.scroll_start_idx; //indicates that the user has scrolled to the top of the page if start_idx < 1 { - return; + return; } else { start_idx -= 1; } @@ -258,19 +270,22 @@ impl Terminal { let result; let slice_len; if buffer_width < start_idx { - result = self.scrollback_buffer.as_str().get(start_idx - buffer_width .. start_idx); + result = self + .scrollback_buffer + .as_str() + .get(start_idx - buffer_width..start_idx); slice_len = buffer_width; } else { - result = self.scrollback_buffer.as_str().get(0 .. start_idx); + result = self.scrollback_buffer.as_str().get(0..start_idx); slice_len = start_idx; } // Searches this slice for a newline if let Some(slice) = result { - let index = slice.rfind('\n'); + let index = slice.rfind('\n'); new_start_idx = match index { - Some(index) => { start_idx - slice_len + index }, // Moves the starting index back to the position of the nearest newline back - None => { start_idx - slice_len}, // If no newline is found, moves the start index back by the buffer width value + Some(index) => start_idx - slice_len + index, // Moves the starting index back to the position of the nearest newline back + None => start_idx - slice_len, // If no newline is found, moves the start index back by the buffer width value }; // we're moving the cursor one position to the right relative to the end of the input string } else { return; @@ -287,16 +302,16 @@ impl Terminal { // Prevents the user from scrolling down if already at the bottom of the page if self.is_scroll_end { return; - } + } prev_start_idx = self.scroll_start_idx; let result = self.calc_end_idx(prev_start_idx); let mut end_idx = match result { Ok(end_idx) => end_idx, - Err(ScrollError::OffEndBound) => self.scrollback_buffer.len() -1, + Err(ScrollError::OffEndBound) => self.scrollback_buffer.len() - 1, }; // If the newly calculated end index is the bottom of the scrollback buffer, recalculates the start index and returns - if end_idx == self.scrollback_buffer.len() -1 { + if end_idx == self.scrollback_buffer.len() - 1 { self.is_scroll_end = true; let new_start = self.calc_start_idx(end_idx).0; self.scroll_start_idx = new_start; @@ -307,21 +322,27 @@ impl Terminal { { let result; let slice_len; // specifies the length of the grabbed slice - // Grabs a slice (the size of the buffer width at most) of the scrollback buffer that is directly below the current slice being displayed on the text display + // Grabs a slice (the size of the buffer width at most) of the scrollback buffer that is directly below the current slice being displayed on the text display if self.scrollback_buffer.len() > end_idx + buffer_width { slice_len = buffer_width; - result = self.scrollback_buffer.as_str().get(end_idx .. end_idx + buffer_width); + result = self + .scrollback_buffer + .as_str() + .get(end_idx..end_idx + buffer_width); } else { - slice_len = self.scrollback_buffer.len() - end_idx -1; - result = self.scrollback_buffer.as_str().get(end_idx .. self.scrollback_buffer.len()); + slice_len = self.scrollback_buffer.len() - end_idx - 1; + result = self + .scrollback_buffer + .as_str() + .get(end_idx..self.scrollback_buffer.len()); } // Searches the grabbed slice for a newline if let Some(slice) = result { - let index = slice.find('\n'); + let index = slice.find('\n'); new_end_idx = match index { - Some(index) => { end_idx + index + 1}, // Moves end index forward to the next newline - None => { end_idx + slice_len}, // If no newline is found, moves the end index forward by the buffer width value - }; + Some(index) => end_idx + index + 1, // Moves end index forward to the next newline + None => end_idx + slice_len, // If no newline is found, moves the end index forward by the buffer width value + }; } else { return; } @@ -343,14 +364,14 @@ impl Terminal { let start_idx = self.scroll_start_idx; let result = self.calc_end_idx(start_idx); let new_start_idx = match result { - Ok(idx) => idx+ 1, + Ok(idx) => idx + 1, Err(ScrollError::OffEndBound) => { let scrollback_buffer_len = self.scrollback_buffer.len(); let new_start_idx = self.calc_start_idx(scrollback_buffer_len).0; self.scroll_start_idx = new_start_idx; self.is_scroll_end = true; return; - }, + } }; let result = self.calc_end_idx(new_start_idx); let new_end_idx = match result { @@ -361,9 +382,9 @@ impl Terminal { self.scroll_start_idx = new_start_idx; self.is_scroll_end = true; return; - }, + } }; - if new_end_idx == self.scrollback_buffer.len() -1 { + if new_end_idx == self.scrollback_buffer.len() - 1 { // if the user page downs near the bottom of the page so only gets a partial shift self.is_scroll_end = true; return; @@ -378,13 +399,13 @@ impl Terminal { let end_idx = match result { Ok(end_idx) => end_idx, Err(ScrollError::OffEndBound) => { - let new_end_idx = self.scrollback_buffer.len() -1; + let new_end_idx = self.scrollback_buffer.len() - 1; let new_start_idx = self.calc_start_idx(new_end_idx).0; self.scroll_start_idx = new_start_idx; new_end_idx - }, + } }; - let result = self.scrollback_buffer.get(start_idx..=end_idx); // =end_idx includes the end index in the slice + let result = self.scrollback_buffer.get(start_idx..=end_idx); // =end_idx includes the end index in the slice if let Some(slice) = result { self.text_display.set_text(&slice); self.display_text()?; @@ -395,10 +416,16 @@ impl Terminal { } /// Display the text displayable in the window and render it to the screen - fn display_text(&mut self) -> Result<(), &'static str>{ - let coord = self.window.area().top_left; - let area_to_render = self.text_display.display(coord, self.window.framebuffer_mut().deref_mut())?; - self.window.render(Some(area_to_render)) + fn display_text(&mut self) -> Result<(), &'static str> { + let mut drawable_area = self.window.lock().drawable_area().to_relative_pos(); + self.window.lock().print_string( + &mut drawable_area, + &mut self.text_display.text, + self.text_display.fg_color, + self.text_display.bg_color, + )?; + + Ok(()) } /// Updates the text display by taking a string index and displaying as much as it can going backwards from the passed string index (i.e. starts from the bottom of the display and goes up) @@ -422,21 +449,29 @@ impl Terminal { impl Terminal { /// Creates a new terminal and adds it to the window manager `wm_mutex` pub fn new() -> Result { - let wm_ref = window_manager::WINDOW_MANAGER.get().ok_or("The window manager is not initialized")?; + let wm_ref = porthole::WINDOW_MANAGER + .get() + .ok_or("The window manager is not initialized")?; let (window_width, window_height) = { let wm = wm_ref.lock(); - wm.get_screen_size() + wm.screen_size() }; - let window = window::Window::new( - Coord::new(0, 0), - window_width, - window_height, - FONT_BACKGROUND_COLOR, - )?; - - let area = window.area(); - let text_display = TextDisplay::new(area.width(), area.height(), FONT_FOREGROUND_COLOR, FONT_BACKGROUND_COLOR)?; + let rect = Rect::new(450, 512, 0, 0); + + let window = wm_ref.lock().new_window(&rect, Some(format!("Terminal")))?; + + let relative_pos = window.lock().drawable_area().to_relative_pos(); + let text_display = TextDisplayInfo::new( + rect.width, + rect.height, + RelativePos::new(relative_pos.x, relative_pos.y), + 0, + 0, + String::new(), + 0xFBF1C7, + 0x3C3836, + ); let mut terminal = Terminal { window, @@ -446,14 +481,17 @@ impl Terminal { text_display, cursor: Cursor::default(), }; + terminal.window.lock().fill(0x3C3836)?; + terminal.print_to_terminal(format!( + "Theseus Terminal Emulator\nPress Ctrl+C to quit a task\n" + )); terminal.display_text()?; - terminal.print_to_terminal(format!("Theseus Terminal Emulator\nPress Ctrl+C to quit a task\n")); Ok(terminal) } /// Adds a string to be printed to the terminal to the terminal scrollback buffer. - /// Note that one needs to call `refresh_display` to get things actually printed. + /// Note that one needs to call `refresh_display` to get things actually printed. pub fn print_to_terminal(&mut self, s: String) { self.scrollback_buffer.push_str(&s); } @@ -462,6 +500,7 @@ impl Terminal { pub fn refresh_display(&mut self) -> Result<(), &'static str> { let start_idx = self.scroll_start_idx; // handling display refreshing errors here so that we don't clog the main loop of the terminal + if self.is_scroll_end { let buffer_len = self.scrollback_buffer.len(); self.update_display_backwards(buffer_len)?; @@ -469,6 +508,12 @@ impl Terminal { self.update_display_forwards(start_idx)?; } + self.window + .lock() + .display_window_title(self.text_display.fg_color, self.text_display.bg_color)?; + //self.display_text()?; + //self.display_cursor()?; + Ok(()) } @@ -487,9 +532,12 @@ impl Terminal { /// After invoke this function, one must call `refresh_display` to get the updates actually showed on the screen. pub fn insert_char(&mut self, c: char, offset_from_end: usize) -> Result<(), &'static str> { let buflen = self.scrollback_buffer.len(); - if buflen < offset_from_end { return Err("offset_from_end is larger than length of scrollback buffer"); } + if buflen < offset_from_end { + return Err("offset_from_end is larger than length of scrollback buffer"); + } let insert_idx = buflen - offset_from_end; - self.scrollback_buffer.insert_str(insert_idx, &c.to_string()); + self.scrollback_buffer + .insert_str(insert_idx, &c.to_string()); Ok(()) } @@ -504,13 +552,17 @@ impl Terminal { /// After invoke this function, one must call `refresh_display` to get the updates actually showed on the screen. pub fn remove_char(&mut self, offset_from_end: usize) -> Result<(), &'static str> { let buflen = self.scrollback_buffer.len(); - if buflen < offset_from_end { return Err("offset_from_end is larger than length of scrollback buffer"); } - if offset_from_end == 0 { return Err("cannot remove character at offset_from_end == 0"); } + if buflen < offset_from_end { + return Err("offset_from_end is larger than length of scrollback buffer"); + } + if offset_from_end == 0 { + return Err("cannot remove character at offset_from_end == 0"); + } let remove_idx = buflen - offset_from_end; self.scrollback_buffer.remove(remove_idx); Ok(()) } - + /// Scroll the screen to the very beginning. pub fn move_screen_to_begin(&mut self) -> Result<(), &'static str> { // Home command only registers if the text display has the ability to scroll @@ -520,7 +572,7 @@ impl Terminal { self.cursor.disable(); self.display_cursor()?; } - + Ok(()) } @@ -582,57 +634,66 @@ impl Terminal { } /// Clear the scrollback buffer and reset the scroll positions. - pub fn clear(&mut self) { + pub fn clear(&mut self) -> Result<(), &'static str> { + self.window.lock().fill(0x3C3836)?; self.scrollback_buffer.clear(); self.scroll_start_idx = 0; self.is_scroll_end = true; + Ok(()) } /// Gets an event from the window's event queue. - /// + /// /// Returns `None` if no events have been sent to this window. pub fn get_event(&mut self) -> Option { - match self.window.handle_event() { - Ok(event) => event, - Err(_e) => { - error!("Terminal::get_event(): error in the window's event handler: {:?}.", _e); - Some(Event::ExitEvent) - } - } + self.window.lock().pop_event() } /// Display the cursor of the terminal. pub fn display_cursor(&mut self) -> Result<(), &'static str> { - // get info about the text displayable - let (col_num, line_num, text_next_pos) = { - let text_next_pos = self.text_display.get_next_index(); - let (col_num, line_num) = self.get_text_dimensions(); - (col_num, line_num, text_next_pos) - }; - - // return if the cursor is not in the screen - if text_next_pos >= col_num * line_num { - return Ok(()) + let (col_num,_) = self.get_text_dimensions(); + let mut window = self.window.lock(); + let line_count = self.text_display.text.lines().count(); + let last_line_len = self + .text_display + .text + .lines() + .last() + .ok_or("Error trying to to get last line of `text_display`")? + .len(); + + let x = last_line_len ; + let mut y = line_count - 1; + for (index, line) in self.text_display.text.lines().enumerate() { + if index != line_count - 1 { + y += (line.len() * CHARACTER_WIDTH) / window.width(); + } } + y = core::cmp::min( + (window.frame_buffer.height - CHARACTER_HEIGHT) / CHARACTER_HEIGHT, + y, + ); + + + let text_next_pos = x; // calculate the cursor position let cursor_pos = text_next_pos - self.cursor.offset_from_end; let cursor_line = cursor_pos / col_num; let cursor_col = cursor_pos % col_num; - // Get the bounding box that contains the displayed cursor. - let bounding_box = { - let coord = self.window.area().top_left; - let bounding_box = self.cursor.display( - coord, - cursor_col, - cursor_line, - self.window.framebuffer_mut().deref_mut(), - )?; - bounding_box - }; - - self.window.render(Some(bounding_box)) + let coord = window.drawable_area(); + self.cursor.display( + RelativePos { + x: coord.x as u32, + y: coord.y as u32, + }, + cursor_col, + y + cursor_line, + &mut window, + )?; + + Ok(()) } /// Gets the position of the cursor relative to the end of text in number of characters. @@ -651,9 +712,10 @@ impl Terminal { /// Resizes this terminal and its underlying text display and then refreshes the window. /// This does not automatically redisplay the terminal cursor. - pub fn resize(&mut self, new_position: Rectangle) -> Result<(), &'static str> { - self.text_display.set_size(new_position.width(), new_position.height()); - self.text_display.reset_cache(); + pub fn resize(&mut self) -> Result<(), &'static str> { + self.window.lock().fill(0x3C3836)?; + //self.text_display.set_size(new_position.width(), new_position.height()); + //self.text_display.reset_cache(); self.refresh_display()?; Ok(()) } diff --git a/kernel/porthole/Cargo.toml b/kernel/porthole/Cargo.toml new file mode 100644 index 0000000000..e5c0c07e77 --- /dev/null +++ b/kernel/porthole/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "porthole" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +mpmc = "0.1.6" +spin = "0.9.0" +zerocopy = "0.5.0" +core2 = { version = "0.4.0", default-features = false, features = ["alloc", "nightly"] } + +[dependencies.log] +version = "0.4.8" + +[dependencies.memory] +path = "../../kernel/memory" + +[dependencies.memory_structs] +path = "../../kernel/memory_structs" + +[target.'cfg(target_arch = "x86_64")'.dependencies] +page_attribute_table = { path = "../../kernel/page_attribute_table" } + +[dependencies.multicore_bringup] +path = "../../kernel/multicore_bringup" + +[dependencies.spawn] +path = "../../kernel/spawn" + +[dependencies.task] +path = "../../kernel/task" + +[dependencies.scheduler] +path = "../../kernel/scheduler" + +[dependencies.stdio] +path = "../../libs/stdio" + +[dependencies.hpet] +path = "../../kernel/acpi/hpet" + +[dependencies.mouse_data] +path = "../../libs/mouse_data" + +[dependencies.mouse] +path = "../../kernel/mouse" + +[dependencies.event_types] +path = "../../kernel/event_types" + +[dependencies.keycodes_ascii] +path = "../../libs/keycodes_ascii" + +[dependencies.font] +path = "../../kernel/font" + +[dependencies.device_manager] +path = "../../kernel/device_manager" + +[lib] +crate-type = ["rlib"] \ No newline at end of file diff --git a/kernel/porthole/src/framebuffer.rs b/kernel/porthole/src/framebuffer.rs new file mode 100644 index 0000000000..91d77365df --- /dev/null +++ b/kernel/porthole/src/framebuffer.rs @@ -0,0 +1,230 @@ +#![no_std] +extern crate memory; + +use crate::*; +use core::marker::PhantomData; +use memory::{BorrowedSliceMappedPages, Mutable, PhysicalAddress, PteFlags, PteFlagsArch}; + +use core::slice::IterMut; +use log::{debug, info}; + +/// Virtual framebuffer that is not mapped to actual screen pixels, but used as a backbuffer. +pub struct VirtualFrameBuffer { + pub width: usize, + pub height: usize, + pub buffer: BorrowedSliceMappedPages, +} + +impl VirtualFrameBuffer { + pub fn new(width: usize, height: usize) -> Result { + let kernel_mmi_ref = + memory::get_kernel_mmi_ref().ok_or("KERNEL_MMI was not yet initialized!")?; + let size = width * height * core::mem::size_of::(); + let pages = memory::allocate_pages_by_bytes(size) + .ok_or("could not allocate pages for a new framebuffer")?; + let mapped_buffer = kernel_mmi_ref + .lock() + .page_table + .map_allocated_pages(pages, PteFlags::new().valid(true).writable(true))?; + Ok(VirtualFrameBuffer { + width, + height, + buffer: mapped_buffer + .into_borrowed_slice_mut(0, width * height) + .map_err(|(_mp, s)| s)?, + }) + } + + pub fn blank(&mut self) { + for pixel in self.buffer.iter_mut() { + *pixel = 0x000000; + } + } +} + +/// Physical framebuffer we use for final rendering to the screen. +pub struct PhysicalFrameBuffer { + width: usize, + height: usize, + stride: usize, + pub buffer: BorrowedSliceMappedPages, +} +impl PhysicalFrameBuffer { + pub(crate) fn init_front_buffer() -> Result { + let graphic_info = + multicore_bringup::get_graphic_info().ok_or("Failed to get graphic info")?; + if graphic_info.physical_address() == 0 { + return Err("wrong physical address for porthole"); + } + let vesa_display_phys_start = + PhysicalAddress::new(graphic_info.physical_address() as usize) + .ok_or("Invalid address")?; + let buffer_width = graphic_info.width() as usize; + let buffer_height = graphic_info.height() as usize; + // We are assuming a pixel is 4 bytes big + let stride = graphic_info.bytes_per_scanline() / 4; + + let framebuffer = PhysicalFrameBuffer::new( + buffer_width, + buffer_height, + stride as usize, + vesa_display_phys_start, + )?; + Ok(framebuffer) + } + + pub fn width(&self) -> usize { + self.width + } + + pub fn height(&self) -> usize { + self.height + } + + fn new( + width: usize, + height: usize, + stride: usize, + physical_address: PhysicalAddress, + ) -> Result { + let kernel_mmi_ref = + memory::get_kernel_mmi_ref().ok_or("KERNEL_MMI was not yet initialized!")?; + let size = width * height * core::mem::size_of::(); + let pages = memory::allocate_pages_by_bytes(size) + .ok_or("could not allocate pages for a new framebuffer")?; + + let mapped_framebuffer = { + let mut flags: PteFlagsArch = PteFlags::new().valid(true).writable(true).into(); + + #[cfg(target_arch = "x86_64")] + { + let use_pat = page_attribute_table::init().is_ok(); + if use_pat { + flags = flags.pat_index( + page_attribute_table::MemoryCachingType::WriteCombining.pat_slot_index(), + ); + info!("Using PAT write-combining mapping for real physical framebuffer memory"); + } else { + flags = flags.device_memory(true); + info!("Falling back to cache-disable mapping for real physical framebuffer memory"); + } + } + #[cfg(not(target_arch = "x86_64"))] + { + flags = flags.device_memory(true); + } + + let frames = memory::allocate_frames_by_bytes_at(physical_address, size) + .map_err(|_e| "Couldn't allocate frames for the final framebuffer")?; + let fb_mp = kernel_mmi_ref + .lock() + .page_table + .map_allocated_pages_to(pages, frames, flags)?; + debug!("Mapped real physical framebuffer: {fb_mp:?}"); + fb_mp + }; + Ok(PhysicalFrameBuffer { + width, + height, + stride, + buffer: mapped_framebuffer + .into_borrowed_slice_mut(0, width * height) + .map_err(|(_mp, s)| s)?, + }) + } +} + +/// From given mutable `VirtualFrameBuffer` and `Rect` allows you to mutably iterate +/// rows of mutable slices. +/// +/// To help you understand this structure consider this example: +/// Think `VirtualFrameBuffer` as a big cake and `Rect` as a smaller cake within the `VirtualFrameBuffer` +/// this returns row of mutable slices from that smaller cake. +pub struct FramebufferRowChunks<'a> { + /// Framebuffer we used to get the `rows` from + framebuffer: &'a mut [u32], + /// A `Rect` that specifies the dimensions of the row to be extracted from the framebuffer; + rect: Rect, + /// Number of pixels in a line of `Framebuffer` + stride: usize, + /// the index in the framebuffer at which the current row starts + start_of_row: usize, + /// Where we end the row + end_of_row: usize, + /// The index of the current row being extracted from the framebuffer + current_column: usize, +} + +impl<'a> FramebufferRowChunks<'a> { + /// Creates a new `FramebufferRowChunks` from given `rect` and `stride`; + /// if given `rect.width` is bigger than the given `stride` it will return a row big as the stride. + pub fn new(framebuffer: &'a mut VirtualFrameBuffer, rect: &mut Rect, stride: usize) -> Self { + rect.width = core::cmp::min(rect.width, stride); + let current_column = rect.y as usize; + let start_of_row = (stride * current_column) + rect.x as usize; + let end_of_row = (stride * current_column) + rect.x_plus_width() as usize; + Self { + framebuffer: &mut framebuffer.buffer, + rect: *rect, + stride, + start_of_row, + end_of_row, + current_column, + } + } + + /// Returns a single `IterMut` from specified `row` of the framebuffer if no row found from given parameters + /// returns an empty `IterMut`. + /// + /// * `row` - Specifies which row will be returned from this function + /// * `rect` - Specifies dimensions of the row, `rect.height` is always set to `1` because we want to get a single row. + pub fn get_exact_row(framebuffer: &'a mut VirtualFrameBuffer, rect: Rect, row: usize) -> IterMut { + let mut rect = rect; + rect.y = row as isize; + rect.height = 1; + let stride = framebuffer.width; + let mut rows = FramebufferRowChunks::new(framebuffer, &mut rect, stride); + let row_iterator = { + if let Some(next_row) = rows.next() { + next_row.iter_mut() + } else { + [].iter_mut() + } + }; + row_iterator + } + +} + +impl<'a> Iterator for FramebufferRowChunks<'a> { + type Item = &'a mut [u32]; + + fn next(&mut self) -> Option<&'a mut [u32]> { + if self.current_column < self.rect.y_plus_height() as usize { + // To not fight borrow checker we do this little trick here + let slice = core::mem::replace(&mut self.framebuffer, &mut []); + + if slice.len() < self.end_of_row { + return None; + } + self.current_column += 1; + + let (row, rest_of_slice) = slice.split_at_mut(self.end_of_row); + + // We want to keep rest of the slice + self.framebuffer = rest_of_slice; + if let Some(chunk) = row.get_mut(self.start_of_row..self.end_of_row) { + // Because we are taking part of a slice we need this gap to be added to + // `start_of_row` and `end_of_row` so we can correctly index the framebuffer slice + let gap = self.stride - self.end_of_row; + self.start_of_row = self.start_of_row + gap; + self.end_of_row = self.end_of_row + gap; + return Some(chunk); + } else { + None + } + } else { + None + } + } +} diff --git a/kernel/porthole/src/lib.rs b/kernel/porthole/src/lib.rs new file mode 100644 index 0000000000..1a8313372c --- /dev/null +++ b/kernel/porthole/src/lib.rs @@ -0,0 +1,524 @@ +//! This crate creates and maintains rendering of the windows and the mouse. It defines a `WindowManager` structure and initializes instance of it. +//! +//! The `WindowManager` holds a vector of `Window`, which to be rendered to the front buffer, and their rendering order, it also hold information about the mouse. +//! 'WindowManager' own's a `VirtualFrameBuffer` which acts like a back buffer and also owns a `PhysicalFrameBuffer` which acts like a front buffer. +//! The window manager will iterate through the windows copying their content onto `VirtualFrameBuffer`, then it will render the mouse and then finally it will copy `VirtualFrameBuffer` onto `PhysicalFrameBuffer`, which will update the screen with a new frame. + + +#![no_std] +#![feature(slice_ptr_get)] +#![feature(slice_flatten)] +extern crate alloc; +extern crate device_manager; +extern crate hpet; +extern crate memory; +extern crate mouse; +extern crate mouse_data; +extern crate multicore_bringup; +extern crate scheduler; +extern crate spin; +extern crate task; +pub mod units; +pub mod framebuffer; +pub mod window; +use alloc::format; +use alloc::sync::Arc; +use spin::{Mutex, Once}; + +use event_types::Event; +use mpmc::Queue; + +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use font::{CHARACTER_HEIGHT, CHARACTER_WIDTH, FONT_BASIC}; +use mouse_data::MouseEvent; +use units::*; +use framebuffer::*; +use window::*; + +/// Default window manager +pub static WINDOW_MANAGER: Once> = Once::new(); + +static SCREEN_WIDTH: usize = 1024; +static SCREEN_HEIGHT: usize = 768; + +pub type Color = u32; +pub static DEFAULT_BORDER_COLOR: Color = 0x141414; +pub static DEFAULT_TEXT_COLOR: Color = 0xFBF1C7; +pub static DEFAULT_WINDOW_COLOR: Color = 0x3C3836; + +static MOUSE_POINTER_IMAGE: [[u32; 18]; 11] = { + const T: u32 = 0xFF0000; + const C: u32 = 0x000000; // Cursor + const B: u32 = 0xFFFFFF; // Border + [ + [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, T, T], + [T, B, C, C, C, C, C, C, C, C, C, C, C, C, B, T, T, T], + [T, T, B, C, C, C, C, C, C, C, C, C, C, B, T, T, T, T], + [T, T, T, B, C, C, C, C, C, C, C, C, B, T, T, T, T, T], + [T, T, T, T, B, C, C, C, C, C, C, C, C, B, B, T, T, T], + [T, T, T, T, T, B, C, C, C, C, C, C, C, C, C, B, B, T], + [T, T, T, T, T, T, B, C, C, C, C, B, B, C, C, C, C, B], + [T, T, T, T, T, T, T, B, C, C, B, T, T, B, B, C, B, T], + [T, T, T, T, T, T, T, T, B, C, B, T, T, T, T, B, B, T], + [T, T, T, T, T, T, T, T, T, B, B, T, T, T, T, T, T, T], + [T, T, T, T, T, T, T, T, T, T, B, T, T, T, T, T, T, T], + ] +}; +/// Our mouse image is [`MOUSE_POINTER_IMAGE`] column major 2D array +/// This type returns us row major, 1D vec of that image +struct MouseImageRowIterator<'a> { + /// Mouse image [`MOUSE_POINTER_IMAGE`] + mouse_image: &'a [[u32; 18]; 11], + /// Rect of our mouse + bounding_box: Rect, + /// Since image is column major we will iterate will use + /// individual columns to create a row, think of it as y axis + current_column: usize, + /// Used to traverse image in x axis + current_row: usize, +} + +impl<'a> MouseImageRowIterator<'a> { + fn new(mouse_image: &'a [[u32; 18]; 11], bounding_box: Rect) -> Self { + Self { + mouse_image, + bounding_box, + current_column: 0, + current_row: 0, + } + } +} + +impl<'a> Iterator for MouseImageRowIterator<'a> { + type Item = Vec; + + fn next(&mut self) -> Option> { + // We start from MOUSE_POINTER_IMAGE[0][0], get the color on that index push it to our `row` + // then move to MOUSE_POINTER_IMAGE[1][0] do the same thing + // until we hit `bounding_box.width -1` then we reset our `current_column` to `0` and increase + // our `current_row` by `1` + if self.current_row < self.bounding_box.height - 1 { + let mut row = Vec::with_capacity(self.bounding_box.width - 1); + while self.current_column < self.bounding_box.width { + let color = self + .mouse_image + .get(self.current_column)? + .get(self.current_row)?; + + row.push(*color); + self.current_column += 1; + if self.current_column == self.bounding_box.width - 1 { + self.current_column = 0; + break; + } + } + self.current_row += 1; + Some(row) + } else { + None + } + } +} +#[derive(PartialEq, Eq)] +pub enum Holding { + Background, + Nothing, + Window(usize), +} + +impl Holding { + fn nothing(&self) -> bool { + *self == Holding::Nothing + } + + fn backgrond(&self) -> bool { + *self == Holding::Background + } + + fn window(&self) -> bool { + !self.nothing() && !self.backgrond() + } +} +/// The window manager, maintains windows, and the mouse, renders final frame to the screen. +pub struct WindowManager { + /// Windows that are on the screen + windows: Vec>>, + /// Rendering order for the windows + window_rendering_order: Vec, + /// Backbuffer + v_framebuffer: VirtualFrameBuffer, + /// Frontbuffer + p_framebuffer: PhysicalFrameBuffer, + /// Width, height and position of the mouse + pub mouse: Rect, + /// Previous position of the mouse + prev_mouse_pos: ScreenPos, + /// What's currently held by the mouse + mouse_holding: Holding, + /// Holds the index of the active window/last element in the `window_rendering_order` + active_window_index: usize, +} + +impl WindowManager { + /// Initializes the window manager, returns keyboard and mouse producer for the I/O devices + pub fn init() -> Result<(Queue, Queue), &'static str> { + let p_framebuffer = PhysicalFrameBuffer::init_front_buffer()?; + let v_framebuffer = VirtualFrameBuffer::new(p_framebuffer.width(), p_framebuffer.height())?; + // FIXME: Don't use magic numbers, + let mouse = Rect::new(11, 18, 200, 200); + + let window_manager = WindowManager { + windows: Vec::new(), + window_rendering_order: Vec::new(), + v_framebuffer, + p_framebuffer, + mouse, + prev_mouse_pos: mouse.to_screen_pos(), + mouse_holding: Holding::Nothing, + active_window_index: usize::MAX, + }; + WINDOW_MANAGER.call_once(|| Mutex::new(window_manager)); + let key_consumer: Queue = Queue::with_capacity(100); + let key_producer = key_consumer.clone(); + + let mouse_consumer: Queue = Queue::with_capacity(100); + let mouse_producer = mouse_consumer.clone(); + spawn::new_task_builder(port_loop, (key_consumer, mouse_consumer)) + .name("port_loop".to_string()) + .pin_on_core(0) + .spawn()?; + Ok((key_producer, mouse_producer)) + } + + + /// Creates a new `Window`, with given dimensions and an optional title. + pub fn new_window( + &mut self, + rect: &Rect, + title: Option, + ) -> Result>, &'static str> { + let len = self.windows.len(); + + self.window_rendering_order.push(len); + let window = Window::new( + *rect, + VirtualFrameBuffer::new(rect.width, rect.height)?, + title, + ); + let arc_window = Arc::new(Mutex::new(window)); + arc_window.lock().active = true; + let returned_window = arc_window.clone(); + self.windows.push(arc_window); + Ok(returned_window) + } + + /// Iterates through the `window_rendering_order`, gets the particular `Window` from `self.windows` + /// and then locks it to hold the lock until we are done rendering that particular window into + /// backbuffer/`v_framebuffer`. + fn draw_windows(&mut self) { + for order in self.window_rendering_order.iter() { + if let Some(mut window) = self + .windows + .get(*order) + .and_then(|window| Some(window.lock())) + { + let mut visible_window = window.rect().visible_rect(); + let window_stride = window.frame_buffer.width; + let mut relative_visible_window = window.relative_visible_rect(); + let stride = self.v_framebuffer.width; + let screen_rows = FramebufferRowChunks::new( + &mut self.v_framebuffer, + &mut visible_window, + stride, + ); + // To handle rendering when the window is partially outside the screen we use relative version of visible rect + let window_rows = FramebufferRowChunks::new( + &mut window.frame_buffer, + &mut relative_visible_window, + window_stride, + ); + + for (screen_row, window_row) in screen_rows.zip(window_rows) { + screen_row.copy_from_slice(window_row); + } + } + } + } + + /// Draws visible parts of the mouse + fn draw_mouse(&mut self) { + let mut visible_mouse = self.mouse.visible_rect(); + + let screen_rows = FramebufferRowChunks::new( + &mut self.v_framebuffer, + &mut visible_mouse, + SCREEN_WIDTH, + ); + + let mouse_image = MouseImageRowIterator::new(&MOUSE_POINTER_IMAGE, visible_mouse); + for (screen_row, mouse_image_row) in screen_rows.zip(mouse_image) { + for (screen_pixel, mouse_pixel) in screen_row.iter_mut().zip(mouse_image_row.iter()) { + if mouse_pixel != &0xFF0000 { + *screen_pixel = *mouse_pixel; + } + } + } + } + + /// Returns current screen width and height + pub fn screen_size(&self) -> (usize, usize) { + (SCREEN_WIDTH, SCREEN_HEIGHT) + } + + pub fn set_mouse_pos(&mut self, screen_positon: &ScreenPos) { + self.mouse.x = screen_positon.x as isize; + self.mouse.y = screen_positon.y as isize; + } + + pub fn set_window_event(&mut self, event: Event) -> Result<(),&'static str> { + if let Some(window) = self.windows.get_mut(self.active_window_index) { + window.lock().push_event(event).map_err(|_| "Failed to enque event, window event queue was full")?; + Ok(()) + }else { + Ok(()) + } + } + + /// Updates `v_framebuffer` before the final render. + /// Clears the whole buffer by calling `blank` + /// Draws each window and then the mouse. + fn update(&mut self) { + self.v_framebuffer.blank(); + self.draw_windows(); + self.draw_mouse(); + } + + fn calculate_next_mouse_pos( + &self, + current_position: ScreenPos, + relative_offset: ScreenPos, + ) -> ScreenPos { + let mut new_pos = relative_offset + current_position; + + // handle left + new_pos.x = core::cmp::max(new_pos.x, 0); + // handle right + new_pos.x = core::cmp::min( + new_pos.x, + self.v_framebuffer.width as i32 - MOUSE_VISIBLE_GAP, + ); + + // handle top + new_pos.y = core::cmp::max(new_pos.y, 0); + // handle bottom + new_pos.y = core::cmp::min( + new_pos.y, + self.v_framebuffer.height as i32 - MOUSE_VISIBLE_GAP, + ); + + new_pos + } + + /// Returns currently active window + fn active_window(&mut self) -> Option<&mut Arc>> { + if let Some(window) = self.windows.get_mut(self.active_window_index) { + Some(window) + } else { + None + } + } + + fn update_mouse_position(&mut self, raw_x: i32, raw_y: i32) { + let relative_offset = ScreenPos::new(raw_x, raw_y); + self.prev_mouse_pos = self.mouse.to_screen_pos(); + let new_pos = self.calculate_next_mouse_pos(self.mouse.to_screen_pos(), relative_offset); + + self.set_mouse_pos(&new_pos); + } + + fn set_window_non_active(&mut self, window_index: usize) { + if let Some(window) = self.windows.get_mut(window_index) { + window.lock().active = false; + } + } + + // TODO: This can be greatly simplfied, instead of having one big function cut this into smaller ones. + fn handle_mouse_events_on_windows(&mut self, screen_position: ScreenPos, mouse_event: &MouseEvent) { + if !mouse_event.buttons.left() && !mouse_event.buttons.right() { + self.mouse_holding = Holding::Nothing; + if let Some(window) = self.active_window() { + if window.lock().resizing { + window.lock().resizing = false; + } + } + } + if mouse_event.buttons.left() && !mouse_event.buttons.right() { + match self.mouse_holding { + // TODO: Add functionality of being able to grab no window/background. + Holding::Background => {} + Holding::Nothing => { + // We are cloning this value because we will use it to iterate through our windows while editing the original one + let rendering_order = self.window_rendering_order.clone(); + // `iter_index` = index of the window in `self.window_rendering_order` + // `window_index` = index of the window in `self.windows` + for (iter_index, &window_index) in rendering_order.iter().enumerate().rev() { + let window = &mut self.windows[window_index].clone(); + if window.lock().rect().detect_collision(&Rect::new(4, 4, self.mouse.x, self.mouse.y)) { + // If colliding window is not active one make it active + // we first remove colliding window from it's position in + // window_rendering_order, then push it to the back of + // window_rendering_order, this way we don't have to do any special sorting + if window_index != self.active_window_index { + self.set_window_non_active(self.active_window_index); + self.active_window_index = window_index; + self.window_rendering_order.remove(iter_index); + self.window_rendering_order.push(window_index); + window.lock().active = true; + } + // If user is holding the window from it's title border pos + // it means user wants to move the window + if window + .lock() + .dynamic_title_border_pos() + .detect_collision(&Rect::new(4, 4, self.mouse.x, self.mouse.y)) + { + self.mouse_holding = Holding::Window(window_index); + } + break; + } + self.mouse_holding = Holding::Nothing; + } + // If couldn't hold onto anything we must have hold onto background + if self.mouse_holding.nothing() { + self.mouse_holding = Holding::Background + } + } + Holding::Window(i) => { + // These calculations are required because we do want finer control + // over a window's movement. + let prev_mouse_pos = self.prev_mouse_pos; + let next_mouse_pos = + self.calculate_next_mouse_pos(prev_mouse_pos, screen_position); + let window = &mut self.windows[i]; + let window_rect = window.lock().rect(); + let diff = next_mouse_pos - prev_mouse_pos; + let mut new_pos = diff + window_rect.to_screen_pos(); + + //handle left + if (new_pos.x + (window_rect.width as i32 - WINDOW_VISIBLE_GAP as i32)) < 0 { + new_pos.x = -(window_rect.width as i32 - WINDOW_VISIBLE_GAP); + } + + //handle right + if (new_pos.x + WINDOW_VISIBLE_GAP) > self.v_framebuffer.width as i32 { + new_pos.x = SCREEN_WIDTH as i32 - WINDOW_VISIBLE_GAP + } + + //handle top + if new_pos.y < 0 { + new_pos.y = 0 + } + + // handle bottom + if new_pos.y + WINDOW_VISIBLE_GAP > self.v_framebuffer.height as i32 { + new_pos.y = (SCREEN_HEIGHT as i32 - WINDOW_VISIBLE_GAP) as i32; + } + + window.lock().set_screen_pos(&new_pos); + } + } + } else if mouse_event.buttons.right() { + for &i in self.window_rendering_order.iter().rev() { + let window = &mut self.windows[i].lock(); + if window.rect().detect_collision(&Rect::new( + self.mouse.width, + self.mouse.height, + self.mouse.x, + self.mouse.y, + )) { + window.resizing = true; + window + .resize_window(screen_position.x, screen_position.y); + window.reset_drawable_area(); + window.reset_title_pos_and_border(); + break; + } + } + } + } + + /// Does the final rendering by copying `v_framebuffer`. + fn render(&mut self) { + self.p_framebuffer + .buffer + .copy_from_slice(&self.v_framebuffer.buffer); + } +} + +fn port_loop( + (key_consumer, mouse_consumer): (Queue, Queue), +) -> Result<(), &'static str> { + let window_manager = WINDOW_MANAGER.get().ok_or("Unable to get WindowManager")?; + //let window = window_manager.lock().new_window(&Rect::new(400, 400, 0, 0), None)?; + + loop { + let event_opt = key_consumer + .pop() + .or_else(|| mouse_consumer.pop()) + .or_else(|| { + scheduler::schedule(); + None + }); + + if let Some(event) = event_opt { + match event { + Event::MouseMovementEvent(ref mouse_event) => { + window_manager.lock().set_window_event(Event::MouseMovementEvent(mouse_event.clone()))?; + let movement = &mouse_event.movement; + let mut x = (movement.x_movement as i8) as isize; + let mut y = (movement.y_movement as i8) as isize; + while let Some(next_event) = mouse_consumer.pop() { + match next_event { + Event::MouseMovementEvent(ref next_mouse_event) => { + if next_mouse_event.movement.scroll_movement + == mouse_event.movement.scroll_movement + && next_mouse_event.buttons.left() == mouse_event.buttons.left() + && next_mouse_event.buttons.right() + == mouse_event.buttons.right() + && next_mouse_event.buttons.fourth() + == mouse_event.buttons.fourth() + && next_mouse_event.buttons.fifth() + == mouse_event.buttons.fifth() + { + x += (next_mouse_event.movement.x_movement as i8) as isize; + y += (next_mouse_event.movement.y_movement as i8) as isize; + } + } + + _ => { + break; + } + } + } + if x != 0 || y != 0 { + window_manager + .lock() + .update_mouse_position(x as i32, -(y as i32)); + } + window_manager + .lock() + .handle_mouse_events_on_windows(ScreenPos::new(x as i32, -(y as i32)), &mouse_event); + } + Event::KeyboardEvent(ref input_event) => { + window_manager.lock().set_window_event(event)?; + } + _ => (), + } + } + //window.lock().fill(0xFFF111)?; + window_manager.lock().update(); + window_manager.lock().render(); + } + Ok(()) +} diff --git a/kernel/porthole/src/units.rs b/kernel/porthole/src/units.rs new file mode 100644 index 0000000000..4c63c67e0f --- /dev/null +++ b/kernel/porthole/src/units.rs @@ -0,0 +1,172 @@ +#![no_std] +use core::ops::{Add, Sub}; + +pub static SCREEN_WIDTH: usize = 1024; +pub static SCREEN_HEIGHT: usize = 768; +/// Position that is relative to the screen. +#[derive(Debug, Clone, Copy)] +pub struct RelativePos { + pub x: u32, + pub y: u32, +} + +impl RelativePos { + pub fn new(x: u32, y: u32) -> Self { + let x = core::cmp::min(x,SCREEN_WIDTH as u32); + let y = core::cmp::min(y, SCREEN_HEIGHT as u32); + Self { x, y } + } + + pub fn to_1d_pos(&self, target_stride: u32) -> usize { + ((target_stride * self.y) + self.x) as usize + } +} + +/// Position that is relative to the screen +#[derive(Debug, Clone, Copy)] +pub struct ScreenPos { + pub x: i32, + pub y: i32, +} + +impl ScreenPos { + pub fn new(x: i32, y: i32) -> Self { + Self { x, y } + } + + pub fn to_1d_pos(&self) -> usize { + ((SCREEN_WIDTH as i32 * self.y) + self.x) as usize + } +} + +impl Add for ScreenPos { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { + x: self.x + other.x, + y: self.y + other.y, + } + } +} + +impl Sub for ScreenPos { + type Output = Self; + + fn sub(self, other: Self) -> Self { + Self { + x: self.x - other.x, + y: self.y - other.y, + } + } +} + +impl Add for ScreenPos { + type Output = Self; + + fn add(self, other: Rect) -> Self { + Self { + x: self.x + other.x as i32, + y: self.y + other.y as i32, + } + } +} + +/// Ubiquitous structure representing a rectangle. +#[derive(Clone, Copy, Debug)] +pub struct Rect { + pub width: usize, + pub height: usize, + pub x: isize, + pub y: isize, +} + +impl Rect { + pub fn new(width: usize, height: usize, x: isize, y: isize) -> Rect { + Rect { + width, + height, + x, + y, + } + } + + pub fn width(&self) -> usize { + self.width + } + + pub fn height(&self) -> usize { + self.height + } + + pub fn to_screen_pos(&self) -> ScreenPos { + ScreenPos { + x: self.x as i32, + y: self.y as i32, + } + } + + /// Return's `RelativePos` from `x` and `y` of itself, + pub fn to_relative_pos(&self) -> RelativePos { + let x = core::cmp::max(0, self.x) as u32; + RelativePos { x, y: self.y as u32 } + } + + pub fn set_position(&mut self, x: u32, y: u32) { + self.x = x as isize; + self.y = y as isize; + } + + pub fn x_plus_width(&self) -> isize { + self.x + self.width as isize + } + + pub fn y_plus_height(&self) -> isize { + self.y + self.height as isize + } + + pub fn detect_collision(&self, other: &Rect) -> bool { + self.x < other.x_plus_width() + && self.x_plus_width() > other.x + && self.y < other.y_plus_height() + && self.y_plus_height() > other.y + } + + /// Checks if left side of `Rectangle` is outside the screen or not. + pub fn left_side_out(&self) -> bool { + self.x < 0 + } + + /// Checks if right side of `Rectangle` is outside the screen or not. + pub fn right_side_out(&self) -> bool { + self.x + self.width as isize > SCREEN_WIDTH as isize + } + + /// Checks if bottom side of `Rectangle` is outside the screen or not. + pub fn bottom_side_out(&self) -> bool { + self.y + self.height as isize > SCREEN_HEIGHT as isize + } + + /// Creates a new `Rect` from visible parts of itself, by obtaining `SCREEN_WIDTH` and `SCREEN_HEIGHT` then + /// compares with it's own dimensions. + pub fn visible_rect(&self) -> Rect { + let mut x = self.x; + let y = self.y; + let mut width = self.width as isize; + let mut height = self.height as isize; + if self.left_side_out() { + x = 0; + width = self.x_plus_width(); + } else if self.right_side_out() { + x = self.x; + let gap = (self.x + self.width as isize) - SCREEN_WIDTH as isize; + width = self.width as isize - gap; + } + if self.bottom_side_out() { + let gap = (self.y + self.height as isize) - SCREEN_HEIGHT as isize; + height = self.height as isize - gap; + } + let visible_rect = Rect::new(width as usize, height as usize, x, y); + visible_rect + } +} diff --git a/kernel/porthole/src/window.rs b/kernel/porthole/src/window.rs new file mode 100644 index 0000000000..74e6d04da8 --- /dev/null +++ b/kernel/porthole/src/window.rs @@ -0,0 +1,444 @@ +use crate::*; + +/// Controls amount of visible `Window` we see when we move a `Window` out of the screen +pub static WINDOW_VISIBLE_GAP: i32 = 20; + +/// Controls amount of visible mouse we see when we move the mouse out of the screen +pub static MOUSE_VISIBLE_GAP: i32 = 3; +/// Height of the Window's title bar +pub static TITLE_BAR_HEIGHT: usize = 20; +pub struct Window { + rect: Rect, + pub frame_buffer: VirtualFrameBuffer, + resized: bool, + pub resizing: bool, + title: Option, + title_border: Option, + title_pos: Option, + drawable_area: Option, + pub event: Queue, + pub(crate) active: bool, +} + +impl Window { + pub(crate) fn new( + rect: Rect, + frame_buffer: VirtualFrameBuffer, + title: Option, + ) -> Window { + let events = Queue::with_capacity(100); + Window { + rect, + frame_buffer, + resized: false, + resizing: false, + title, + title_border: None, + title_pos: None, + drawable_area: None, + event: events, + active: false, + } + } + + pub fn active(&self) -> bool { + self.active + } + + /// Prints a string onto the window + /// + /// * `position` - This indicates where line of text will be. + /// * `string` - Text we are printing + /// * `fg_color` - Foreground color of the text + /// * `bg_color` - Background color of the text + pub fn print_string( + &mut self, + position: &mut RelativePos, + string: &mut String, + fg_color: Color, + bg_color: Color, + ) -> Result<(), &'static str> { + for line in string.lines() { + // Number of characters that can fit in a line + let line_len = line.len() * CHARACTER_WIDTH; + // If text fits to a single line + if line_len < self.width() - CHARACTER_WIDTH { + self.print_string_line(position, line, fg_color, bg_color)?; + + let mut window_rect = self.rect(); + window_rect.height = CHARACTER_HEIGHT - 1; + let rest_of_the_line = window_rect.width - line_len; + window_rect.width = rest_of_the_line; + window_rect.y = position.y as isize; + window_rect.x = line_len as isize; + // We fill rest of the line with `bg_color` to clear the screen + self.fill_rectangle(&mut window_rect, 0x1FF333); + if position.y != self.height() as u32 { + position.y += CHARACTER_HEIGHT as u32; + } + } else { + let max_text_width = self.width() / CHARACTER_WIDTH; + let mut text_start = 0; + while let Some(shorter_line) = line.get(text_start..) { + text_start += max_text_width; + if position.y >= 479 { + log::info!("position is {position:?}"); + log::info!("shorter line is {shorter_line}"); + } + + self.print_string_line(position, shorter_line, fg_color, bg_color)?; + + let mut window_rect = self.rect(); + window_rect.height = CHARACTER_HEIGHT - 1; + let rest_of_the_line = + window_rect.width - (shorter_line.len() * CHARACTER_WIDTH); + window_rect.width = rest_of_the_line; + window_rect.y = position.y as isize; + window_rect.x = (shorter_line.len() * CHARACTER_WIDTH) as isize; + self.fill_rectangle(&mut window_rect, bg_color); + + if position.y < self.height() as u32 { + position.y += CHARACTER_HEIGHT as u32; + } + } + } + } + Ok(()) + } + + /// Prints a line of string to the onto the window + /// + /// * `position` - This indicates where line of text will be. + /// * `slice` - Text we are printing + /// * `fg_color` - Foreground color of the text + /// * `bg_color` - Background color of the text + pub fn print_string_line( + &mut self, + position: &RelativePos, + slice: &str, + fg_color: Color, + bg_color: Color, + ) -> Result<(), &'static str> { + if !slice.is_empty() { + let slice = slice.as_bytes(); + let start_x = position.x; + let start_y = position.y; + + let mut x_index = 0; + let mut row_controller = 0; + let mut char_index = 0; + let mut char_color_on_x_axis = x_index; + + let mut window_rect = self.rect(); + window_rect.set_position(start_x, start_y); + // We want to get smmallest iterator possible for given `str` and `Rect` + let min_width = core::cmp::min(self.rect.width(), slice.len() * CHARACTER_WIDTH); + window_rect.width = min_width; + + let mut row_of_pixels = FramebufferRowChunks::get_exact_row( + &mut self.frame_buffer, + window_rect, + start_y as usize, + ); + + loop { + let y = start_y + row_controller as u32; + if x_index % CHARACTER_WIDTH == 0 { + char_color_on_x_axis = 0; + } + let color = if char_color_on_x_axis >= 1 { + let index = char_color_on_x_axis - 1; + let char_font = *FONT_BASIC + .get(*slice.get(char_index).unwrap_or(&32) as usize) + .ok_or("Couldn't find corresponding font for the char")? + .get(row_controller) + .ok_or("Couldn't find corresponding pixel for the char")?; + char_color_on_x_axis += 1; + if get_bit(char_font, index) != 0 { + fg_color + } else { + bg_color + } + } else { + char_color_on_x_axis += 1; + bg_color + }; + + // Altough bit ugly, this works quite well with our current way of rendering fonts + if let Some(pixel) = row_of_pixels.next() { + *pixel = color; + } + + x_index += 1; + if x_index == CHARACTER_WIDTH || x_index % CHARACTER_WIDTH == 0 { + if slice.len() >= 1 && char_index < slice.len() - 1 { + char_index += 1; + } + + if x_index >= CHARACTER_WIDTH * slice.len() + && x_index % (CHARACTER_WIDTH * slice.len()) == 0 + { + row_of_pixels = FramebufferRowChunks::get_exact_row( + &mut self.frame_buffer, + window_rect, + y as usize, + ); + row_controller += 1; + char_index = 0; + x_index = 0; + } + + if row_controller == CHARACTER_HEIGHT { + break; + } + } + } + } + Ok(()) + } + + pub fn display_window_title( + &mut self, + fg_color: Color, + bg_color: Color, + ) -> Result<(), &'static str> { + if self.title.is_some() { + let title = self + .title + .as_ref() + .ok_or("Couldn't clone Window Tittle")? + .clone(); + let slice = title.as_str(); + let title_pos = self.title_pos(&slice.len()); + self.print_string_line(&title_pos, slice, fg_color, bg_color)?; + } + Ok(()) + } + + pub fn width(&self) -> usize { + self.rect.width + } + + pub fn height(&self) -> usize { + self.rect.height + } + + pub fn fill_rectangle(&mut self, rect: &mut Rect, color: Color) { + let width = self.width(); + if rect.x <= (self.rect.width() as isize - CHARACTER_WIDTH as isize) + && rect.y <= (self.rect.height as isize - CHARACTER_HEIGHT as isize) + && self.rect.width == self.frame_buffer.width + && self.rect.height == self.frame_buffer.height + { + let row_chunks = FramebufferRowChunks::new(&mut self.frame_buffer, rect, width); + for row in row_chunks { + for pixel in row { + *pixel = color; + } + } + } + } + + pub fn screen_pos(&self) -> ScreenPos { + let screen_pos = ScreenPos::new(self.rect.x as i32, self.rect.y as i32); + screen_pos + } + + pub fn set_screen_pos(&mut self, screen_position: &ScreenPos) { + self.rect.x = screen_position.x as isize; + self.rect.y = screen_position.y as isize; + } + + /// Pushes an event into `self.event` + pub fn push_event(&mut self, event: Event) -> Result<(), Event> { + self.event.push(event) + } + + /// Pops event from `self.event` and returns it + pub fn pop_event(&self) -> Option { + self.event.pop() + } + + pub fn resize_window(&mut self, width: i32, height: i32) { + // We clamp the values so resizing is not so jumpy and multiply them by CHARACTER_WIDTH + // and CHARACTER_HEIGHT so the Window is almost always divisible by those values. + let width = width.clamp(-1, 1) * CHARACTER_WIDTH as i32; + let height = height.clamp(-1, 1) * CHARACTER_HEIGHT as i32; + + // We don't want any window to be smaller than 180 and bigger than screen itself + let mut new_width = core::cmp::max(self.width() + width as usize, 180); + new_width = core::cmp::min(SCREEN_WIDTH, new_width); + let mut new_height = core::cmp::max(self.height() + height as usize, 180); + new_height = core::cmp::min(SCREEN_HEIGHT, new_height); + self.rect.width = new_width; + self.rect.height = new_height; + self.resized = true; + } + + pub fn reset_drawable_area(&mut self) { + self.drawable_area = None; + } + + pub fn reset_title_pos_and_border(&mut self) { + self.title_border = None; + self.title_pos = None; + } + + /// Returns Window's border area width and height with 0 as position + pub fn title_border(&mut self) -> Rect { + let border = + self.title_border + .get_or_insert(Rect::new(self.rect.width, TITLE_BAR_HEIGHT, 0, 0)); + *border + } + + /// Return's title border's position in screen coordinates + pub fn dynamic_title_border_pos(&self) -> Rect { + let mut rect = self.rect; + rect.height = TITLE_BAR_HEIGHT; + rect + } + + /// Return's drawable area + pub fn drawable_area(&mut self) -> Rect { + let border = self.title_border(); + let drawable_area = self.drawable_area.get_or_insert({ + let x = 0; + let y = border.height; + let width = border.width; + let height = self.rect.height - y; + let drawable_area = Rect::new(width, height, x, y as isize); + drawable_area + }); + *drawable_area + } + + /// From given title length returns center position of the title border + pub fn title_pos(&mut self, title_length: &usize) -> RelativePos { + let border = self.title_border(); + let relative_pos = self.title_pos.get_or_insert({ + let pos = (border.width - (title_length * CHARACTER_WIDTH)) / 2; + let relative_pos = RelativePos::new(pos as u32, 0); + relative_pos + }); + *relative_pos + } + + pub fn draw_title_border(&mut self) { + let mut border = self.title_border(); + let stride = self.frame_buffer.width; + let rows = FramebufferRowChunks::new(&mut self.frame_buffer, &mut border, stride); + for row in rows { + for pixel in row { + *pixel = DEFAULT_BORDER_COLOR; + } + } + } + + /// Return's the window's `Rect` + pub fn rect(&self) -> Rect { + self.rect + } + + /// Clears the window screen back to it's default color + pub fn clear(&mut self) { + for pixel in self.frame_buffer.buffer.iter_mut() { + *pixel = DEFAULT_WINDOW_COLOR; + } + } + + pub fn resized(&self) -> bool { + self.resized + } + + /// If the window is resized, resizes window's framebuffer + pub fn should_resize_framebuffer(&mut self) -> Result<(), &'static str> { + if self.resized() { + self.resize_framebuffer()?; + self.resized = false; + } + Ok(()) + } + + /// Fill the window with specified color + pub fn fill(&mut self, color: Color) -> Result<(), &'static str> { + self.should_resize_framebuffer()?; + + for pixel in self.frame_buffer.buffer.iter_mut() { + *pixel = color; + } + self.draw_title_border(); + Ok(()) + } + + /// Resizes framebuffer after to Window's width and height + fn resize_framebuffer(&mut self) -> Result<(), &'static str> { + self.frame_buffer = VirtualFrameBuffer::new(self.rect.width, self.rect.height)?; + Ok(()) + } + + /// Returns visible part of self's `rect` with relative bounds applied: + /// This is used for rendering a window when it's partially outside the screen, + /// because we don't change window's framebuffer width and height when its outside screen coordinates + /// we want to be able to render only parts of it: + /// e.g if `self.rect: { width: 400, height: 400, x: -103, y: 0 }`, + /// visible rect is `{ width: 297, height: 400, x: 0, y: 0 }`, + /// this function will return `{ width: 297, height: 400, x: 103, y: 0 }` + /// which will allow us to give the illusion of partially rendering the window. + pub fn relative_visible_rect(&self) -> Rect { + let mut visible_window = self.rect.visible_rect(); + visible_window.x = 0; + if self.rect.left_side_out() { + visible_window.x = (self.rect.width - visible_window.width) as isize; + } + visible_window.y = 0; + visible_window + } +} + +fn get_bit(char_font: u8, i: usize) -> u8 { + char_font & (0x80 >> i) +} + +pub struct TextDisplayInfo { + width: usize, + height: usize, + pos: RelativePos, + next_col: usize, + next_line: usize, + pub text: String, + pub fg_color: Color, + pub bg_color: Color, +} + +impl TextDisplayInfo { + pub fn new( + width: usize, + height: usize, + pos: RelativePos, + next_col: usize, + next_line: usize, + text: String, + fg_color: Color, + bg_color: Color, + ) -> Self { + Self { + width, + height, + pos, + next_col, + next_line, + text, + fg_color, + bg_color, + } + } + + pub fn append_char(&mut self, char: char) { + self.text.push(char); + } + + pub fn set_text(&mut self, text: &str) { + self.text = String::from(text); + } +} diff --git a/kernel/window/Cargo.toml b/kernel/window/Cargo.toml deleted file mode 100644 index 467b2f5a82..0000000000 --- a/kernel/window/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[package] -name = "window" -version = "0.1.0" -authors = ["Yue Wu ", "Wenqiu Yu "] -description = "an easy-to-use window object owned by application" - -[dependencies] -spin = "0.9.4" -mpmc = "0.1.6" - -[dependencies.log] -version = "0.4.8" - -[dependencies.shapes] -path = "../shapes" - -[dependencies.color] -path = "../color" - -[dependencies.framebuffer_drawer] -path = "../framebuffer_drawer" - -[dependencies.window_inner] -path = "../window_inner" - -[dependencies.window_manager] -path = "../window_manager" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.event_types] -path = "../event_types" - -[dependencies.spawn] -path = "../spawn" - -[dependencies.mouse] -path = "../mouse" - -[dependencies.path] -path = "../path" - -[dependencies.dereffer] -path = "../../libs/dereffer" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/window/src/lib.rs b/kernel/window/src/lib.rs deleted file mode 100644 index d60235ed0a..0000000000 --- a/kernel/window/src/lib.rs +++ /dev/null @@ -1,520 +0,0 @@ -//! A `Window` object should be owned by an application. It can display a `Displayable` object in its framebuffer. See `applications/new_window` as a demo to use this library. -//! -//! This library will create a window with default title bar and border. It handles the commonly used interactions like moving -//! the window or close the window. Also, it is responsible to show title bar differently when window is active. -//! -//! A window can render itself to the screen via a window manager. The window manager will compute the bounding box of the updated part and composites it with other existing windows according to their order. -//! -//! The library -//! frees applications from handling the complicated interaction with window manager, however, advanced users could learn from -//! this library about how to use window manager APIs directly. -//! - -#![no_std] -#![feature(type_alias_impl_trait)] - -extern crate alloc; -extern crate mpmc; -extern crate event_types; -extern crate spin; -#[macro_use] -extern crate log; -extern crate framebuffer; -extern crate framebuffer_drawer; -extern crate mouse; -extern crate window_inner; -extern crate window_manager; -extern crate shapes; -extern crate color; -extern crate dereffer; - -use alloc::sync::Arc; -use dereffer::{DerefsTo, DerefsToMut}; -use mpmc::Queue; -use event_types::{Event, MousePositionEvent}; -use framebuffer::{Framebuffer, AlphaPixel}; -use color::Color; -use shapes::{Coord, Rectangle}; -use spin::{Mutex, MutexGuard}; -use window_inner::{WindowInner, WindowMovingStatus, DEFAULT_BORDER_SIZE, DEFAULT_TITLE_BAR_HEIGHT}; -use window_manager::{WINDOW_MANAGER}; - - -// border radius, in number of pixels -const WINDOW_RADIUS: usize = 5; -// border and title bar color when window is inactive -const WINDOW_BORDER_COLOR_INACTIVE: Color = Color::new(0x00333333); -// border and title bar color when window is active, the top part color -const WINDOW_BORDER_COLOR_ACTIVE_TOP: Color = Color::new(0x00BBBBBB); -// border and title bar color when window is active, the bottom part color -const WINDOW_BORDER_COLOR_ACTIVE_BOTTOM: Color = Color::new(0x00666666); -// window button color: red -const WINDOW_BUTTON_COLOR_CLOSE: Color = Color::new(0x00E74C3C); -// window button color: green -const WINDOW_BUTTON_COLOR_MINIMIZE_MAMIMIZE: Color = Color::new(0x00239B56); -// window button color: purple -const WINDOW_BUTTON_COLOR_HIDE: Color = Color::new(0x007D3C98); -// window button margin from left, in number of pixels -const WINDOW_BUTTON_BIAS_X: usize = 12; -// the interval between buttons, in number of pixels -const WINDOW_BUTTON_BETWEEN: usize = 15; -// the button size, in number of pixels -const WINDOW_BUTTON_SIZE: usize = 6; - -// The buttons shown in title bar -enum TopButton { - // Button to close the window - Close, - // Button to minimize/maximize the window (depends on the current state) - MinimizeMaximize, - // Button to hide the window - Hide, -} - -impl From for TopButton { - fn from(item: usize) -> Self { - match item { - 0 => TopButton::Close, - 1 => TopButton::MinimizeMaximize, - 2 => TopButton::Hide, - _ => TopButton::Close, - } - } -} - - -/// This struct is the application-facing representation of a window. -/// -pub struct Window { - /// The system-facing inner representation of this window. - /// The window manager interacts with this object directly; - /// thus, applications should not be able to access this directly. - /// - /// This is wrapped in an `Arc` such that the window manager can hold `Weak` references to it. - inner: Arc>, - /// The event queue - event_consumer: Queue, - /// last mouse position event, used to judge click and press-moving event - /// TODO FIXME (kevinaboos): why is mouse-specific stuff here? - last_mouse_position_event: MousePositionEvent, - /// record last result of whether this window is active, to reduce redraw overhead - last_is_active: bool, -} - -impl Window { - /// Creates a new window to be displayed on screen. - /// - /// The given `framebuffer` will be filled with the `initial_background` color. - /// - /// The newly-created `Window` will be set as the "active" window that has current focus. - /// - /// # Arguments: - /// * `coordinate`: the position of the window relative to the top-left corner of the screen. - /// * `width`, `height`: the dimensions of the window in pixels. - /// * `initial_background`: the default color of the window. - pub fn new( - coordinate: Coord, - width: usize, - height: usize, - initial_background: Color, - ) -> Result { - let wm_ref = window_manager::WINDOW_MANAGER.get().ok_or("The window manager is not initialized")?; - - // Create a new virtual framebuffer to hold this window's contents only, - // and fill it with the initial background color. - let mut framebuffer = Framebuffer::new(width, height, None)?; - framebuffer.fill(initial_background.into()); - let (width, height) = framebuffer.get_size(); - - // TODO: FIXME: (kevinaboos) this condition seems wrong... at least the first conditional does. - if width <= 2 * DEFAULT_TITLE_BAR_HEIGHT || height <= DEFAULT_TITLE_BAR_HEIGHT + DEFAULT_BORDER_SIZE { - return Err("window dimensions must be large enough for the title bar and borders to be drawn"); - } - - // Create an event queue to allow the window manager to pass events to this `Window` via its `WindowInner` instance, - // and to allow applications to receive events from this `Window` object itself. - let event_consumer = Queue::with_capacity(100); - let event_producer = event_consumer.clone(); - - let window_inner = WindowInner::new(coordinate, framebuffer, event_producer); - let mut window = Window { - inner: Arc::new(Mutex::new(window_inner)), - event_consumer, - last_mouse_position_event: MousePositionEvent::default(), - last_is_active: true, // new window is now set as the active window by default - }; - - // Draw the actual window frame, the title bar and borders. - window.draw_border(true); - { - let mut inner = window.inner.lock(); - window.show_button(TopButton::Close, 1, &mut inner); - window.show_button(TopButton::MinimizeMaximize, 1, &mut inner); - window.show_button(TopButton::Hide, 1, &mut inner); - } - - let mut wm = wm_ref.lock(); - wm.set_active(&window.inner, false)?; - - // Currently, refresh the whole screen instead of just the new window's bounds - // wm.refresh_bottom_windows(Some(window_bounding_box), true)?; - wm.refresh_bottom_windows(Option::::None, true)?; - - Ok(window) - } - - - /// Tries to receive an `Event` that has been sent to this `Window`. - /// If no events exist on the queue, it returns `Ok(None)`. - /// - /// "Internal" events will be automatically handled rather than returned. - /// If an error occurs while obtaining the event (or when handling internal events), - /// - /// Otherwise, the event at the front of this window's event queue will be popped off and returned. - pub fn handle_event(&mut self) -> Result, &'static str> { - let mut call_later_do_refresh_floating_border = false; - let mut call_later_do_move_active_window = false; - let mut need_to_set_active = false; - let mut need_refresh_three_button = false; - - let wm_ref = window_manager::WINDOW_MANAGER.get().ok_or("The window manager is not initialized")?; - - let is_active = { - let wm = wm_ref.lock(); - wm.is_active(&self.inner) - }; - if is_active != self.last_is_active { - self.draw_border(is_active); - self.last_is_active = is_active; - let mut inner = self.inner.lock(); - self.show_button(TopButton::Close, 1, &mut inner); - self.show_button(TopButton::MinimizeMaximize, 1, &mut inner); - self.show_button(TopButton::Hide, 1, &mut inner); - } - - // If we cannot handle this event as an "internal" event (e.g., clicking on the window title bar or border), - // we simply return that event from this function such that the application can handle it. - let mut unhandled_event: Option = None; - - - while let Some(event) = self.event_consumer.pop() { - // TODO FIXME: for a performant design, the goal is to AVOID holding the lock on `inner` as much as possible. - // That means that most of the drawing logic should be moved into the `window_inner` crate itself. - let mut inner = self.inner.lock(); - let (width, height) = inner.get_size(); - - match event { - Event::MousePositionEvent(ref mouse_event) => { - match inner.moving { - WindowMovingStatus::Moving(_) => { - // only wait for left button up to exit this mode - if !mouse_event.left_button_hold { - self.last_mouse_position_event = mouse_event.clone(); - call_later_do_move_active_window = true; - } - call_later_do_refresh_floating_border = true; - }, - WindowMovingStatus::Stationary => { - if (mouse_event.coordinate.y as usize) < inner.title_bar_height - && (mouse_event.coordinate.x as usize) < width - { - // the region of title bar - let r2 = WINDOW_RADIUS * WINDOW_RADIUS; - let mut is_three_button = false; - for i in 0..3 { - let dcoordinate = Coord::new( - mouse_event.coordinate.x - - WINDOW_BUTTON_BIAS_X as isize - - (i as isize) * WINDOW_BUTTON_BETWEEN as isize, - mouse_event.coordinate.y - inner.title_bar_height as isize / 2, - ); - if dcoordinate.x * dcoordinate.x + dcoordinate.y * dcoordinate.y - <= r2 as isize - { - is_three_button = true; - if mouse_event.left_button_hold { - self.show_button(TopButton::from(i), 2, &mut inner); - need_refresh_three_button = true; - } else { - self.show_button(TopButton::from(i), 0, &mut inner); - need_refresh_three_button = true; - if self.last_mouse_position_event.left_button_hold { - // Kevin: disabling the close button until it actually works - /* - // click event - if i == 0 { - debug!("close window"); - return Err("user close window"); - // window will not close until app drop self - } - */ - } - } - } else { - self.show_button(TopButton::from(i), 1, &mut inner); - need_refresh_three_button = true; - } - } - // check if user clicked and held the title bar, which means user wanted to move the window - if !is_three_button - && !self.last_mouse_position_event.left_button_hold - && mouse_event.left_button_hold - { - inner.moving = WindowMovingStatus::Moving(mouse_event.gcoordinate); - call_later_do_refresh_floating_border = true; - } - } else { - // The mouse event occurred within the actual window content, not in the title bar. - // Thus, we let the caller handle it. - unhandled_event = Some(Event::MousePositionEvent(mouse_event.clone())); - } - if (mouse_event.coordinate.y as usize) < height - && (mouse_event.coordinate.x as usize) < width - && !self.last_mouse_position_event.left_button_hold - && mouse_event.left_button_hold - { - need_to_set_active = true; - } - self.last_mouse_position_event = mouse_event.clone(); - } - } - } - unhandled => { - unhandled_event = Some(unhandled); - } - } - - // Immediately return any unhandled events to the caller - // before we loop back to handle additional events. - if unhandled_event.is_some() { - break; - } - } - - let mut wm = wm_ref.lock(); - if need_to_set_active { - wm.set_active(&self.inner, true)?; - } - - if need_refresh_three_button { - let area = self.get_button_area(); - wm.refresh_active_window(Some(area))?; - wm.refresh_mouse()?; - } - - if call_later_do_refresh_floating_border { - wm.move_floating_border()?; - } - - if call_later_do_move_active_window { - wm.move_active_window()?; - self.inner.lock().moving = WindowMovingStatus::Stationary; - } - - Ok(unhandled_event) - } - - /// Renders the area of this `Window` specified by the given `bounding_box`, - /// which is relative to the top-left coordinate of this `Window`. - /// - /// Refreshes the whole window if `bounding_box` is `None`. - /// - /// This method should be invoked after updating the window's contents in order to see its new content. - pub fn render(&mut self, bounding_box: Option) -> Result<(), &'static str> { - - // Induced bug rendering attempting to access out of bound memory - #[cfg(downtime_eval)] - { - if bounding_box.unwrap().top_left == Coord::new(150,150) { - unsafe { *(0x5050DEADBEEF as *mut usize) = 0x5555_5555_5555; } - } - } - - let wm_ref = WINDOW_MANAGER.get().ok_or("The static window manager was not yet initialized")?; - - // Convert the given relative `bounding_box` to an absolute one (relative to the screen, not the window). - let coordinate = { - let window = self.inner.lock(); - window.get_position() - }; - let absolute_bounding_box = bounding_box.map(|bb| bb + coordinate); - - wm_ref.lock().refresh_windows(absolute_bounding_box) - } - - /// Returns a `Rectangle` describing the position and dimensions of this Window's content region, - /// i.e., the area within the window excluding the title bar and border - /// that is available for rendering application content. - /// - /// The returned `Rectangle` is expressed relative to this Window's position. - pub fn area(&self) -> Rectangle { - self.inner.lock().content_area() - } - - /// Returns an immutable reference to this window's virtual `Framebuffer`. - pub fn framebuffer(&self) -> FramebufferRef { - FramebufferRef::new( - self.inner.lock(), - |guard| guard.framebuffer(), - ) - } - - /// Returns a mutable reference to this window's virtual `Framebuffer`. - pub fn framebuffer_mut(&mut self) -> FramebufferRefMut { - FramebufferRefMut::new( - self.inner.lock(), - |guard| guard.framebuffer(), - |guard| guard.framebuffer_mut(), - ) - } - - /// Returns `true` if this window is the currently active window. - /// - /// Obtains the lock on the window manager instance. - pub fn is_active(&self) -> bool { - WINDOW_MANAGER.get() - .map(|wm| wm.lock().is_active(&self.inner)) - .unwrap_or(false) - } - - /// Draw the border of this window, with argument of whether this window is active now - fn draw_border(&mut self, active: bool) { - let mut inner = self.inner.lock(); - let border_size = inner.border_size; - let title_bar_height = inner.title_bar_height; - - // first draw left, bottom, right border - let border_color = if active { - WINDOW_BORDER_COLOR_ACTIVE_BOTTOM - } else { - WINDOW_BORDER_COLOR_INACTIVE - }; - let (width, height) = inner.get_size(); - - framebuffer_drawer::draw_rectangle( - inner.framebuffer_mut(), - Coord::new(0, title_bar_height as isize), - border_size, - height - title_bar_height, - border_color.into(), - ); - - framebuffer_drawer::draw_rectangle( - inner.framebuffer_mut(), - Coord::new(0, (height - border_size) as isize), - width, - border_size, - border_color.into(), - ); - framebuffer_drawer::draw_rectangle( - inner.framebuffer_mut(), - Coord::new( - (width - border_size) as isize, - title_bar_height as isize, - ), - border_size, - height - title_bar_height, - border_color.into(), - ); - - // then draw the title bar - if active { - for i in 0..title_bar_height { - framebuffer_drawer::draw_rectangle( - inner.framebuffer_mut(), - Coord::new(0, i as isize), - width, - 1, - framebuffer::Pixel::weight_blend( - WINDOW_BORDER_COLOR_ACTIVE_BOTTOM.into(), - WINDOW_BORDER_COLOR_ACTIVE_TOP.into(), - (i as f32) / (title_bar_height as f32) - ) - ); - } - } else { - framebuffer_drawer::draw_rectangle( - inner.framebuffer_mut(), - Coord::new(0, 0), - width, - title_bar_height, - border_color.into(), - ); - } - - // draw radius finally - let r2 = WINDOW_RADIUS * WINDOW_RADIUS; - let trans_pixel = color::TRANSPARENT.into(); - - for i in 0..WINDOW_RADIUS { - for j in 0..WINDOW_RADIUS { - let dx1 = WINDOW_RADIUS - i; - let dy1 = WINDOW_RADIUS - j; - if dx1 * dx1 + dy1 * dy1 > r2 { - // draw this to transparent - inner.framebuffer_mut().overwrite_pixel(Coord::new(i as isize, j as isize), trans_pixel); - inner.framebuffer_mut().overwrite_pixel(Coord::new((width - i - 1) as isize, j as isize), trans_pixel); - } - } - } - } - - /// show three button with status. state = 0,1,2 for three different color - fn show_button(&self, button: TopButton, state: usize, inner: &mut WindowInner) { - let y = inner.title_bar_height / 2; - let x = WINDOW_BUTTON_BIAS_X - + WINDOW_BUTTON_BETWEEN - * match button { - TopButton::Close => 0, - TopButton::MinimizeMaximize => 1, - TopButton::Hide => 2, - }; - let color = match button { - TopButton::Close => WINDOW_BUTTON_COLOR_CLOSE, - TopButton::MinimizeMaximize => WINDOW_BUTTON_COLOR_MINIMIZE_MAMIMIZE, - TopButton::Hide => WINDOW_BUTTON_COLOR_HIDE, - }; - framebuffer_drawer::draw_circle( - inner.framebuffer_mut(), - Coord::new(x as isize, y as isize), - WINDOW_BUTTON_SIZE, - framebuffer::Pixel::weight_blend( - color::BLACK.into(), - color.into(), - 0.2f32 * (state as f32), - ), - ); - } - - /// Gets the rectangle occupied by the three buttons - fn get_button_area(&self) -> Rectangle { - let inner = self.inner.lock(); - let width = inner.get_size().0; - Rectangle { - top_left: Coord::new(0, 0), - bottom_right: Coord::new(width as isize, inner.title_bar_height as isize) - } - } -} - -impl Drop for Window{ - fn drop(&mut self){ - if let Some(wm) = WINDOW_MANAGER.get() { - if let Err(err) = wm.lock().delete_window(&self.inner) { - error!("Failed to delete_window upon drop: {:?}", err); - } - } else { - error!("BUG: Could not delete_window upon drop because the window manager was not initialized"); - } - } -} - -/// A wrapper around a locked inner window that immutably derefs to a `Framebuffer`. -/// -/// The lock is auto-released when this object is dropped. -pub type FramebufferRef<'g> = DerefsTo, Framebuffer>; - -/// A wrapper around a locked inner window that mutably derefs to a `Framebuffer`. -/// -/// The lock is auto-released when this object is dropped. -pub type FramebufferRefMut<'g> = DerefsToMut, Framebuffer>; diff --git a/kernel/window_inner/Cargo.toml b/kernel/window_inner/Cargo.toml deleted file mode 100644 index f0451bbdb5..0000000000 --- a/kernel/window_inner/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "window_inner" -version = "0.1.0" -authors = ["Yue Wu ", "Wenqiu Yu "] -description = "allocate new windows and manage a list of existing windows" - -[dependencies] -mpmc = "0.1.6" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.shapes] -path = "../shapes" - -[dependencies.event_types] -path = "../event_types" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/window_inner/src/lib.rs b/kernel/window_inner/src/lib.rs deleted file mode 100644 index ea0029c3ee..0000000000 --- a/kernel/window_inner/src/lib.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! The `WindowInner` struct is the internal representation of a `Window` used by the window manager. -//! -//! In comparison, the `Window` struct is application-facing, meaning it is used by (owned by) -//! and exposed directly to applications or tasks that wish to display content. -//! -//! The `WindowInner` in the window_manager-facing version of the `Window`, -//! and each `Window` contains a reference to its `WindowInner`. -//! -//! The window manager typically holds `Weak` references to a `WindowInner` struct, -//! which allows it to control the window itself and handle non-application-related components of the window, -//! such as the title bar, border, etc. -//! -//! It also allows the window manager to control the window, e.g., move, hide, show, or resize it -//! in a way that applications may not be able to do. - -#![no_std] - -extern crate mpmc; -extern crate event_types; -extern crate framebuffer; -extern crate shapes; - -use mpmc::Queue; -use event_types::{Event}; -use framebuffer::{Framebuffer, AlphaPixel}; -use shapes::{Coord, Rectangle}; - - -// The title bar height, in number of pixels -pub const DEFAULT_TITLE_BAR_HEIGHT: usize = 16; -// left, right, bottom border size, in number of pixels -pub const DEFAULT_BORDER_SIZE: usize = 2; - - -/// Whether a window is moving (being dragged by the mouse). -pub enum WindowMovingStatus { - /// The window is not in motion. - Stationary, - /// The window is currently in motion. - /// The enclosed `Coord` represents the initial position of the window before it started moving. - Moving(Coord), -} - -/// The `WindowInner` struct is the internal system-facing representation of a window. -/// Its members and functions describe the size, state, and events related to window handling, -/// including elements like: -/// * The underlying virtual framebuffer to which the window is rendered, -/// * THe location and dimensions of the window in the final screen, -/// * The window's title bar, buttons, and borders, -/// * Queues for events that have been received by this window, and more. -/// -/// The window manager directly interacts with instances of `WindowInner` rather than `Window`, -/// and the application tasks should not have direct access to this struct for correctness reasons. -/// See the crate-level documentation for more details about how to use this -/// and how it differs from `Window`. -pub struct WindowInner { - /// The position of the top-left corner of the window, - /// expressed relative to the top-left corner of the screen. - coordinate: Coord, - /// The width of the border in pixels. - /// By default, there is a border on the left, right, and bottom edges of the window. - pub border_size: usize, - /// The height of title bar in pixels. - /// By default, there is one title bar at the top edge of the window. - pub title_bar_height: usize, - /// The producer side of this window's event queue. - /// Entities that want to send events to this window (or the application that owns this window) - /// should push events onto this queue. - /// - /// The corresponding consumer for this event queue is found in the `Window` struct - /// that created and owns this `WindowInner` instance. - event_producer: Queue, // event output used by window manager - /// The virtual framebuffer that is used exclusively for rendering only this window. - framebuffer: Framebuffer, - /// Whether a window is moving or stationary. - /// - /// TODO: FIXME (kevinaboos): this should be private, and window moving logic should be moved into this crate. - pub moving: WindowMovingStatus, -} - -impl WindowInner { - /// Creates a new `WindowInner` object backed by the given `framebuffer` - /// and that will be rendered at the given `coordinate` relative to the screen. - pub fn new( - coordinate: Coord, - framebuffer: Framebuffer, - event_producer: Queue, - ) -> WindowInner { - WindowInner { - coordinate, - border_size: DEFAULT_BORDER_SIZE, - title_bar_height: DEFAULT_TITLE_BAR_HEIGHT, - event_producer, - framebuffer, - moving: WindowMovingStatus::Stationary, - } - } - - /// Returns `true` if the given `coordinate` (relative to the top-left corner of this window) - /// is within the bounds of this window. - pub fn contains(&self, coordinate: Coord) -> bool { - self.framebuffer.contains(coordinate) - } - - /// Gets the size of a window in pixels - pub fn get_size(&self) -> (usize, usize) { - self.framebuffer.get_size() - } - - /// Gets the top-left position of the window relative to the top-left of the screen - pub fn get_position(&self) -> Coord { - self.coordinate - } - - /// Sets the top-left position of the window relative to the top-left of the screen - pub fn set_position(&mut self, coordinate: Coord) { - self.coordinate = coordinate; - } - - /// Returns an immutable reference to this window's virtual Framebuffer. - pub fn framebuffer(&self) -> &Framebuffer { - &self.framebuffer - } - - /// Returns a mutable reference to this window's virtual Framebuffer. - pub fn framebuffer_mut(&mut self) -> &mut Framebuffer { - &mut self.framebuffer - } - - /// Returns the pixel value at the given `coordinate`, - /// if the `coordinate` is within the window's bounds. - pub fn get_pixel(&self, coordinate: Coord) -> Option { - self.framebuffer.get_pixel(coordinate) - } - - /// Returns the size of the Window border in pixels. - /// There is a border drawn on the left, right, and bottom edges. - pub fn get_border_size(&self) -> usize { - self.border_size - } - - /// Returns the size of the Window title bar in pixels. - /// There is a title bar drawn on the top edge of the Window. - pub fn get_title_bar_height(&self) -> usize { - self.title_bar_height - } - - /// Returns the position and dimensions of the Window's content region, - /// i.e., the area within the window excluding the title bar and border. - /// - /// The returned `Rectangle` is expressed relative to this Window's position. - pub fn content_area(&self) -> Rectangle { - let (window_width, window_height) = self.get_size(); - // There is one title bar on top, and a border on the left, right, and bottom - let top_left = Coord::new(self.border_size as isize, self.title_bar_height as isize); - let bottom_right = Coord::new((window_width - self.border_size) as isize, (window_height - self.border_size) as isize); - Rectangle { top_left, bottom_right } - } - - /// Resizes and moves this window to fit the given `Rectangle` that describes its new position. - pub fn resize(&mut self, new_position: Rectangle) -> Result<(), &'static str> { - // First, perform the actual resize of the inner window - self.coordinate = new_position.top_left; - self.framebuffer = Framebuffer::new(new_position.width(), new_position.height(), None)?; - - // Second, send a resize event to that application window (the `Window` object) - // so it knows to refresh its display. - // Rather than send the total size of the whole window, - // we instead send the size and position of the inner content area of the window. - // This prevents the application from thinking it can render over the area - // that contains this window's title bar or border. - self.send_event(Event::new_window_resize_event(self.content_area())) - .map_err(|_e| "Failed to enqueue the resize event; window event queue was full.")?; - - Ok(()) - } - - /// Sends the given `event` to this window. - /// - /// If the event queue was full, `Err(event)` is returned. - pub fn send_event(&self, event: Event) -> Result<(), Event> { - self.event_producer.push(event) - } -} diff --git a/kernel/window_manager/Cargo.toml b/kernel/window_manager/Cargo.toml deleted file mode 100644 index aaefc57836..0000000000 --- a/kernel/window_manager/Cargo.toml +++ /dev/null @@ -1,64 +0,0 @@ -[package] -name = "window_manager" -version = "0.1.0" -authors = ["Yue Wu ", "Wenqiu Yu "] -description = "allocate new windows and manage a list of existing windows" - -[dependencies] -spin = "0.9.4" -mpmc = "0.1.6" - -[dependencies.log] -version = "0.4.8" - -[dependencies.framebuffer_drawer] -path = "../framebuffer_drawer" - -[dependencies.window_inner] -path = "../window_inner" - -[dependencies.compositor] -path = "../compositor" - -[dependencies.framebuffer_compositor] -path = "../framebuffer_compositor" - -[dependencies.shapes] -path = "../shapes" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.color] -path = "../color" - -[dependencies.event_types] -path = "../event_types" - -[dependencies.font] -path = "../font" - -[dependencies.lazy_static] -features = ["spin_no_std"] -version = "1.4.0" - -[dependencies.mod_mgmt] -path = "../mod_mgmt" - -[dependencies.spawn] -path = "../spawn" - -[dependencies.mouse_data] -path = "../../libs/mouse_data" - -[dependencies.path] -path = "../../kernel/path" - -[lib] -crate-type = ["rlib"] - -[dependencies.keycodes_ascii] -path = "../../libs/keycodes_ascii" - -[dependencies.scheduler] -path = "../../kernel/scheduler" diff --git a/kernel/window_manager/src/lib.rs b/kernel/window_manager/src/lib.rs deleted file mode 100644 index baca360872..0000000000 --- a/kernel/window_manager/src/lib.rs +++ /dev/null @@ -1,818 +0,0 @@ -//! This crate acts as a manager of a list of windows. It defines a `WindowManager` structure and an instance of it. -//! -//! A window manager holds a set of `WindowInner` objects, including an active window, a list of shown windows and a list of hidden windows. The hidden windows are totally overlapped by others. -//! -//! A window manager owns a bottom framebuffer and a top framebuffer. The bottom is the background of the desktop and the top framebuffer contains a floating window border and a mouse arrow. -//! A window manager also contains a final framebuffer which is mapped to the screen. In refreshing an area, the manager will render all the framebuffers to the final one in order: bottom -> hide list -> showlist -> active -> top. -//! -//! The window manager provides methods to update within some bounding boxes rather than the whole screen for better performance. - -#![no_std] - -extern crate spin; -#[macro_use] extern crate log; -#[macro_use] extern crate alloc; -extern crate mpmc; -extern crate event_types; -extern crate compositor; -extern crate framebuffer; -extern crate framebuffer_compositor; -extern crate framebuffer_drawer; -extern crate keycodes_ascii; -extern crate mod_mgmt; -extern crate mouse_data; -extern crate path; -extern crate scheduler; -extern crate spawn; -extern crate window_inner; -extern crate shapes; -extern crate color; - -use alloc::collections::VecDeque; -use alloc::string::ToString; -use alloc::sync::{Arc, Weak}; -use alloc::vec::{Vec}; -use compositor::{Compositor, FramebufferUpdates, CompositableRegion}; - -use mpmc::Queue; -use event_types::{Event, MousePositionEvent}; -use framebuffer::{Framebuffer, AlphaPixel}; -use color::{Color}; -use shapes::{Coord, Rectangle}; -use framebuffer_compositor::{FRAME_COMPOSITOR}; -use keycodes_ascii::{KeyAction, KeyEvent, Keycode}; -use mouse_data::MouseEvent; -use path::Path; -use spin::{Mutex, Once}; -use window_inner::{WindowInner, WindowMovingStatus}; - -/// The instance of the default window manager -pub static WINDOW_MANAGER: Once> = Once::new(); - -/// The width and height size of mouse in number of pixels. -const MOUSE_POINTER_SIZE_Y: usize = 18; -const MOUSE_POINTER_SIZE_X: usize = 11; -/// The mouse pointer image defined as a 2-D pixel array. -static MOUSE_POINTER_IMAGE: [[Color; MOUSE_POINTER_SIZE_Y]; MOUSE_POINTER_SIZE_X] = { - const T: Color = color::TRANSPARENT; - const C: Color = color::BLACK; // Cursor - const B: Color = color::WHITE; // Border - [ - [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, T, T], - [T, B, C, C, C, C, C, C, C, C, C, C, C, C, B, T, T, T], - [T, T, B, C, C, C, C, C, C, C, C, C, C, B, T, T, T, T], - [T, T, T, B, C, C, C, C, C, C, C, C, B, T, T, T, T, T], - [T, T, T, T, B, C, C, C, C, C, C, C, C, B, B, T, T, T], - [T, T, T, T, T, B, C, C, C, C, C, C, C, C, C, B, B, T], - [T, T, T, T, T, T, B, C, C, C, C, B, B, C, C, C, C, B], - [T, T, T, T, T, T, T, B, C, C, B, T, T, B, B, C, B, T], - [T, T, T, T, T, T, T, T, B, C, B, T, T, T, T, B, B, T], - [T, T, T, T, T, T, T, T, T, B, B, T, T, T, T, T, T, T], - [T, T, T, T, T, T, T, T, T, T, B, T, T, T, T, T, T, T], - ] -}; - -// the border indicating new window position and size -const WINDOW_BORDER_SIZE: usize = 3; -// border's inner color -const WINDOW_BORDER_COLOR_INNER: Color = Color::new(0x00CA6F1E); - -/// Window manager structure which maintains a list of windows and a mouse. -pub struct WindowManager { - /// those window currently not shown on screen - hide_list: VecDeque>>, - /// those window shown on screen that may overlapping each other - show_list: VecDeque>>, - /// the only active window, receiving all keyboard events (except for those remained for WM) - active: Weak>, // this one is not in show_list - /// current mouse position - mouse: Coord, - /// If a window is being repositioned (e.g., by dragging it), this is the position of that window's border - repositioned_border: Option, - /// The bottom framebuffer typically contains the background/wallpaper image, - /// which is displayed by default when no other windows exist on top of it. - bottom_fb: Framebuffer, - /// The top framebuffer is used for overlaying visual elements atop the rest of the windows, - /// e.g., the mouse pointer, the border of a window being dragged/moved, etc. - top_fb: Framebuffer, - /// The final framebuffer which is mapped to the screen (the actual display device). - pub final_fb: Framebuffer, -} - -impl WindowManager { - /// Sets one window as active, push last active (if exists) to top of show_list. if `refresh` is `true`, will then refresh the window's area. - /// Returns whether this window is the first active window in the manager. - /// - /// TODO FIXME: (kevinaboos) remove this dumb notion of "first active". This is a bad hack. - pub fn set_active( - &mut self, - inner_ref: &Arc>, - refresh: bool, - ) -> Result { - // if it is currently actived, just return - let first_active = match self.active.upgrade() { - Some(current_active) => { - if Arc::ptr_eq(&(current_active), inner_ref) { - return Ok(true); // do nothing - } else { - // save this to show_list - self.show_list.push_front(self.active.clone()); - } - false - } - None => true, - }; - - match self.is_window_in_show_list(&inner_ref) { - // remove item in current list - Some(i) => { - self.show_list.remove(i); - } - None => {} - } - match self.is_window_in_hide_list(&inner_ref) { - // remove item in current list - Some(i) => { - self.hide_list.remove(i); - } - None => {} - } - self.active = Arc::downgrade(inner_ref); - let area = { - let window = inner_ref.lock(); - let top_left = window.get_position(); - let (width, height) = window.get_size(); - Rectangle { - top_left: top_left, - bottom_right: top_left + (width as isize, height as isize) - } - }; - if refresh { - self.refresh_bottom_windows(Some(area), true)?; - } - Ok(first_active) - } - - /// Returns the index of a window if it is in the show list - fn is_window_in_show_list(&mut self, window: &Arc>) -> Option { - let mut i = 0_usize; - for item in self.show_list.iter() { - if let Some(item_ptr) = item.upgrade() { - if Arc::ptr_eq(&(item_ptr), window) { - return Some(i); - } - } - i += 1; - } - None - } - - /// Returns the index of a window if it is in the hide list - fn is_window_in_hide_list(&mut self, window: &Arc>) -> Option { - let mut i = 0_usize; - for item in self.hide_list.iter() { - if let Some(item_ptr) = item.upgrade() { - if Arc::ptr_eq(&(item_ptr), window) { - return Some(i); - } - } - i += 1; - } - None - } - - /// delete a window and refresh its region - pub fn delete_window(&mut self, inner_ref: &Arc>) -> Result<(), &'static str> { - let (top_left, bottom_right) = { - let inner = inner_ref.lock(); - let top_left = inner.get_position(); - let (width, height) = inner.get_size(); - let bottom_right = top_left + (width as isize, height as isize); - (top_left, bottom_right) - }; - let area = Some( - Rectangle { - top_left: top_left, - bottom_right: bottom_right - } - ); - - if let Some(current_active) = self.active.upgrade() { - if Arc::ptr_eq(¤t_active, inner_ref) { - self.refresh_bottom_windows(area, false)?; - if let Some(window) = self.show_list.remove(0) { - self.active = window; - } else if let Some(window) = self.hide_list.remove(0) { - self.active = window; - } else { - self.active = Weak::new(); // delete reference - } - return Ok(()); - } - } - - if let Some(index) = self.is_window_in_show_list(inner_ref) { - self.show_list.remove(index); - self.refresh_windows(area)?; - return Ok(()) - } - - if let Some(index) = self.is_window_in_hide_list(inner_ref) { - self.hide_list.remove(index); - return Ok(()) - } - Err("cannot find this window") - } - - /// Refresh the region in `bounding_box`. Only render the bottom final framebuffer and windows. Ignore the active window if `active` is false. - pub fn refresh_bottom_windows( - &mut self, - bounding_box: impl IntoIterator + Clone, - active: bool, - ) -> Result<(), &'static str> { - // bottom framebuffer - let bottom_fb_area = FramebufferUpdates { - src_framebuffer: &self.bottom_fb, - coordinate_in_dest_framebuffer: Coord::new(0, 0), - }; - - // list of windows to be updated - let mut window_ref_list = Vec::new(); - for window in &self.hide_list { - if let Some(window_ref) = window.upgrade() { - window_ref_list.push(window_ref); - } - } - for window in &self.show_list { - if let Some(window_ref) = window.upgrade() { - window_ref_list.push(window_ref); - } - } - if active { - if let Some(window_ref) = self.active.upgrade() { - window_ref_list.push(window_ref) - } - } - - // lock windows - let locked_window_list = &window_ref_list.iter().map(|x| x.lock()).collect::>(); - - // create updated framebuffer info objects - let window_bufferlist = locked_window_list.iter().map(|window| { - FramebufferUpdates { - src_framebuffer: window.framebuffer(), - coordinate_in_dest_framebuffer: window.get_position(), - } - }); - - let buffer_iter = Some(bottom_fb_area).into_iter().chain(window_bufferlist); - FRAME_COMPOSITOR.lock().composite(buffer_iter, &mut self.final_fb, bounding_box)?; - - Ok(()) - } - - /// Refresh the region of `bounding_box` in the top framebuffer - pub fn refresh_top( - &mut self, - bounding_box: impl IntoIterator + Clone - ) -> Result<(), &'static str> { - let top_buffer = FramebufferUpdates { - src_framebuffer: &self.top_fb, - coordinate_in_dest_framebuffer: Coord::new(0, 0), - }; - - FRAME_COMPOSITOR.lock().composite(Some(top_buffer), &mut self.final_fb, bounding_box) - } - - /// Refresh the part in `bounding_box` of every window. `bounding_box` is a region relative to the top-left of the screen. Refresh the whole screen if the bounding box is None. - pub fn refresh_windows( - &mut self, - bounding_box: impl IntoIterator + Clone, - ) -> Result<(), &'static str> { - // reference of windows - let mut window_ref_list = Vec::new(); - for window in &self.hide_list { - if let Some(window_ref) = window.upgrade() { - window_ref_list.push(window_ref); - } - } - for window in &self.show_list { - if let Some(window_ref) = window.upgrade() { - window_ref_list.push(window_ref); - } - } - - if let Some(window_ref) = self.active.upgrade() { - window_ref_list.push(window_ref) - } - - // lock windows - let locked_window_list = &window_ref_list.iter().map(|x| x.lock()).collect::>(); - // create updated framebuffer info objects - let bufferlist = locked_window_list.iter().map(|window| { - FramebufferUpdates { - src_framebuffer: window.framebuffer(), - coordinate_in_dest_framebuffer: window.get_position(), - } - }); - - FRAME_COMPOSITOR.lock().composite(bufferlist, &mut self.final_fb, bounding_box) - } - - - /// Refresh the part in `bounding_box` of the active window. `bounding_box` is a region relative to the top-left of the screen. Refresh the whole screen if the bounding box is None. - pub fn refresh_active_window(&mut self, bounding_box: Option) -> Result<(), &'static str> { - if let Some(window_ref) = self.active.upgrade() { - let window = window_ref.lock(); - let buffer_update = FramebufferUpdates { - src_framebuffer: window.framebuffer(), - coordinate_in_dest_framebuffer: window.get_position(), - }; - FRAME_COMPOSITOR.lock().composite(Some(buffer_update), &mut self.final_fb, bounding_box) - } else { - Ok(()) - } - } - - /// Passes the given keyboard event to the currently active window. - fn pass_keyboard_event_to_window(&self, key_event: KeyEvent) -> Result<(), &'static str> { - let active_window = self.active.upgrade().ok_or("no window was set as active to receive a keyboard event")?; - active_window.lock().send_event(Event::new_keyboard_event(key_event)) - .map_err(|_e| "Failed to enqueue the keyboard event; window event queue was full.")?; - Ok(()) - } - - /// Passes the given mouse event to the window that the mouse is currently over. - /// - /// If the mouse is not over any window, an error is returned; - /// however, this error is quite common and expected when the mouse is not positioned within a window, - /// and is not a true failure. - fn pass_mouse_event_to_window(&self, mouse_event: MouseEvent) -> Result<(), &'static str> { - let coordinate = { &self.mouse }; - let mut event: MousePositionEvent = MousePositionEvent { - coordinate: Coord::new(0, 0), - gcoordinate: coordinate.clone(), - scrolling_up: mouse_event.movement.scroll_movement > 0, //TODO: might be more beneficial to save scroll_movement here - scrolling_down: mouse_event.movement.scroll_movement < 0, //FIXME: also might be the wrong way around - left_button_hold: mouse_event.buttons.left(), - right_button_hold: mouse_event.buttons.right(), - fourth_button_hold: mouse_event.buttons.fourth(), - fifth_button_hold: mouse_event.buttons.fifth(), - }; - - // TODO: FIXME: improve this logic to just send the mouse event to the top-most window in the entire WM list, - // not just necessarily the active one. (For example, scroll wheel events can be sent to non-active windows). - - - // first check the active one - if let Some(current_active) = self.active.upgrade() { - let current_active_win = current_active.lock(); - let current_coordinate = current_active_win.get_position(); - if current_active_win.contains(*coordinate - current_coordinate) || match current_active_win.moving { - WindowMovingStatus::Moving(_) => true, - _ => false, - }{ - event.coordinate = *coordinate - current_coordinate; - // debug!("pass to active: {}, {}", event.x, event.y); - current_active_win.send_event(Event::MousePositionEvent(event)) - .map_err(|_e| "Failed to enqueue the mouse event; window event queue was full.")?; - return Ok(()); - } - } - - // TODO FIXME: (kevinaboos): the logic below here is actually incorrect -- it could send mouse events to an invisible window below others. - - // then check show_list - for i in 0..self.show_list.len() { - if let Some(now_inner_mutex) = self.show_list[i].upgrade() { - let now_inner = now_inner_mutex.lock(); - let current_coordinate = now_inner.get_position(); - if now_inner.contains(*coordinate - current_coordinate) { - event.coordinate = *coordinate - current_coordinate; - now_inner.send_event(Event::MousePositionEvent(event)) - .map_err(|_e| "Failed to enqueue the mouse event; window event queue was full.")?; - return Ok(()); - } - } - } - - Err("the mouse position does not fall within the bounds of any window") - } - - /// Refresh the floating border, which is used to show the outline of a window while it is being moved. - /// `show` indicates whether to show the border or not. - /// `new_border` defines the rectangular outline of the border. - fn refresh_floating_border( - &mut self, - show: bool, - new_border: Rectangle, - ) -> Result<(), &'static str> { - // first clear old border if exists - match self.repositioned_border { - Some(border) => { - let pixels = self.draw_floating_border(&border, color::TRANSPARENT); - self.refresh_bottom_windows(pixels.into_iter(), true)?; - }, - None =>{} - } - - // then draw current border - if show { - let pixels = self.draw_floating_border(&new_border, WINDOW_BORDER_COLOR_INNER); - self.refresh_top(pixels.into_iter())?; - self.repositioned_border = Some(new_border); - } else { - self.repositioned_border = None; - } - - Ok(()) - } - - /// draw the floating border with `pixel`. Return the list of coordinates of pixels that were updated. - /// `border` indicates the position of the border as a rectangle. - /// `color` is the color of the floating border. - fn draw_floating_border(&mut self, border: &Rectangle, color: Color) -> Vec { - let mut coordinates = Vec::new(); - let pixel = color.into(); - for i in 0..(WINDOW_BORDER_SIZE) as isize { - let width = (border.bottom_right.x - border.top_left.x) - 2 * i; - let height = (border.bottom_right.y - border.top_left.y) - 2 * i; - let coordinate = border.top_left + (i as isize, i as isize); - if width <= 0 || height <= 0 { - break; - } - framebuffer_drawer::draw_rectangle( - &mut self.top_fb, - coordinate, - width as usize, - height as usize, - pixel - ); - - for m in 0..width { - coordinates.push(coordinate + (m, 0)); - coordinates.push(coordinate + (m, height)); - } - - for m in 1..height - 1 { - coordinates.push(coordinate + (0, m)); - coordinates.push(coordinate + (width, m)); - } - } - - coordinates - } - - /// take active window's base position and current mouse, move the window with delta - pub fn move_active_window(&mut self) -> Result<(), &'static str> { - if let Some(current_active) = self.active.upgrade() { - let border = Rectangle { - top_left: Coord::new(0, 0), - bottom_right: Coord::new(0, 0) - }; - self.refresh_floating_border(false, border)?; - - let (old_top_left, old_bottom_right, new_top_left, new_bottom_right) = { - let mut current_active_win = current_active.lock(); - let (current_x, current_y) = { - let m = &self.mouse; - (m.x as isize, m.y as isize) - }; - match current_active_win.moving { - WindowMovingStatus::Moving(base) => { - let old_top_left = current_active_win.get_position(); - let new_top_left = old_top_left + ((current_x - base.x), (current_y - base.y)); - let (width, height) = current_active_win.get_size(); - let old_bottom_right = old_top_left + (width as isize, height as isize); - let new_bottom_right = new_top_left + (width as isize, height as isize); - current_active_win.set_position(new_top_left); - (old_top_left, old_bottom_right, new_top_left, new_bottom_right) - }, - WindowMovingStatus::Stationary => { - return Err("The window is not moving"); - } - } - }; - self.refresh_bottom_windows(Some(Rectangle{top_left: old_top_left, bottom_right: old_bottom_right}), false)?; - - self.refresh_active_window(Some(Rectangle{top_left: new_top_left, bottom_right: new_bottom_right}))?; - self.refresh_mouse()?; - } else { - return Err("cannot find active window to move"); - } - Ok(()) - } - - /// Refresh the mouse display - pub fn refresh_mouse(&mut self) -> Result<(), &'static str> { - let bounding_box = Some(Rectangle { - top_left: self.mouse, - bottom_right: self.mouse + (MOUSE_POINTER_SIZE_X as isize, MOUSE_POINTER_SIZE_Y as isize) - }); - - self.refresh_top(bounding_box.into_iter()) - } - - /// Move mouse. `relative` indicates the new position relative to current position. - fn move_mouse(&mut self, relative: Coord) -> Result<(), &'static str> { - let old = self.mouse; - let mut new = old + relative; - - let (screen_width, screen_height) = self.get_screen_size(); - if new.x < 0 { - new.x = 0; - } - if new.y < 0 { - new.y = 0; - } - - // keep mouse pointer border in the screen when it is at the right or bottom side. - const MOUSE_POINTER_BORDER: isize = 3; - new.x = core::cmp::min(new.x, screen_width as isize - MOUSE_POINTER_BORDER); - new.y = core::cmp::min(new.y, screen_height as isize - MOUSE_POINTER_BORDER); - - self.move_mouse_to(new) - } - - // Move mouse to absolute position `new` - fn move_mouse_to(&mut self, new: Coord) -> Result<(), &'static str> { - // clear old mouse - for y in self.mouse.y..self.mouse.y + MOUSE_POINTER_SIZE_Y as isize { - for x in - self.mouse.x..self.mouse.x + MOUSE_POINTER_SIZE_X as isize { - let coordinate = Coord::new(x, y); - self.top_fb.overwrite_pixel(coordinate, color::TRANSPARENT.into()); - } - } - let bounding_box = Some(Rectangle { - top_left: self.mouse, - bottom_right: self.mouse + (MOUSE_POINTER_SIZE_X as isize, MOUSE_POINTER_SIZE_Y as isize) - }); - self.refresh_bottom_windows(bounding_box.into_iter(), true)?; - - // draw new mouse - self.mouse = new; - for y in new.y..new.y + MOUSE_POINTER_SIZE_Y as isize { - for x in new.x..new.x + MOUSE_POINTER_SIZE_X as isize { - let coordinate = Coord::new(x, y); - let pixel = MOUSE_POINTER_IMAGE[(x - new.x) as usize][(y - new.y) as usize].into(); - self.top_fb.overwrite_pixel(coordinate, pixel); - } - } - self.refresh_mouse()?; - - Ok(()) - } - - /// Move the floating border when a window is moving. - pub fn move_floating_border(&mut self) -> Result<(), &'static str> { - let (new_x, new_y) = { - let m = &self.mouse; - (m.x as isize, m.y as isize) - }; - - if let Some(current_active) = self.active.upgrade() { - let (is_draw, border_start, border_end) = { - let current_active_win = current_active.lock(); - match current_active_win.moving { - WindowMovingStatus::Moving(base) => { - // move this window - // for better performance, while moving window, only border is shown for indication - let coordinate = current_active_win.get_position(); - // let (current_x, current_y) = (coordinate.x, coordinate.y); - let (width, height) = current_active_win.get_size(); - let border_start = coordinate + (new_x - base.x, new_y - base.y); - let border_end = border_start + (width as isize, height as isize); - (true, border_start, border_end) - } - WindowMovingStatus::Stationary => (false, Coord::new(0, 0), Coord::new(0, 0)), - } - }; - let border = Rectangle { - top_left: border_start, - bottom_right: border_end, - }; - self.refresh_floating_border(is_draw, border)?; - } else { - let border = Rectangle { - top_left: Coord::new(0, 0), - bottom_right: Coord::new(0, 0), - }; - self.refresh_floating_border(false, border)?; - } - - Ok(()) - } - - /// Returns true if the given `window` is the currently active window. - pub fn is_active(&self, window: &Arc>) -> bool { - self.active.upgrade() - .map(|active| Arc::ptr_eq(&active, window)) - .unwrap_or(false) - } - - /// Returns the `(width, height)` in pixels of the screen itself (the final framebuffer). - pub fn get_screen_size(&self) -> (usize, usize) { - self.final_fb.get_size() - } -} - -/// Initialize the window manager. It returns (keyboard_producer, mouse_producer) for the I/O devices. -pub fn init() -> Result<(Queue, Queue), &'static str> { - let final_fb: Framebuffer = framebuffer::init()?; - let (width, height) = final_fb.get_size(); - - let mut bottom_fb = Framebuffer::new(width, height, None)?; - let mut top_fb = Framebuffer::new(width, height, None)?; - let (screen_width, screen_height) = bottom_fb.get_size(); - bottom_fb.fill(color::LIGHT_GRAY.into()); - top_fb.fill(color::TRANSPARENT.into()); - - // Initial position for the mouse - let mouse = Coord { - x: screen_width as isize / 2, - y: screen_height as isize / 2, - }; - - // Initialize static window manager - let window_manager = WindowManager { - hide_list: VecDeque::new(), - show_list: VecDeque::new(), - active: Weak::new(), - mouse, - repositioned_border: None, - bottom_fb, - top_fb, - final_fb, - }; - WINDOW_MANAGER.call_once(|| Mutex::new(window_manager)); - - // keyinput queue initialization - let key_consumer: Queue = Queue::with_capacity(100); - let key_producer = key_consumer.clone(); - - // mouse input queue initialization - let mouse_consumer: Queue = Queue::with_capacity(100); - let mouse_producer = mouse_consumer.clone(); - - spawn::new_task_builder(window_manager_loop, (key_consumer, mouse_consumer)) - .name("window_manager_loop".to_string()) - .spawn()?; - - Ok((key_producer, mouse_producer)) -} - -/// handles all keyboard and mouse movement in this window manager -fn window_manager_loop( - (key_consumer, mouse_consumer): (Queue, Queue), -) -> Result<(), &'static str> { - loop { - let event_opt = key_consumer.pop() - .or_else(||mouse_consumer.pop()) - .or_else(||{ - scheduler::schedule(); - None - }); - - if let Some(event) = event_opt { - // Currently, the window manager only cares about keyboard or mouse events - match event { - Event::KeyboardEvent(ref input_event) => { - let key_input = input_event.key_event; - keyboard_handle_application(key_input)?; - } - Event::MouseMovementEvent(ref mouse_event) => { - //as isize to fit larger values - let mut x = mouse_event.movement.x_movement as isize; - let mut y = mouse_event.movement.y_movement as isize; - - // need to combine mouse events if there pending a lot - while let Some(next_event) = mouse_consumer.pop() { - match next_event { - Event::MouseMovementEvent(ref next_mouse_event) => { - if next_mouse_event.movement.scroll_movement - == mouse_event.movement.scroll_movement - && next_mouse_event.buttons.left() - == mouse_event.buttons.left() - && next_mouse_event.buttons.right() - == mouse_event.buttons.right() - && next_mouse_event.buttons.fourth() - == mouse_event.buttons.fourth() - && next_mouse_event.buttons.fifth() - == mouse_event.buttons.fifth() { - x = x.saturating_add(next_mouse_event.movement.x_movement as isize); - y = y.saturating_add(next_mouse_event.movement.y_movement as isize); - } - } - _ => { - break; - } - } - // next_event.mark_completed(); - } - if x != 0 || y != 0 { - let mut wm = WINDOW_MANAGER - .get() - .ok_or("The static window manager was not yet initialized")? - .lock(); - wm.move_mouse( - Coord::new(x as isize, -(y as isize)) - )?; - } - cursor_handle_application(mouse_event.clone())?; // tell the event to application, or moving window - } - _other => { - trace!("WINDOW_MANAGER: ignoring unexpected event: {:?}", _other); - } - } - } - } -} - -/// handle keyboard event, push it to the active window if one exists -fn keyboard_handle_application(key_input: KeyEvent) -> Result<(), &'static str> { - let win_mgr = WINDOW_MANAGER.get().ok_or("The window manager was not yet initialized")?; - - // First, we handle keyboard shortcuts understood by the window manager. - - // "Super + Arrow" will resize and move windows to the specified half of the screen (left, right, top, or bottom) - if key_input.modifiers.is_super_key() && key_input.action == KeyAction::Pressed { - let screen_dimensions = win_mgr.lock().get_screen_size(); - let (width, height) = (screen_dimensions.0 as isize, screen_dimensions.1 as isize); - let new_position: Option = match key_input.keycode { - Keycode::Left => Some(Rectangle { - top_left: Coord { x: 0, y: 0 }, - bottom_right: Coord { x: width / 2, y: height }, - }), - Keycode::Right => Some(Rectangle { - top_left: Coord { x: width / 2, y: 0 }, - bottom_right: Coord { x: width, y: height }, - }), - Keycode::Up => Some(Rectangle { - top_left: Coord { x: 0, y: 0 }, - bottom_right: Coord { x: width, y: height / 2 }, - }), - Keycode::Down => Some(Rectangle { - top_left: Coord { x: 0, y: height / 2 }, - bottom_right: Coord { x: width, y: height }, - }), - _ => None, - }; - - if let Some(position) = new_position { - let mut wm = win_mgr.lock(); - if let Some(active_window) = wm.active.upgrade() { - debug!("window_manager: resizing active window to {:?}", new_position); - active_window.lock().resize(position)?; - - // force refresh the entire screen for now - // TODO: perform a proper screen refresh here: only refresh the area that contained the active_window's old bounds. - wm.refresh_bottom_windows(Option::::None, true)?; - } - } - - return Ok(()); - } - - // Spawn a new terminal via Ctrl+Alt+T - if key_input.modifiers.is_control() - && key_input.modifiers.is_alt() - && key_input.keycode == Keycode::T - && key_input.action == KeyAction::Pressed - { - // Because this task (the window manager loop) runs in a kernel-only namespace, - // we have to create a new application namespace in order to be able to actually spawn a shell. - - let new_app_namespace = mod_mgmt::create_application_namespace(None)?; - let shell_objfile = new_app_namespace.dir().get_file_starting_with("shell-") - .ok_or("Couldn't find shell application file to run upon Ctrl+Alt+T")?; - let path = Path::new(shell_objfile.lock().get_absolute_path()); - spawn::new_application_task_builder(path, Some(new_app_namespace))? - .name(format!("shell")) - .spawn()?; - - debug!("window_manager: spawned new shell app in new app namespace."); - return Ok(()); - } - - // Any keyboard event unhandled above should be passed to the active window. - if let Err(_e) = win_mgr.lock().pass_keyboard_event_to_window(key_input) { - warn!("window_manager: failed to pass keyboard event to active window. Error: {:?}", _e); - // If no window is currently active, then something might be potentially wrong, - // but we can likely recover in the future when another window becomes active. - // Thus, we don't need to return a hard error here. - } - Ok(()) -} - -/// handle mouse event, push it to related window or anyone asked for it -fn cursor_handle_application(mouse_event: MouseEvent) -> Result<(), &'static str> { - let wm = WINDOW_MANAGER.get().ok_or("The static window manager was not yet initialized")?.lock(); - if let Err(_) = wm.pass_mouse_event_to_window(mouse_event) { - // the mouse event should be passed to the window that satisfies: - // 1. the mouse position is currently in the window area - // 2. the window is the top one (active window or show_list windows) under the mouse pointer - // if no window is found in this position, that is system background area. Add logic to handle those events later - } - Ok(()) -}