diff --git a/Cargo.lock b/Cargo.lock index 7f347682fd..8a0aaae0f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -374,6 +374,7 @@ dependencies = [ "no_drop", "ota_update_client", "page_attribute_table", + "porthole", "scheduler", "simd_personality", "spawn", @@ -383,7 +384,6 @@ dependencies = [ "tlb_shootdown", "tsc", "vga_buffer", - "window_manager", ] [[package]] @@ -457,14 +457,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" @@ -883,16 +875,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" @@ -1123,56 +1105,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]] @@ -1678,20 +1646,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]] @@ -2665,6 +2627,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" @@ -3280,7 +3268,6 @@ dependencies = [ "spin 0.9.4", "stdio", "task", - "window_manager", ] [[package]] @@ -3697,8 +3684,6 @@ dependencies = [ "async_channel", "color", "cpu", - "framebuffer", - "framebuffer_drawer", "getopts", "hpet", "log", @@ -3710,7 +3695,6 @@ dependencies = [ "spin 0.9.4", "task", "unified_channel", - "window", ] [[package]] @@ -3877,19 +3861,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" @@ -4501,61 +4472,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 bbe489e012..5fe0514b5c 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 { @@ -1246,7 +1246,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 @@ -1255,17 +1255,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 @@ -1278,36 +1278,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 { @@ -1315,14 +1317,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; } } @@ -1334,6 +1343,7 @@ impl Shell { } } } + } /// Shell internal command related methods. @@ -1372,7 +1382,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 2f6c42a924..3dc19a78f6 100644 --- a/kernel/captain/Cargo.toml +++ b/kernel/captain/Cargo.toml @@ -21,8 +21,8 @@ path = "../kernel_config" [dependencies.logger_x86_64] path = "../logger_x86_64" -[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 e0cf603b3f..c0d2baf966 100644 --- a/kernel/captain/src/lib.rs +++ b/kernel/captain/src/lib.rs @@ -18,6 +18,36 @@ #![no_std] extern crate alloc; +#[macro_use] extern crate log; + +extern crate kernel_config; // our configuration options, just a set of const definitions. +extern crate irq_safety; // for irq-safe locking and interrupt utilities +extern crate dfqueue; // decoupled, fault-tolerant queue + +extern crate logger; +extern crate memory; // the virtual memory subsystem +extern crate no_drop; +extern crate stack; +extern crate cpu; +extern crate mod_mgmt; +extern crate spawn; +extern crate tsc; +extern crate task; +extern crate interrupts; +extern crate acpi; +extern crate device_manager; +extern crate e1000; +extern crate scheduler; +#[cfg(mirror_log_to_vga)] #[macro_use] extern crate app_io; +extern crate first_application; +extern crate exceptions_full; +extern crate network_manager; +extern crate porthole; +extern crate tlb_shootdown; +extern crate multiple_heaps; +extern crate console; +#[cfg(simd_personality)] extern crate simd_personality; + use core::ops::DerefMut; use log::{error, info}; @@ -154,7 +184,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/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/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_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_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/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 fd417239ff..643c860fb7 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, - 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 72ee56649f..a72c916157 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 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,23 +70,31 @@ 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(); let mut start_idx = end_idx; // Grabs a max-size slice of the scrollback buffer (usually does not totally fit because of newlines) - let result = if end_idx > buffer_width * buffer_height { - self.scrollback_buffer.get(end_idx - buffer_width * buffer_height..end_idx) + if end_idx > buffer_width * buffer_height { + result = self + .scrollback_buffer + .get(end_idx - buffer_width * buffer_height..end_idx); } else { self.scrollback_buffer.get(0..end_idx) }; @@ -105,44 +109,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; @@ -161,26 +167,32 @@ 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 - (start_idx, (total_lines - 1) * buffer_width + last_line_chars) - + return ( + start_idx, + (total_lines - 1) * buffer_width + last_line_chars, + ); } else { - (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(); let mut end_idx = start_idx; // Grabs a max-size slice of the scrollback buffer (usually does not totally fit because of newlines) - let result = if start_idx + buffer_width * buffer_height > scrollback_buffer_len { - self.scrollback_buffer.get(start_idx..scrollback_buffer_len-1) + if start_idx + buffer_width * buffer_height > scrollback_buffer_len { + result = self + .scrollback_buffer + .get(start_idx..scrollback_buffer_len - 1); } else { - 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 if let Some(slice) = result { @@ -191,36 +203,36 @@ impl Terminal { if new_line_indices.is_empty() { // 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() { - 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 @@ -232,13 +244,13 @@ impl Terminal { end_idx += last_line_chars; } - if end_idx < self.scrollback_buffer.len() { - Ok(end_idx) + if end_idx <= self.scrollback_buffer.len() - 1 { + return Ok(end_idx); } else { Err(ScrollError::OffEndBound) } } else { - Ok(self.scrollback_buffer.len() - 1) /* WARNING: maybe should return Error? */ + return Ok(self.scrollback_buffer.len() - 1); /* WARNING: maybe should return Error? */ } } @@ -248,7 +260,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; } @@ -256,19 +268,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; @@ -284,16 +299,16 @@ impl Terminal { // Prevents the user from scrolling down if already at the bottom of the page if self.is_scroll_end { return; - } - let prev_start_idx = self.scroll_start_idx; + } + 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; @@ -304,21 +319,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; } @@ -340,14 +361,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 { @@ -358,9 +379,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; @@ -375,13 +396,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()?; @@ -392,10 +413,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) @@ -419,21 +446,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, @@ -443,14 +478,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("Theseus Terminal Emulator\nPress Ctrl+C to quit a task\n".to_string()); 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); } @@ -459,6 +497,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)?; @@ -466,6 +505,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(()) } @@ -484,9 +529,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(()) } @@ -501,13 +549,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 @@ -517,7 +569,7 @@ impl Terminal { self.cursor.disable(); self.display_cursor()?; } - + Ok(()) } @@ -579,57 +631,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. @@ -648,9 +709,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"