too funny not to preserve
This commit is contained in:
@@ -12,6 +12,7 @@ fn main() {
|
|||||||
height: fb.var_screen_info.yres_virtual as usize,
|
height: fb.var_screen_info.yres_virtual as usize,
|
||||||
bytes_per_pixel,
|
bytes_per_pixel,
|
||||||
stride: fb.fix_screen_info.line_length as usize / bytes_per_pixel,
|
stride: fb.fix_screen_info.line_length as usize / bytes_per_pixel,
|
||||||
|
old_stride: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
init(fb, fb_info);
|
init(fb, fb_info);
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ use crate::window_manager::{ DrawInstructions };
|
|||||||
const MONO_WIDTH: u8 = 10;
|
const MONO_WIDTH: u8 = 10;
|
||||||
|
|
||||||
pub struct PressButton<T> {
|
pub struct PressButton<T> {
|
||||||
top_left: Point,
|
pub top_left: Point,
|
||||||
size: Dimensions,
|
pub size: Dimensions,
|
||||||
text: String,
|
text: String,
|
||||||
press_return: T,
|
press_return: T,
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,9 @@ impl<T: Clone> Component<T> for PressButton<T> {
|
|||||||
//
|
//
|
||||||
fn handle_message(&mut self, message: WindowMessage) -> Option<T> {
|
fn handle_message(&mut self, message: WindowMessage) -> Option<T> {
|
||||||
match message {
|
match message {
|
||||||
WindowMessage::Touch(x, y) => {
|
WindowMessage::Touch(_, _) => {
|
||||||
|
//assume that the parent window-like passed it to us intentionally and checked already
|
||||||
|
//we can check again, but why?
|
||||||
Some(self.press_return.clone())
|
Some(self.press_return.clone())
|
||||||
},
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|||||||
@@ -3,19 +3,20 @@ use std::vec::Vec;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, KeyChar };
|
use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, KeyChar };
|
||||||
use crate::messages::{ WindowMessage, WindowMessageResponse };
|
use crate::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest };
|
||||||
use crate::framebuffer::Dimensions;
|
use crate::framebuffer::Dimensions;
|
||||||
use crate::themes::ThemeInfo;
|
use crate::themes::ThemeInfo;
|
||||||
use crate::components::Component;
|
use crate::components::Component;
|
||||||
use crate::components::press_button::PressButton;
|
use crate::components::press_button::PressButton;
|
||||||
|
use crate::utils::point_inside;
|
||||||
|
|
||||||
const padding_y: usize = 15;
|
const PADDING_Y: usize = 15;
|
||||||
const padding_x: usize = 15;
|
const PADDING_X: usize = 15;
|
||||||
//padding in between keys in the x direction
|
//padding in between keys in the x direction
|
||||||
const key_padding_x: usize = 5;
|
const KEY_PADDING_X: usize = 5;
|
||||||
const key_padding_y: usize = 5;
|
const KEY_PADDING_Y: usize = 5;
|
||||||
|
|
||||||
#[derive(Default, Eq, PartialEq, Hash)]
|
#[derive(Clone, Default, Eq, PartialEq, Hash)]
|
||||||
enum Board {
|
enum Board {
|
||||||
#[default]
|
#[default]
|
||||||
Regular,
|
Regular,
|
||||||
@@ -24,6 +25,17 @@ enum Board {
|
|||||||
SymbolsShift,
|
SymbolsShift,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Board {
|
||||||
|
fn inc(&mut self) -> Self {
|
||||||
|
match self {
|
||||||
|
Board::Regular => Board::Shift,
|
||||||
|
Board::Shift => Board::Symbols,
|
||||||
|
Board::Symbols => Board::SymbolsShift,
|
||||||
|
Board::SymbolsShift => Board::Regular,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum KeyResponse {
|
enum KeyResponse {
|
||||||
Key(char),
|
Key(char),
|
||||||
@@ -32,11 +44,15 @@ enum KeyResponse {
|
|||||||
SwitchBoard,
|
SwitchBoard,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//if alt is true and ctrl is true, only alt will be sent.
|
||||||
|
//because I don't care about ctrl+alt stuff, and won't use it.
|
||||||
|
//(and probably not supported by this with a real keyboard anyways)
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct OnscreenKeyboard {
|
pub struct OnscreenKeyboard {
|
||||||
dimensions: Dimensions,
|
dimensions: Dimensions,
|
||||||
components: Vec<Box<PressButton<KeyResponse>>>,
|
components: Vec<Box<PressButton<KeyResponse>>>,
|
||||||
alt: bool,
|
alt: bool,
|
||||||
|
ctrl: bool,
|
||||||
board: Board,
|
board: Board,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +64,39 @@ impl WindowLike for OnscreenKeyboard {
|
|||||||
self.set_key_components();
|
self.set_key_components();
|
||||||
WindowMessageResponse::JustRedraw
|
WindowMessageResponse::JustRedraw
|
||||||
},
|
},
|
||||||
//
|
WindowMessage::Touch(x, y) => {
|
||||||
|
for c in &mut self.components {
|
||||||
|
if point_inside([x, y], c.top_left, c.size) {
|
||||||
|
let returned = c.handle_message(WindowMessage::Touch(x, y));
|
||||||
|
if let Some(returned) = returned {
|
||||||
|
return match returned {
|
||||||
|
KeyResponse::Key(ch) => {
|
||||||
|
WindowMessageResponse::Request(WindowManagerRequest::DoKeyChar(if self.alt {
|
||||||
|
KeyChar::Alt(ch)
|
||||||
|
} else if self.ctrl {
|
||||||
|
KeyChar::Ctrl(ch)
|
||||||
|
} else {
|
||||||
|
KeyChar::Press(ch)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
KeyResponse::Alt => {
|
||||||
|
self.alt = !self.alt;
|
||||||
|
WindowMessageResponse::DoNothing
|
||||||
|
},
|
||||||
|
KeyResponse::Ctrl => {
|
||||||
|
self.ctrl = !self.ctrl;
|
||||||
|
WindowMessageResponse::DoNothing
|
||||||
|
},
|
||||||
|
KeyResponse::SwitchBoard => {
|
||||||
|
self.board = self.board.inc();
|
||||||
|
WindowMessageResponse::DoNothing
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowMessageResponse::DoNothing
|
||||||
|
},
|
||||||
_ => WindowMessageResponse::DoNothing,
|
_ => WindowMessageResponse::DoNothing,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +108,7 @@ impl WindowLike for OnscreenKeyboard {
|
|||||||
}
|
}
|
||||||
instructions
|
instructions
|
||||||
}
|
}
|
||||||
//
|
|
||||||
fn subtype(&self) -> WindowLikeType {
|
fn subtype(&self) -> WindowLikeType {
|
||||||
WindowLikeType::OnscreenKeyboard
|
WindowLikeType::OnscreenKeyboard
|
||||||
}
|
}
|
||||||
@@ -93,7 +141,7 @@ impl OnscreenKeyboard {
|
|||||||
HashMap::from([
|
HashMap::from([
|
||||||
(Board::Regular, vec!['𐘃', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '𐘁']), //escape and backspace
|
(Board::Regular, vec!['𐘃', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '𐘁']), //escape and backspace
|
||||||
(Board::Shift, vec!['𐘃', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '𐘁']), //escape and backspace
|
(Board::Shift, vec!['𐘃', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '𐘁']), //escape and backspace
|
||||||
(Board::Symbols, vec!['~', '*', '"', '\'', ':', ';', '!', '?', '𐘁']), //basckspace
|
(Board::Symbols, vec!['~', '*', '"', '\'', ':', ';', '!', '?', '𐘁']), //backspace
|
||||||
(Board::SymbolsShift, vec!['[', ']', '{', '}', '𐘁']), //backspace
|
(Board::SymbolsShift, vec!['[', ']', '{', '}', '𐘁']), //backspace
|
||||||
]),
|
]),
|
||||||
HashMap::from([
|
HashMap::from([
|
||||||
@@ -101,16 +149,17 @@ impl OnscreenKeyboard {
|
|||||||
(Board::Shift, vec!['𐘧', '𐘎', ' ', '𐘂']), //switch board (special case, not a real key), alt (special case, not a real key), enter
|
(Board::Shift, vec!['𐘧', '𐘎', ' ', '𐘂']), //switch board (special case, not a real key), alt (special case, not a real key), enter
|
||||||
(Board::Symbols, vec!['𐘧', '𐘎', ',', '_', ' ', '/', '.', '𐘂']), //switch board (special case, not a real key), alt (special case, not a real key), enter
|
(Board::Symbols, vec!['𐘧', '𐘎', ',', '_', ' ', '/', '.', '𐘂']), //switch board (special case, not a real key), alt (special case, not a real key), enter
|
||||||
(Board::SymbolsShift, vec!['𐘧', '𐘎', '\\', '<', ' ', '>', '𐘾', '𐘂']), //switch board (special case, not a real key), alt (special case, not a real key), ctrl (special case, not a real key), enter
|
(Board::SymbolsShift, vec!['𐘧', '𐘎', '\\', '<', ' ', '>', '𐘾', '𐘂']), //switch board (special case, not a real key), alt (special case, not a real key), ctrl (special case, not a real key), enter
|
||||||
|
//ctrl = shimazu
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
//hardcoded for now
|
//hardcoded for now
|
||||||
let mut y = padding_y;
|
let mut y = PADDING_Y;
|
||||||
let key_height = (self.dimensions[1] - padding_y * 2 - key_padding_y * (rows.len() - 1)) / rows.len();
|
let key_height = (self.dimensions[1] - PADDING_Y * 2 - KEY_PADDING_Y * (rows.len() - 1)) / rows.len();
|
||||||
let reg_key_width = (self.dimensions[0] - padding_x * 2 - key_padding_x * (10 - 1)) / 10;
|
let reg_key_width = (self.dimensions[0] - PADDING_X * 2 - KEY_PADDING_X * (10 - 1)) / 10;
|
||||||
for row in rows {
|
for row in rows {
|
||||||
let row_keys = &row[&self.board];
|
let row_keys = &row[&self.board];
|
||||||
//centre
|
//centre
|
||||||
let mut x = padding_x + (10 - row_keys.len()) * (reg_key_width + key_padding_x) / 2;
|
let mut x = PADDING_X + (10 - row_keys.len()) * (reg_key_width + KEY_PADDING_X) / 2;
|
||||||
for key in row_keys {
|
for key in row_keys {
|
||||||
let press_return = if key == &'𐘧' {
|
let press_return = if key == &'𐘧' {
|
||||||
KeyResponse::SwitchBoard
|
KeyResponse::SwitchBoard
|
||||||
@@ -121,10 +170,24 @@ impl OnscreenKeyboard {
|
|||||||
} else {
|
} else {
|
||||||
KeyResponse::Key(*key)
|
KeyResponse::Key(*key)
|
||||||
};
|
};
|
||||||
self.components.push(Box::new(PressButton::new([x, y], [reg_key_width, key_height], key.to_string(), press_return)));
|
let mut text = key.to_string();
|
||||||
x += reg_key_width + key_padding_x;
|
if text == "𐘧" {
|
||||||
|
text = "Switch".to_string();
|
||||||
|
} else if text == "𐘎" {
|
||||||
|
text = "Alt".to_string();
|
||||||
|
} else if text == "𐘂" {
|
||||||
|
text = "Enter".to_string();
|
||||||
|
} else if text == "𐘁" {
|
||||||
|
text = "Back".to_string();
|
||||||
|
} else if text == "𐘃" {
|
||||||
|
text = "Esc".to_string();
|
||||||
|
} else if text == "𐘾" {
|
||||||
|
text = "Ctrl".to_string();
|
||||||
}
|
}
|
||||||
y += key_height + key_padding_y;
|
self.components.push(Box::new(PressButton::new([x, y], [reg_key_width, key_height], text, press_return)));
|
||||||
|
x += reg_key_width + KEY_PADDING_X;
|
||||||
|
}
|
||||||
|
y += key_height + KEY_PADDING_Y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ pub struct FramebufferInfo {
|
|||||||
pub height: usize,
|
pub height: usize,
|
||||||
pub bytes_per_pixel: usize,
|
pub bytes_per_pixel: usize,
|
||||||
pub stride: usize,
|
pub stride: usize,
|
||||||
|
pub old_stride: Option<usize>, //used/set only when rotate is true
|
||||||
}
|
}
|
||||||
|
|
||||||
//currently doesn't check if writing onto next line accidentally
|
//currently doesn't check if writing onto next line accidentally
|
||||||
@@ -45,6 +46,7 @@ pub struct FramebufferWriter {
|
|||||||
info: FramebufferInfo,
|
info: FramebufferInfo,
|
||||||
buffer: Vec<u8>,
|
buffer: Vec<u8>,
|
||||||
saved_buffer: Option<Vec<u8>>,
|
saved_buffer: Option<Vec<u8>>,
|
||||||
|
rotate_buffer: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FramebufferWriter {
|
impl FramebufferWriter {
|
||||||
@@ -57,10 +59,25 @@ impl FramebufferWriter {
|
|||||||
self.info.clone()
|
self.info.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_buffer(&self) -> &[u8] {
|
pub fn get_buffer(&mut self) -> &[u8] {
|
||||||
&self.buffer
|
&self.buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_transposed_buffer(&mut self) -> &[u8] {
|
||||||
|
let mut output_array = vec![255; self.info.byte_len];
|
||||||
|
let row_bytes_len = self.info.stride * self.info.bytes_per_pixel;
|
||||||
|
let row_bytes_len_transposed = self.info.old_stride.unwrap_or(self.info.height) * self.info.bytes_per_pixel;
|
||||||
|
for y in 0..self.info.height {
|
||||||
|
for x in 0..self.info.width {
|
||||||
|
for i in 0..self.info.bytes_per_pixel {
|
||||||
|
output_array[x * row_bytes_len_transposed + y * self.info.bytes_per_pixel + i] = self.buffer[y * row_bytes_len + x * self.info.bytes_per_pixel + i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.rotate_buffer = Some(output_array);
|
||||||
|
&self.rotate_buffer.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save_buffer(&mut self) {
|
pub fn save_buffer(&mut self) {
|
||||||
self.saved_buffer = Some(self.buffer.clone());
|
self.saved_buffer = Some(self.buffer.clone());
|
||||||
}
|
}
|
||||||
@@ -237,6 +254,7 @@ impl Default for FramebufferWriter {
|
|||||||
info: Default::default(),
|
info: Default::default(),
|
||||||
buffer: Vec::new(),
|
buffer: Vec::new(),
|
||||||
saved_buffer: None,
|
saved_buffer: None,
|
||||||
|
rotate_buffer: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ pub enum WindowManagerRequest {
|
|||||||
CloseStartMenu,
|
CloseStartMenu,
|
||||||
Unlock,
|
Unlock,
|
||||||
Lock,
|
Lock,
|
||||||
|
DoKeyChar(KeyChar),
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pub const TASKBAR_HEIGHT: usize = 38;
|
|||||||
pub const INDICATOR_HEIGHT: usize = 20;
|
pub const INDICATOR_HEIGHT: usize = 20;
|
||||||
const WINDOW_TOP_HEIGHT: usize = 26;
|
const WINDOW_TOP_HEIGHT: usize = 26;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
pub enum KeyChar {
|
pub enum KeyChar {
|
||||||
Press(char),
|
Press(char),
|
||||||
Alt(char),
|
Alt(char),
|
||||||
@@ -53,6 +53,23 @@ enum ThreadMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
||||||
|
let args: Vec<_> = env::args().collect();
|
||||||
|
|
||||||
|
let rotate = args.contains(&"rotate".to_string());
|
||||||
|
|
||||||
|
let framebuffer_info = if rotate {
|
||||||
|
FramebufferInfo {
|
||||||
|
byte_len: framebuffer_info.byte_len,
|
||||||
|
width: framebuffer_info.height,
|
||||||
|
height: framebuffer_info.width,
|
||||||
|
bytes_per_pixel: framebuffer_info.bytes_per_pixel,
|
||||||
|
stride: framebuffer_info.height,
|
||||||
|
old_stride: Some(framebuffer_info.stride),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
framebuffer_info
|
||||||
|
};
|
||||||
|
|
||||||
let dimensions = [framebuffer_info.width, framebuffer_info.height];
|
let dimensions = [framebuffer_info.width, framebuffer_info.height];
|
||||||
|
|
||||||
println!("bg: {}x{}", dimensions[0], dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT);
|
println!("bg: {}x{}", dimensions[0], dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT);
|
||||||
@@ -61,7 +78,7 @@ pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
|||||||
|
|
||||||
writer.init(framebuffer_info.clone());
|
writer.init(framebuffer_info.clone());
|
||||||
|
|
||||||
let mut wm: WindowManager = WindowManager::new(writer, framebuffer, dimensions);
|
let mut wm: WindowManager = WindowManager::new(writer, framebuffer, dimensions, rotate);
|
||||||
|
|
||||||
wm.draw(None, false);
|
wm.draw(None, false);
|
||||||
|
|
||||||
@@ -91,7 +108,6 @@ pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let args: Vec<_> = env::args().collect();
|
|
||||||
let touch = args.contains(&"touch".to_string());
|
let touch = args.contains(&"touch".to_string());
|
||||||
|
|
||||||
//read touchscreen presses (hopefully)
|
//read touchscreen presses (hopefully)
|
||||||
@@ -204,6 +220,7 @@ impl fmt::Debug for WindowLikeInfo {
|
|||||||
|
|
||||||
pub struct WindowManager {
|
pub struct WindowManager {
|
||||||
writer: RefCell<FramebufferWriter>,
|
writer: RefCell<FramebufferWriter>,
|
||||||
|
rotate: bool,
|
||||||
id_count: usize,
|
id_count: usize,
|
||||||
window_infos: Vec<WindowLikeInfo>,
|
window_infos: Vec<WindowLikeInfo>,
|
||||||
osk: Option<WindowLikeInfo>,
|
osk: Option<WindowLikeInfo>,
|
||||||
@@ -219,9 +236,10 @@ pub struct WindowManager {
|
|||||||
//1 is up, 2 is down
|
//1 is up, 2 is down
|
||||||
|
|
||||||
impl WindowManager {
|
impl WindowManager {
|
||||||
pub fn new(writer: FramebufferWriter, framebuffer: Framebuffer, dimensions: Dimensions) -> Self {
|
pub fn new(writer: FramebufferWriter, framebuffer: Framebuffer, dimensions: Dimensions, rotate: bool) -> Self {
|
||||||
let mut wm = WindowManager {
|
let mut wm = WindowManager {
|
||||||
writer: RefCell::new(writer),
|
writer: RefCell::new(writer),
|
||||||
|
rotate,
|
||||||
id_count: 0,
|
id_count: 0,
|
||||||
window_infos: Vec::new(),
|
window_infos: Vec::new(),
|
||||||
osk: None,
|
osk: None,
|
||||||
@@ -612,7 +630,7 @@ impl WindowManager {
|
|||||||
} else {
|
} else {
|
||||||
//see if in onscreen keyboard, if so send to it after offsetting coords
|
//see if in onscreen keyboard, if so send to it after offsetting coords
|
||||||
if self.osk.is_some() {
|
if self.osk.is_some() {
|
||||||
let mut osk = self.osk.as_mut().unwrap();
|
let osk = self.osk.as_mut().unwrap();
|
||||||
if point_inside([x, y], osk.top_left, osk.dimensions) {
|
if point_inside([x, y], osk.top_left, osk.dimensions) {
|
||||||
osk.window_like.handle_message(WindowMessage::Touch(x - osk.top_left[0], y - osk.top_left[1]))
|
osk.window_like.handle_message(WindowMessage::Touch(x - osk.top_left[0], y - osk.top_left[1]))
|
||||||
} else {
|
} else {
|
||||||
@@ -692,6 +710,9 @@ impl WindowManager {
|
|||||||
WindowManagerRequest::ClipboardCopy(content) => {
|
WindowManagerRequest::ClipboardCopy(content) => {
|
||||||
self.clipboard = Some(content);
|
self.clipboard = Some(content);
|
||||||
},
|
},
|
||||||
|
WindowManagerRequest::DoKeyChar(kc) => {
|
||||||
|
self.handle_message(WindowManagerMessage::KeyChar(kc));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -804,6 +825,9 @@ impl WindowManager {
|
|||||||
self.writer.borrow_mut().draw_buffer(window_info.top_left, window_dimensions[1], window_dimensions[0] * bytes_per_pixel, &window_writer.get_buffer());
|
self.writer.borrow_mut().draw_buffer(window_info.top_left, window_dimensions[1], window_dimensions[0] * bytes_per_pixel, &window_writer.get_buffer());
|
||||||
w_index += 1;
|
w_index += 1;
|
||||||
}
|
}
|
||||||
self.framebuffer.write_frame(self.writer.borrow().get_buffer());
|
//could probably figure out a way to do borrow() when self.rotate is false but does it matter?
|
||||||
|
let mut writer_borrow = self.writer.borrow_mut();
|
||||||
|
let frame = if self.rotate { writer_borrow.get_transposed_buffer() } else { writer_borrow.get_buffer() };
|
||||||
|
self.framebuffer.write_frame(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user