Skip to content

Commit

Permalink
Add Take Screenshot as a shortcuts, with default being PrintScreen
Browse files Browse the repository at this point in the history
  • Loading branch information
hansl committed Dec 5, 2023
1 parent 78fc570 commit c8c8c10
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 100 deletions.
101 changes: 101 additions & 0 deletions mister-fpga/src/framebuffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use cyclone_v::memory::{DevMemMemoryMapper, MemoryMapper};
use tracing::info;

pub const FB_PIXEL_COUNT: usize = 1920 * 1080;
pub const FB_SIZE: usize = FB_PIXEL_COUNT * 4 * 3;
pub const FB_BASE_ADDRESS: usize = 0x2000_0000;
pub const BUFFER_SIZE: usize = 2048 * 1024 * 3;

const DE10_PAGE_SIZE: usize = 4096;

pub struct Image {
pub width: u32,
pub height: u32,
pub data: Vec<u8>,
}

#[derive(Debug)]
#[repr(C)]
struct FbHeader {
pub magic: u16,
pub header_len: u16,
pub width: u16,
pub height: u16,
pub line: u16,
pub output_width: u16,
pub output_height: u16,
}

pub struct FpgaFramebuffer<M: MemoryMapper> {
memory: M,
header: FbHeader,
start: *const u8,
}

impl Default for FpgaFramebuffer<DevMemMemoryMapper> {
fn default() -> Self {
let address = FB_BASE_ADDRESS; // - DE10_PAGE_SIZE;
let size = BUFFER_SIZE;
let start = address & !(DE10_PAGE_SIZE - 1);
let offset = start - address;
let mapper =
DevMemMemoryMapper::create(address, size).expect("Could not mmap framebuffer.");

Self::new(mapper, offset).unwrap()
}
}

impl<M: MemoryMapper> FpgaFramebuffer<M> {
fn new(memory: M, offset: usize) -> Result<Self, &'static str> {
let header: *const u8 = memory.as_ptr();

let buffer = unsafe { std::slice::from_raw_parts(header, 16) };

// Bytes are in big endian, but ARM is in little endian.
let header = FbHeader {
magic: (buffer[0] as u16) << 8 | (buffer[1] as u16),
header_len: (buffer[2] as u16) << 8 | (buffer[3] as u16),
width: (buffer[6] as u16) << 8 | (buffer[7] as u16),
height: (buffer[8] as u16) << 8 | (buffer[9] as u16),
line: (buffer[10] as u16) << 8 | (buffer[11] as u16),
output_width: (buffer[12] as u16) << 8 | (buffer[13] as u16),
output_height: (buffer[14] as u16) << 8 | (buffer[15] as u16),
};

if header.magic != 0x0101 {
return Err("Invalid framebuffer header.");
}
info!("Header data: {:?}", header);
let start = unsafe { memory.as_ptr::<u8>().add(offset) };

Ok(Self {
memory,
header,
start,
})
}

pub fn take_screenshot(&mut self) -> Result<Image, String> {
let height = self.header.height as usize;
let width = self.header.width as usize;
let line = self.header.line as usize;
let fb = unsafe {
std::slice::from_raw_parts(
self.start.add(self.header.header_len as usize),
height * width * 3,
)
};

let mut data = vec![0; height * width * 3];
for y in 0..height {
let line = &fb[y * line..y * line + width * 3];
data[y * width * 3..y * width * 3 + width * 3].copy_from_slice(line)
}

return Ok(Image {
width: width as u32,
height: height as u32,
data,
});
}
}
1 change: 1 addition & 0 deletions mister-fpga/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ pub mod config_string;
pub mod core;
pub mod core_info;
pub mod fpga;
pub mod framebuffer;
pub mod osd;
pub mod types;
5 changes: 2 additions & 3 deletions src/application/panels/core_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ fn commands_(app: &mut GoLEmApp, core: &impl Core) -> Vec<(ShortcutCommand, Basi
settings
.inner()
.mappings()
.global_commands()
.chain(settings.inner().mappings().core_commands(core.name()))
.all_commands(core.name())
.map(|(cmd, shortcut)| (cmd, shortcut.clone()))
.collect::<Vec<_>>()
}
Expand Down Expand Up @@ -54,7 +53,7 @@ fn core_loop(app: &mut GoLEmApp, mut core: impl Core) {
}

for ev in state.events() {
debug!(?ev, "Core loop event");
// debug!(?ev, "Core loop event");
match ev {
Event::KeyDown {
scancode: Some(scancode),
Expand Down
78 changes: 27 additions & 51 deletions src/application/panels/core_loop/menu/input_mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,25 @@ pub fn menu(app: &mut GoLEmApp, core: &Option<&mut (impl Core + ?Sized)>) {
let mut state = None;

loop {
let show_menu = app
.settings()
.inner()
.mappings()
.show_menu
.as_ref()
.map(|m| m.to_string());
let reset_core = app
.settings()
.inner()
.mappings()
.reset_core
.as_ref()
.map(|m| m.to_string());
let quit_core = app
.settings()
.inner()
.mappings()
.quit_core
.as_ref()
.map(|m| m.to_string());
let global_shortcuts = ShortcutCommand::globals()
.into_iter()
.filter_map(|command| {
Some((
command.setting_name()?,
app.settings()
.inner()
.mappings()
.for_command(None, command)
.map(|x| x.to_string())
.unwrap_or_default(),
MenuAction::Remap(command),
))
})
.collect::<Vec<_>>();
let global_items = global_shortcuts
.iter()
.map(|(a, b, c)| (*a, b.as_str(), *c))
.collect::<Vec<_>>();

let menu = if let Some(c) = core {
let menu = c.menu_options();
Expand Down Expand Up @@ -85,36 +83,14 @@ pub fn menu(app: &mut GoLEmApp, core: &Option<&mut (impl Core + ?Sized)>) {
app,
"Input Mapping",
items.as_slice(),
TextMenuOptions::default().with_state(state).with_prefix(&[
("Global Shortcuts (all cores)", "", MenuAction::Unselectable),
(
"Show Menu",
if let Some(s) = show_menu.as_ref() {
s.as_str()
} else {
""
},
MenuAction::Remap(ShortcutCommand::ShowCoreMenu),
),
(
"Reset Core",
if let Some(s) = reset_core.as_ref() {
s.as_str()
} else {
""
},
MenuAction::Remap(ShortcutCommand::QuitCore),
),
(
"Quit Core",
if let Some(s) = quit_core.as_ref() {
s.as_str()
} else {
""
},
MenuAction::Remap(ShortcutCommand::QuitCore),
),
]),
TextMenuOptions::default().with_state(state).with_prefix(
[
vec![("Global Shortcuts (all cores)", "", MenuAction::Unselectable)],
global_items,
]
.concat()
.as_slice(),
),
);

state = Some(new_state);
Expand Down
8 changes: 8 additions & 0 deletions src/data/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ pub fn config_root_path() -> PathBuf {
p
}

pub fn screenshots_root() -> PathBuf {
let p = config_root_path().join("screenshots");
if !p.exists() {
std::fs::create_dir_all(&p).unwrap();
}
p
}

pub fn core_root_path() -> PathBuf {
let p = config_root_path().join("cores");
if !p.exists() {
Expand Down
80 changes: 37 additions & 43 deletions src/data/settings/mappings.rs
Original file line number Diff line number Diff line change
@@ -1,61 +1,57 @@
use crate::input::commands::ShortcutCommand;
use crate::input::BasicInputShortcut;
use mister_fpga::config_string::ConfigMenu;
use sdl3::keyboard::Scancode;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::str::FromStr;
use tracing::info;

#[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq)]
pub struct MappingSettings {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub show_menu: Option<BasicInputShortcut>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reset_core: Option<BasicInputShortcut>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub quit_core: Option<BasicInputShortcut>,
// #[serde(default)]
// pub save_state: Option<()>,
// #[serde(default)]
// pub load_save_state: Option<()>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
cores: BTreeMap<String, BTreeMap<String, BasicInputShortcut>>,

#[serde(flatten)]
shortcuts: BTreeMap<String, BasicInputShortcut>,
}

impl Default for MappingSettings {
fn default() -> Self {
let global_shortcuts = ShortcutCommand::globals();

Self {
show_menu: Some(BasicInputShortcut::default().with_key(Scancode::F12)),
reset_core: None,
quit_core: None,
cores: BTreeMap::new(),
shortcuts: global_shortcuts
.into_iter()
.filter_map(|k| {
Some((k.setting_name().unwrap().to_string(), k.default_shortcut()?))
})
.collect(),
}
}
}

impl MappingSettings {
pub fn core_commands(
pub fn all_commands(
&self,
core_name: &str,
) -> impl Iterator<Item = (ShortcutCommand, &BasicInputShortcut)> {
self.cores.get(core_name).into_iter().flat_map(|core| {
core.iter().map(|(cmd, shortcut)| {
(
ShortcutCommand::CoreSpecificCommand(ConfigMenu::id_from_str(cmd)),
shortcut,
)
self.cores
.get(core_name)
.into_iter()
.flat_map(|core| {
core.iter().map(|(cmd, shortcut)| {
(
ShortcutCommand::CoreSpecificCommand(ConfigMenu::id_from_str(cmd)),
shortcut,
)
})
})
})
}

pub fn global_commands(&self) -> impl Iterator<Item = (ShortcutCommand, &BasicInputShortcut)> {
vec![
(ShortcutCommand::ShowCoreMenu, self.show_menu.as_ref()),
(ShortcutCommand::ResetCore, self.reset_core.as_ref()),
(ShortcutCommand::QuitCore, self.quit_core.as_ref()),
]
.into_iter()
.filter_map(|(cmd, shortcut)| shortcut.map(|s| (cmd, s)))
.chain(self.shortcuts.iter().filter_map(|(cmd, shortcut)| {
ShortcutCommand::from_str(cmd)
.ok()
.map(|cmd| (cmd, shortcut))
}))
}

fn find_core_command_for_id(
Expand All @@ -73,9 +69,6 @@ impl MappingSettings {
command: ShortcutCommand,
) -> Option<&BasicInputShortcut> {
match command {
ShortcutCommand::ShowCoreMenu => self.show_menu.as_ref(),
ShortcutCommand::ResetCore => self.reset_core.as_ref(),
ShortcutCommand::QuitCore => self.quit_core.as_ref(),
ShortcutCommand::CoreSpecificCommand(id) => {
if let Some(core) = core {
self.cores
Expand All @@ -85,14 +78,12 @@ impl MappingSettings {
None
}
}
_ => self.shortcuts.get(command.setting_name()?),
}
}

pub fn delete(&mut self, core: Option<&str>, command: ShortcutCommand) {
match command {
ShortcutCommand::ShowCoreMenu => self.show_menu = None,
ShortcutCommand::ResetCore => self.reset_core = None,
ShortcutCommand::QuitCore => self.quit_core = None,
ShortcutCommand::CoreSpecificCommand(id) => {
if let Some(core) = core {
if let Some(core) = self.cores.get_mut(core) {
Expand All @@ -103,6 +94,11 @@ impl MappingSettings {
}
}
}
other => {
if let Some(x) = other.setting_name() {
self.shortcuts.remove(x);
}
}
}
}

Expand All @@ -118,11 +114,9 @@ impl MappingSettings {
}

pub fn set(&mut self, command: ShortcutCommand, shortcut: BasicInputShortcut) {
match command {
ShortcutCommand::ShowCoreMenu => self.show_menu = Some(shortcut),
ShortcutCommand::ResetCore => self.reset_core = Some(shortcut),
ShortcutCommand::QuitCore => self.quit_core = Some(shortcut),
_ => {}
info!("Setting global command {} to {:?}", command, shortcut);
if let Some(name) = command.setting_name() {
self.shortcuts.insert(name.to_string(), shortcut);
}
}
}
Expand Down
Loading

0 comments on commit c8c8c10

Please sign in to comment.