12 Commits

Author SHA1 Message Date
stjet
10daa9982b v1.2.1: text measuring support
several malvim features added, font data caching, taskbar title overflow fix, new background
2025-09-06 06:23:57 +00:00
stjet
08c2358bdc v1.2
random lockscreen message, remove rand dep for audio player, add version to about window, add o/O to malvim, add circles to draw, bug fixes, minor byte savings for font .alpha format
2025-08-18 17:27:16 +00:00
stjet
2c4455f623 lines, barebones drawing window
addition of lines means ipc slightly changed, though can be ignored. also, minor malvim fix
2025-08-12 06:54:21 +00:00
stjet
ec5cba13c8 fix for touchscreen fb redraw 2025-05-04 19:11:29 +00:00
Jon Dough
4c4c9a1b35 v1.1: Merge pull request #2 from stjet/v1.1 2025-05-04 17:10:46 +00:00
Jon Dough
6cc8e06e89 Read /dev/input for touchscreen (#3) 2025-05-04 03:00:34 +00:00
stjet
dfe065d26a fix un-fullscreening windows changing the coords, fix touchscreen not redrawing framebuffer 2025-05-02 01:07:15 +00:00
stjet
c1afd3f33e inhouse pty 2025-04-30 04:50:11 +00:00
stjet
724ffbd494 multi-line copy/paste, more copy/paste
fix C and D in nimbus romono
2025-04-26 05:17:06 +00:00
stjet
7c6a7d6b6d remove termion: inhouse key parsing
replace blake2 with sha512, fix C and D in romono, minor terminal and audio player fixes
2025-04-25 15:01:20 +00:00
stjet
667b4cd2d9 change project structure to make more sense
(move wm only stuff to wm dir)
2025-04-21 06:57:42 +00:00
stjet
c5a41244b4 handle tty raw mode ourselves 2025-04-21 05:51:07 +00:00
62 changed files with 1287 additions and 334 deletions

View File

@@ -1,6 +1,6 @@
[package]
name = "ming-wm"
version = "1.0.3"
version = "1.2.1"
repository = "https://github.com/stjet/ming-wm"
license = "GPL-3.0-or-later"
edition = "2021"
@@ -9,38 +9,33 @@ default-run = "ming"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
members = ["linux"]
members = [ "wm", "linux" ]
[build-dependencies]
bmp-rust = "0.5.0"
blake2 = { version = "0.10.6", default-features = false }
bitcoin_hashes = { version = "0.16.0", default-features = false }
[dependencies]
ming-wm-lib = { path = "ming-wm-lib" }
blake2 = { version = "0.10.6", default-features = false }
linux = { path = "linux" }
termion = { version = "4.0.3", optional = true }
wm = { path = "wm", optional = true }
linux = { path = "linux", optional = true }
rodio = { version = "0.19.0", default-features = false, features = [ "flac", "mp3", "symphonia-vorbis", "wav" ], optional = true }
rand = { version = "0.9.0", default-features = false, features = [ "small_rng" ], optional = true }
id3 = { version = "1.10.0", optional = true }
mp4ameta = { version = "0.11.0", optional = true }
metaflac = { version = "0.2.5", optional = true }
bmp-rust = "0.5.0"
pty-process = { version = "0.5.1", optional = true }
[features]
default = [ "main", "terminal" ]
main = [ "termion" ]
terminal = [ "pty-process" ]
audio_player = [ "id3", "mp4ameta", "metaflac", "rand", "rodio" ]
default = [ "wm", "terminal" ]
terminal = [ "linux" ]
audio_player = [ "id3", "mp4ameta", "metaflac", "rodio" ]
[profile.release]
lto = true
[[bin]]
name = "ming"
path = "src/bin/main.rs"
required-features = [ "main" ]
path = "src/bin/wm.rs"
required-features = [ "wm" ]
[[bin]]
name = "mingFiles_Audio_Player"
@@ -67,3 +62,7 @@ path = "src/bin/malvim.rs"
[[bin]]
name = "mingGames_Reversi"
path = "src/bin/reversi.rs"
[[bin]]
name = "mingUtils_Draw"
path = "src/bin/draw.rs"

View File

@@ -1,4 +1,4 @@
Ming-wm is a keyboard-based, retro-themed window manager for Linux. It is neither for Wayland or the X Window System - it writes directly to the framebuffer. Inspirations include i3, Haiku, SerenityOS, and Windows98, and it is a conceptual successor to the previous [mingde](https://github.com/stjet/mingde) and [ming-os](https://github.com/stjet/ming-os).
Ming-wm is a keyboard-based, retro-themed window manager for Linux. It is neither for Wayland or the X Window System - it writes directly to the framebuffer. Inspirations include i3, Haiku, SerenityOS, and Windows98, and it is a conceptual successor to my previous projects [ming-de](https://github.com/stjet/mingde) and [ming-os](https://github.com/stjet/ming-os).
![example 1](/docs/images/ws1.png)
![example 2](/docs/images/ws3.png)
@@ -49,9 +49,9 @@ Usage for most of the included windows and window-likes are included in `docs/wi
## Running on Mobile Linux
Running with an onscreen keyboard. The framebuffer may not be redrawn to the screen without a (real) key press. The volume down button seems to work. If someone knows why this is the case, and/or how to fix this, please let me know.
More or the less the same, but includes with an onscreen keyboard for touchscreens.
`evtest` needs to be installed. Currently, the input device is assumed to be at `/dev/first-touchscreen`.
Currently, the touchscreen input device is assumed to be at `/dev/input/by-path/first-touchscreen`, but this is easily editable (see `src/bin/wm.rs`). For touchscreen support, the user running `ming` needs to have read permissions for that `/dev/input/` file.
```
ming touch
@@ -73,12 +73,18 @@ See [/docs/philosophy.md](/docs/philosophy.md) for some hopefully interesting ra
Windows (may be called apps in other window managers) can be developed in any language, though it is easiest to do so in Rust because the `ming-wm-lib` crate can be used.
See [koxinga](https://github.com/stjet/koxinga) or `src/bin` for examples. The `docs` directory includes a [brief introduction to writing windows](docs/system/writing_windows.md), and (incomplete) documentation on the workings of ming-wm.
The `docs` directory includes a [brief introduction to writing windows](docs/system/writing_windows.md), and (incomplete) documentation on the workings of ming-wm.
See [koxinga](https://github.com/stjet/koxinga) or `src/bin` for examples.
A (very poorly written, and WIP) window is being written in Lisp Scheme: [ming-flashcards](https://github.com/stjet/ming-flashcards).
## Security
Make sure the permissions of `password.env` are so other users cannot read or write to it. If there is no plan to recompile, just delete it.
Understand the implications of adding the user to the `video` group. And if the permissions of a `/dev/input/` file was changed for touchscreen support, understand those implications too.
Obviously, don't run the executable with `sudo` or `doas`, or as the root user!
## License

BIN
bmps/arhants1440x842.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 MiB

View File

Before

Width:  |  Height:  |  Size: 70 B

After

Width:  |  Height:  |  Size: 70 B

View File

Before

Width:  |  Height:  |  Size: 250 B

After

Width:  |  Height:  |  Size: 250 B

View File

Before

Width:  |  Height:  |  Size: 250 B

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 582 B

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 630 B

After

Width:  |  Height:  |  Size: 534 B

View File

Before

Width:  |  Height:  |  Size: 378 B

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

View File

@@ -4,7 +4,7 @@ use std::env;
use std::path::Path;
use std::process::Command;
use blake2::{ Blake2b512, Digest };
use bitcoin_hashes::Sha512;
use bmp_rust::bmp::BMP;
fn font_chars_to_alphas(dir: &str) {
@@ -35,7 +35,7 @@ fn font_chars_to_alphas(dir: &str) {
ch.push(row);
}
let ch: Vec<String> = ch.into_iter().map(|row| {
row.join(",")
row.join(",").replace(",,,,", ":").replace(",,,", ";").replace(",,", ".")
}).collect();
let chars: Vec<char> = file_name[0].chars().collect();
File::create(dir.to_string() + "/" + &chars[0].to_string() + ".alpha").unwrap().write_all(
@@ -47,12 +47,11 @@ fn font_chars_to_alphas(dir: &str) {
fn main() {
//hash + "salt" password and add to build
let password = read_to_string("password.env").unwrap_or("password".to_string()).replace("\n", "") + "salt?sorrycryptographers";
let mut hasher = Blake2b512::new();
hasher.update(password.as_bytes());
let password = read_to_string("password.env").unwrap_or("password".to_string()).replace("\n", "") + "salt?sorrycryptographers!1!";
let hash = Sha512::hash(password.as_bytes()).to_byte_array();
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("password.rs");
write(&dest_path, format!("pub const PASSWORD_HASH: [u8; 64] = {:?};", hasher.finalize())).unwrap();
write(&dest_path, format!("pub const PASSWORD_HASH: [u8; 64] = {:?};", hash)).unwrap();
//process bmps
for entry in read_dir("./bmps").unwrap() {
let path = entry.unwrap().path();
@@ -62,7 +61,9 @@ fn main() {
}
//copy bmp folders to target
let profile = env::var_os("PROFILE").unwrap().to_string_lossy().to_string();
Command::new("rm").arg("-rf").arg(format!("./target/{}/ming_bmps", profile)).output().unwrap(); //delete at target first so cp works
Command::new("cp").arg("-r").arg("./bmps").arg(format!("./target/{}/ming_bmps", profile)).output().unwrap();
//also copy the docs folder
Command::new("rm").arg("-rf").arg(format!("./target/{}/ming_docs", profile)).output().unwrap();
Command::new("cp").arg("-r").arg("./docs").arg(format!("./target/{}/ming_docs", profile)).output().unwrap();
}

View File

@@ -69,5 +69,10 @@ More often than not, not relying on dependencies removes unnecessary bloat and c
Expect to see more dependencies in Cargo.toml eliminated soon.
PS:
1. `rodio` is unlikely to ever be eliminated (simply because audio is *complex*), and it's optional (if the audio player is not wanted)
1. `rodio` is unlikely to ever be eliminated (simply because audio is *complex*), and it's optional (if the audio player is not wanted). The dependency which provides hashing will also not be removed as I am not a cryptographer and not (quite) stupid enough to pretend being one
2. `bmp-rust` is written by me and so isn't technically an external dependency
> ### Update
> Ignoring the audio player dependencies and `bmp-rust`, the only dependencies not written by me are now `libc` for libc Rust bindings and `bitcoin-hashes` for SHA-512 password hashing (yes, I know SHA-512 is not the greatest password hash function, but it is lightweight, relatively).
>
> Prior dependencies like `termion`, `linux_framebuffer`, `pty-process`, `evtest` have been removed and replaced by a me-written version in the `linux` crate. As of v1.1, the dependency removing goal has been achieved. Huzzah!

View File

@@ -0,0 +1,3 @@
## Launch on Login
To launch on user login, simply add `ming` as the last line of `~/.profile`. This should work for most shells.

View File

@@ -15,6 +15,8 @@ Since the windows and the window manager are separate binaries, they need some w
The serialization format is in `ming-wm-lib/src/serialize.rs`. Make sure any newlines (`\n`) in strings are removed before/after serializations. When doing IPC, the window manager assumes the response to a query is one line, so if a newline is present, it will fail to parse the response.
> In the case of `WindowMessage::Request(WindowManagerRequest::ClipboardCopy(<copy_string>))`, windows should convert any `\n` into `𐘂` when copying to clipboard and vice versa when pasting, in order to allow for multi-line clipboard contents.
## Hello, World!
A minimal example using `ming-wm-lib`.

View File

@@ -14,6 +14,8 @@ Type to write commands, backspace to delete last character, and enter to run com
Tab completion is supported for the `<dir>` and `<dir / playlist file>` arguments.
The copy shortcut will copy the currently playing song's file name, if there is a currently playing song.
## Playlists
Example playlist file:

15
docs/window-likes/draw.md Normal file
View File

@@ -0,0 +1,15 @@
Very basic drawing board.
## Moving
Arrow keys or hjkl. `i` to enter input mode. Use the enter key to finish a line or rectangle. Escape to cancel line or rectangle in progress.
## Input Commands
- `line/l`: Start line with current point as start. Move and hit enter to determine line endpoint
- `rect/r`: Start rect with current point as a corner. Move and hit enter to determine the opposite corner
- `circle/c`: Start circle with current point as centre. Move and hit enter to determine radius
- `color/co/colour [lowercase 6 char hex string]`: Set current colour
- `linewidth/lw [int]`: Set current line width
- `undo`: Undo last draw
- `clear`: Clear all drawings

View File

@@ -14,22 +14,28 @@ It is probably best to read a Vim tutorial for the basics. All supportd keystrok
- `w[rite]`
- `/<query>`
Tab completion is supported for the `<file>` argument.
Tab completion is supported for the `<file>` argument. Down arrow will clear the current command, and up arrow will fill in the last ran command.
### Supported in Normal Mode
- `:`
- `i`
- `o`, `O`
- `A`
- `r`
- `dd`
- `dw`
- `<number>dd`
- `dw` (`dw` is not identical to vim's behaviour), `dW`
- `d$`
- `G`
- `gg`
- `<number>gg`
- `f<char>`
- `F<char>`
- `f<char>`, `F<char>`
- `<number>f<char>`, `<number>F<char>`
- `;` (same as `f<char>` but with the char the cursor is on, so not the same as vim)
- `<num>;`
- `,` (same as `F<char>` but with the char the cursor is on, so not the same as vim)
- `<num>,`
- `x`
- `h` (or left arrow), `j` (or down arrow), `k` (or up arrow), `l` (or right arrow)
- `<num>h`, `<num>j` (or down arrow), `<num>k` (or up arrow), `<num>l`

View File

@@ -25,7 +25,7 @@ To get sudo to read from stdin, the `-S` option will need to be used (eg, `sudo
## Copy / Paste
This window-like supports the paste [shortcut](../system/shortcuts.md) (`Alt+P`) if in INPUT or STDIN mode.
This window-like supports the paste [shortcut](../system/shortcuts.md) (`Alt+P`) if in INPUT or STDIN mode. The copy shortcut will copy the output of the last ran command.
## Notes

View File

@@ -16,3 +16,4 @@ cp ./target/release/mingGames_Minesweeper /usr/local/bin/mingGames_Minesweeper
cp ./target/release/mingFiles_File_Explorer /usr/local/bin/mingFiles_File_Explorer
cp ./target/release/mingFiles_Audio_Player /usr/local/bin/mingFiles_Audio_Player
cp ./target/release/mingEditing_Malvim /usr/local/bin/mingEditing_Malvim
cp ./target/release/mingUtils_Draw /usr/local/bin/mingUtils_Draw

108
linux/src/input.rs Normal file
View File

@@ -0,0 +1,108 @@
use std::os::fd::RawFd;
use std::mem::size_of;
use std::ffi::CString;
use libc::{ open, close, read, poll, pollfd, input_event, timeval, __u16, __s32, c_void };
//https://stackoverflow.com/questions/15949163/read-from-dev-input#15949311
//https://www.man7.org/linux/man-pages/man2/poll.2.html
#[allow(non_camel_case_types)]
#[repr(u16)]
#[derive(Clone, Copy, PartialEq)]
pub enum EventType {
EV_SYN = 0, //event sep
EV_KEY,
EV_REL,
EV_ABS,
//nothing below will probably ever be relevant to ming-wm
EV_MSC, //misc
EV_SW, //switch/toggle
EV_LED,
EV_SND,
EV_REP,
EV_FF,
EV_PWR,
EV_FF_STATUS,
Unknown(__u16),
}
impl TryFrom<__u16> for EventType {
type Error = ();
fn try_from(value: __u16) -> Result<Self, Self::Error> {
//if the list is any longer should probably somehow make this a macro
let values = [Self::EV_SYN, Self::EV_KEY, Self::EV_REL, Self::EV_ABS, Self::EV_MSC, Self::EV_SW, Self::EV_LED, Self::EV_SND, Self::EV_REP, Self::EV_FF, Self::EV_PWR, Self::EV_FF_STATUS];
let value = value as usize;
if value >= values.len() {
Err(())
} else {
Ok(values[value])
}
}
}
//we do not care about time. no one cares about time (probably)
pub struct InputEvent {
pub type_: EventType,
pub code: __u16, //depends on EventType
pub value: __s32,
}
pub struct Input(RawFd);
impl Input {
pub fn new(input_name: &str) -> Result<Self, ()> {
let input_name = CString::new(input_name).unwrap();
let fd = unsafe { open(input_name.as_ptr(), libc::O_RDONLY | libc::O_NONBLOCK) };
if fd == -1 {
Err(())
} else {
Ok(Self(fd))
}
}
}
impl Iterator for Input {
type Item = InputEvent;
fn next(&mut self) -> Option<Self::Item> {
//wait until there is something available
let mut fds = vec![pollfd {
fd: self.0,
events: libc::POLLIN, //return when "there is data to read"
revents: 0,
}];
let poll_result = unsafe { poll(fds.as_mut_ptr(), 1, -1) }; //neg num means no timeout
if poll_result == -1 {
return None;
}
//now read the event
let ie_size = size_of::<input_event>();
let mut ie = input_event {
time: timeval {
tv_sec: 0,
tv_usec: 0,
},
type_: 0,
code: 0,
value: 0,
};
let read_result = unsafe { read(self.0, &mut ie as *mut _ as *mut c_void, ie_size) };
if read_result == -1 {
return None;
}
let type_ = ie.type_.try_into().unwrap_or(EventType::Unknown(ie.type_));
Some(Self::Item {
type_,
code: ie.code,
value: ie.value,
})
}
}
impl Drop for Input {
fn drop(&mut self) {
unsafe { close(self.0) };
}
}

89
linux/src/keys.rs Normal file
View File

@@ -0,0 +1,89 @@
use std::io::{ Read, Stdin };
use std::sync::mpsc::{ channel, Receiver };
use std::thread;
//includes a section on reading keys
//https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
const ALPHABET: [char; 26] = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
pub enum Key {
Char(char),
Alt(char),
Ctrl(char),
Backspace,
Esc,
ArrowUp,
ArrowDown,
ArrowLeft,
ArrowRight,
Other(u8), //we don't get about anything else, lmao
}
pub struct RawStdin {
//bytes: Peekable<Bytes<StdinLock<'a>>>,
receiver: Receiver<u8>,
}
impl RawStdin {
pub fn new(stdin: Stdin) -> Self {
let (sender, receiver) = channel();
thread::spawn(move || {
let bytes = stdin.lock().bytes();
for b in bytes {
sender.send(b.unwrap()).unwrap();
}
});
RawStdin {
//bytes: stdin.lock().bytes().peekable(),
receiver,
}
}
}
impl Iterator for RawStdin {
type Item = Key;
fn next(&mut self) -> Option<Self::Item> {
let first = self.receiver.recv().unwrap();
Some(match first {
1..=26 => {
//ctrl
if first == 9 {
Key::Char('\t')
} else if first == 13 {
//ctrl+m and enter give the same thing
Key::Char('\n')
} else {
Key::Ctrl(ALPHABET[first as usize - 1])
}
},
27 => {
//escape sequence
//not handling escape sequences that are 3+ bytes is probably going to come back to bite us
let n = self.receiver.try_recv();
if let Ok(b'[') = n {
let n = self.receiver.recv().unwrap();
match n {
b'A' => Key::ArrowUp,
b'B' => Key::ArrowDown,
b'C' => Key::ArrowRight,
b'D' => Key::ArrowLeft,
_ => Key::Other(n),
}
} else if n.is_ok() {
//Alt+<char> sends Esc+<char>
Key::Alt(char::from(n.unwrap()))
} else {
Key::Esc
}
},
127 => {
Key::Backspace
},
_ => {
Key::Char(char::from(first))
},
})
}
}

View File

@@ -1,2 +1,5 @@
pub mod fb;
pub mod raw;
pub mod keys;
pub mod pty;
pub mod input;

55
linux/src/pty.rs Normal file
View File

@@ -0,0 +1,55 @@
use std::ptr;
use std::process::{ Command, Child };
use std::fs::File;
use std::os::fd::{ OwnedFd, FromRawFd };
use libc::openpty;
//basically the master and slave are linked? slave behaves just like normal terminal
//I don't totally get it, I guess just attach the command's stdout and stderr to ptyslave for reading?
pub struct PtyMaster {
pub file: File,
}
impl PtyMaster {
pub fn new(fd: OwnedFd) -> Self {
Self {
file: File::from(fd),
}
}
}
pub struct PtySlave {
pub file: File,
fd: OwnedFd,
}
impl PtySlave {
pub fn new(fd: OwnedFd) -> Self {
Self {
file: File::from(fd.try_clone().unwrap()),
fd,
}
}
//assume stdin is piped
pub fn attach_and_spawn(&self, command: &mut Command) -> std::io::Result<Child> {
command.stdout(self.fd.try_clone().unwrap());
command.stderr(self.fd.try_clone().unwrap());
command.spawn()
}
}
pub fn open_pty() -> Result<(PtyMaster, PtySlave), ()> {
let mut master_fd = 0;
let mut slave_fd = 0;
let result = unsafe { openpty(&mut master_fd, &mut slave_fd, ptr::null_mut(), ptr::null_mut(), ptr::null_mut()) };
if result == -1 {
Err(())
} else {
let master_fd = unsafe { OwnedFd::from_raw_fd(master_fd) };
let slave_fd = unsafe { OwnedFd::from_raw_fd(slave_fd) };
Ok((PtyMaster::new(master_fd), PtySlave::new(slave_fd)))
}
}

65
linux/src/raw.rs Normal file
View File

@@ -0,0 +1,65 @@
use std::io::Stdout;
use std::mem::zeroed;
use std::os::fd::AsRawFd;
use libc::{ cfmakeraw, c_int, tcgetattr, tcsetattr, termios, TCSAFLUSH };
//https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
//on TCSAFLUSH: "The TCSAFLUSH argument specifies when to apply the change: in this case, it waits for all pending output to be written to the terminal, and also discards any input that hasn't been read."
//https://www.man7.org/linux/man-pages/man3/termios.3.html
//(use cfmakeraw instead doing all those bitwise stuff manually)
//enter and exit tty raw mode
pub struct RawStdout {
pub stdout: Stdout,
old_termios: termios,
}
impl RawStdout {
pub fn new(stdout: Stdout) -> Self {
RawStdout {
stdout,
old_termios: unsafe { zeroed() },
}
}
pub fn get_termios(raw_fd: c_int) -> Result<termios, ()> {
let mut termios_struct: termios = unsafe { zeroed() };
let result = unsafe {
tcgetattr(raw_fd, &mut termios_struct)
};
if result != -1 {
Ok(termios_struct)
} else {
Err(())
}
}
pub fn enter_raw_mode(&mut self) -> Result<(), ()> {
let raw_fd = self.stdout.as_raw_fd();
let mut termios_struct = Self::get_termios(raw_fd)?;
self.old_termios = termios_struct;
let result = unsafe {
cfmakeraw(&mut termios_struct);
tcsetattr(raw_fd, TCSAFLUSH, &mut termios_struct)
};
if result != -1 {
Ok(())
} else {
Err(())
}
}
pub fn exit_raw_mode(&mut self) -> Result<(), ()> {
let result = unsafe {
tcsetattr(self.stdout.as_raw_fd(), TCSAFLUSH, &mut self.old_termios)
};
if result != -1 {
Ok(())
} else {
Err(())
}
}
}

View File

@@ -16,3 +16,4 @@ cp ./target/release/mingGames_Minesweeper ~/.local/bin/mingGames_Minesweeper
cp ./target/release/mingFiles_File_Explorer ~/.local/bin/mingFiles_File_Explorer
cp ./target/release/mingFiles_Audio_Player ~/.local/bin/mingFiles_Audio_Player
cp ./target/release/mingEditing_Malvim ~/.local/bin/mingEditing_Malvim
cp ./target/release/mingUtils_Draw ~/.local/bin/mingUtils_Draw

View File

@@ -1,6 +1,6 @@
[package]
name = "ming-wm-lib"
version = "0.1.6"
version = "0.2.2"
repository = "https://github.com/stjet/ming-wm"
description = "library for building windows for ming-wm in rust"
readme = "README.md"

View File

@@ -2,10 +2,10 @@ use std::vec;
use std::vec::Vec;
use crate::components::Component;
use ming_wm_lib::framebuffer_types::{ Dimensions, Point };
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::messages::WindowMessage;
use ming_wm_lib::window_manager_types::DrawInstructions;
use crate::framebuffer_types::{ Dimensions, Point };
use crate::themes::ThemeInfo;
use crate::messages::WindowMessage;
use crate::window_manager_types::DrawInstructions;
pub struct HighlightButton<T> {
name_: String,

View File

@@ -1,8 +1,8 @@
use std::vec::Vec;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::messages::WindowMessage;
use ming_wm_lib::window_manager_types::DrawInstructions;
use crate::themes::ThemeInfo;
use crate::messages::WindowMessage;
use crate::window_manager_types::DrawInstructions;
pub mod toggle_button;
pub mod highlight_button;

View File

@@ -1,12 +1,11 @@
use std::vec;
use std::vec::Vec;
use ming_wm_lib::framebuffer_types::{ Dimensions, Point };
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::messages::WindowMessage;
use ming_wm_lib::window_manager_types::DrawInstructions;
use ming_wm_lib::utils::calc_actual_lines;
use crate::framebuffer_types::{ Dimensions, Point };
use crate::themes::ThemeInfo;
use crate::messages::WindowMessage;
use crate::window_manager_types::DrawInstructions;
use crate::utils::calc_actual_lines;
use crate::components::Component;
const MONO_WIDTH: u8 = 10;

View File

@@ -2,10 +2,10 @@ use std::vec;
use std::vec::Vec;
use crate::components::Component;
use ming_wm_lib::framebuffer_types::{ Dimensions, Point };
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::messages::WindowMessage;
use ming_wm_lib::window_manager_types::DrawInstructions;
use crate::framebuffer_types::{ Dimensions, Point };
use crate::themes::ThemeInfo;
use crate::messages::WindowMessage;
use crate::window_manager_types::DrawInstructions;
const MONO_WIDTH: u8 = 10;

View File

@@ -2,10 +2,10 @@ use std::vec;
use std::vec::Vec;
use crate::components::Component;
use ming_wm_lib::framebuffer_types::{ Dimensions, Point };
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::messages::WindowMessage;
use ming_wm_lib::window_manager_types::DrawInstructions;
use crate::framebuffer_types::{ Dimensions, Point };
use crate::themes::ThemeInfo;
use crate::messages::WindowMessage;
use crate::window_manager_types::DrawInstructions;
pub struct ToggleButton<T> {
name_: String,

107
ming-wm-lib/src/fonts.rs Normal file
View File

@@ -0,0 +1,107 @@
use std::fs::File;
use std::io::Read;
use std::collections::HashMap;
use crate::dirs;
#[derive(Clone)]
pub struct FontCharInfo {
pub c: char,
pub data: Vec<Vec<u8>>,
pub top_offset: u8,
pub height: usize,
pub width: usize,
}
fn get_font_char(dir: &str, c: char) -> Option<FontCharInfo> {
let c = if c == '/' { '𐘋' } else if c == '\\' { '𐚆' } else if c == '.' { '𐘅' } else { c };
if let Ok(mut file) = File::open(dir.to_string() + "/" + &c.to_string() + ".alpha") {
let mut ch: Vec<Vec<u8>> = Vec::new();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
let lines: Vec<&str> = contents.split("\n").collect();
for ln in 1..lines.len() {
//.unwrap_or(0) is important because zeroes are just empty
ch.push(lines[ln].replace(":", ",,,,").replace(";", ",,,").replace(".", ",,").split(",").map(|n| n.parse().unwrap_or(0)).collect());
}
return Some(FontCharInfo {
c,
top_offset: lines[0].parse().unwrap(),
height: lines.len() - 1,
width: ch[0].len(),
data: ch,
});
}
None
}
pub fn get_font_char_from_fonts(fonts: &[String], c: char) -> FontCharInfo {
for font in fonts {
let p = dirs::exe_dir(Some(&("ming_bmps/".to_string() + &font))).to_string_lossy().to_string();
if let Some(font_char) = get_font_char(&p, c) {
return font_char;
}
}
let p = dirs::exe_dir(Some(&("ming_bmps/".to_string() + &fonts[0]))).to_string_lossy().to_string();
//so a ? char should be in every font. otherwise will just return blank
get_font_char(&p, '?').unwrap_or(FontCharInfo {
c: '?',
data: vec![vec![0]],
top_offset: 0,
height: 1,
width: 1,
})
}
pub struct MeasureInfo {
pub height: usize,
pub width: usize,
}
/// Doesn't take into account `horiz_spacing`, which defaults to 1 per character
pub fn measure_text(fonts: &[String], text: String) -> MeasureInfo {
let mut height = 0;
let mut width = 0;
for c in text.chars() {
let i = get_font_char_from_fonts(fonts, c);
let c_height = i.top_offset as usize + i.height;
if c_height > height {
height = c_height;
}
width += i.width;
}
MeasureInfo {
height,
width,
}
}
#[derive(Default)]
pub struct CachedFontCharGetter {
cache: HashMap<char, FontCharInfo>,
cache_size: usize, //# of items cached
pub max_cache_size: usize,
}
impl CachedFontCharGetter {
pub fn new(max_cache_size: usize) -> Self {
let mut s: Self = Default::default();
s.max_cache_size = max_cache_size;
s
}
pub fn get(&mut self, fonts: &[String], c: char) -> FontCharInfo {
if let Some(cached) = self.cache.get(&c) {
cached.clone()
} else {
let got = get_font_char_from_fonts(fonts, c);
if self.cache_size == self.max_cache_size {
self.cache_size = 0;
self.cache = HashMap::new();
}
self.cache.insert(c, got.clone());
self.cache_size += 1;
got
}
}
}

View File

@@ -58,9 +58,11 @@ pub fn listen(mut window_like: impl WindowLike) {
let arg = &parts.collect::<Vec<&str>>().join(" ");
let output = match method {
"handle_message" => {
//newlines allowed for ClipboardCopy, but represented by the Linear A char
window_like.handle_message(WindowMessage::deserialize(arg).unwrap()).serialize().to_string()
},
"draw" => {
//newlines never allowed
window_like.draw(&ThemeInfo::deserialize(arg).unwrap()).serialize().replace("\n", "").to_string()
},
"title" => {

View File

@@ -4,6 +4,8 @@ pub mod themes;
pub mod serialize;
pub mod messages;
pub mod ipc;
pub mod components;
pub mod fonts;
pub mod dirs;
pub mod utils;
pub mod logging;

View File

@@ -146,7 +146,7 @@ impl Serializable for WindowMessageResponse {
WindowMessageResponse::Request(req) => {
let req = match req {
WindowManagerRequest::OpenWindow(name) => format!("OpenWindow/{}", name),
WindowManagerRequest::ClipboardCopy(name) => format!("ClipboardCopy/{}", name),
WindowManagerRequest::ClipboardCopy(copy_string) => format!("ClipboardCopy/{}", copy_string.replace("\n", "𐘂")), //serialised output must be 1 line
WindowManagerRequest::CloseStartMenu => "CloseStartMenu".to_string(),
WindowManagerRequest::Unlock => "Unlock".to_string(),
WindowManagerRequest::Lock => "Lock".to_string(),
@@ -221,6 +221,7 @@ impl Serializable for DrawInstructions {
DrawInstructions::Gradient(p, d, c1, c2, u) => format!("Gradient/{}\x1E{}\x1E{}\x1E{}\x1E{}", array_to_string(p), array_to_string(d), array_to_string(c1), array_to_string(c2), u),
DrawInstructions::Bmp(p, s, b) => format!("Bmp/{}\x1E{}\x1E{}", array_to_string(p), s, b),
DrawInstructions::Circle(p, u, c) => format!("Circle/{}\x1E{}\x1E{}", array_to_string(p), u, array_to_string(c)),
DrawInstructions::Line(s, e, w, c) => format!("Line/{}\x1E{}\x1E{}\x1E{}", array_to_string(s), array_to_string(e), w, array_to_string(c)),
}
}
fn deserialize(serialized: &str) -> Result<Self, ()> {
@@ -402,6 +403,35 @@ impl Serializable for DrawInstructions {
let c = get_color(arg.unwrap())?;
Ok(DrawInstructions::Circle(p, u.unwrap(), c))
},
"Line" => {
let rest = get_rest_of_split(&mut parts, Some("/"));
//(s, e, w, c)
let mut args = rest.split("\x1E");
let arg = args.next();
if arg.is_none() {
return Err(());
}
let s = get_two_array(arg.unwrap())?;
let arg = args.next();
if arg.is_none() {
return Err(());
}
let e = get_two_array(arg.unwrap())?;
let arg = args.next();
if arg.is_none() {
return Err(());
}
let w = arg.unwrap().parse();
if w.is_err() {
return Err(());
}
let arg = args.next();
if arg.is_none() {
return Err(());
}
let c = get_color(arg.unwrap())?;
Ok(DrawInstructions::Line(s, e, w.unwrap(), c))
},
_ => Err(()),
}
}
@@ -646,7 +676,7 @@ impl Serializable for WindowMessage {
"FullscreenWindow" => Some(ShortcutType::FullscreenWindow),
"HalfWidthWindow" => Some(ShortcutType::HalfWidthWindow),
"ClipboardCopy" => Some(ShortcutType::ClipboardCopy),
"ClipboardPaste" => Some(ShortcutType::ClipboardPaste(get_rest_of_split(&mut parts, Some("/")))),
"ClipboardPaste" => Some(ShortcutType::ClipboardPaste(get_rest_of_split(&mut parts, Some("/")).replace("𐘂", "\n"))),
_ => None,
};
if let Some(shortcut) = shortcut {

View File

@@ -7,6 +7,14 @@ pub fn min(one: usize, two: usize) -> usize {
if one > two { two } else { one }
}
pub fn random_u32(seed: u32) -> u32 {
let mut seed = seed;
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed >> 5;
seed
}
pub trait Substring {
fn substring(&self, start: usize, end: usize) -> &str;
fn remove(&self, index: usize, len: usize) -> String;
@@ -88,18 +96,6 @@ pub fn calc_actual_lines<'a>(lines: impl Iterator<Item = &'a String>, max_chars_
actual_lines
}
pub fn calc_new_cursor_pos(cursor_pos: usize, new_length: usize) -> usize {
if cursor_pos >= new_length {
if new_length == 0 {
0
} else {
new_length - 1
}
} else {
cursor_pos
}
}
pub fn concat_paths(current_path: &str, add_path: &str) -> Result<PathBuf, ()> {
let mut new_path = PathBuf::from(current_path);
//if current_path is a file, automatically uses it's parent (a directory)
@@ -214,3 +210,17 @@ pub fn path_autocomplete(current_path: &str, partial_path: &str) -> Option<Strin
None
}
}
pub fn get_all_files(dir: PathBuf) -> Vec<PathBuf> {
let mut files = Vec::new();
for entry in read_dir(dir).unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
files.extend(get_all_files(path));
} else {
files.push(path);
}
}
files
}

View File

@@ -14,11 +14,18 @@ pub enum KeyChar {
#[derive(Debug)]
pub enum DrawInstructions {
/// Top left point, dimensions, colour
Rect(Point, Dimensions, RGBColor),
Text(Point, Vec<String>, String, RGBColor, RGBColor, Option<usize>, Option<u8>), //font and text
/// Top left point, fonts, text, colour, background colour, horizontal spacing, monospace width
Text(Point, Vec<String>, String, RGBColor, RGBColor, Option<usize>, Option<u8>),
/// Top left point, dimensions, start colour, end colour, steps
Gradient(Point, Dimensions, RGBColor, RGBColor, usize),
/// Top left point, path to file, reverse
Bmp(Point, String, bool),
/// Centre point, radius, colour
Circle(Point, usize, RGBColor),
/// Start point, end point, line width, line colour
Line(Point, Point, usize, RGBColor),
}
#[derive(Debug, PartialEq)]

View File

@@ -2,25 +2,25 @@ use std::vec::Vec;
use std::vec;
use std::io::BufReader;
use std::path::PathBuf;
use std::collections::HashMap;
use std::fs::{ read_to_string, File };
use std::time::{ Duration, SystemTime, UNIX_EPOCH };
use std::thread;
use std::sync::{ Arc, Mutex };
use rodio::{ Decoder, OutputStream, Sink, Source };
use rand::{ SeedableRng, prelude::SliceRandom, rngs::SmallRng };
use id3::TagLike;
use mp4ameta;
use metaflac;
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType };
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::utils::{ concat_paths, path_autocomplete, format_seconds, Substring };
use ming_wm_lib::fonts::measure_text;
use ming_wm_lib::utils::{ concat_paths, random_u32, get_all_files, path_autocomplete, format_seconds, Substring };
use ming_wm_lib::dirs::home;
use ming_wm_lib::ipc::listen;
use ming_wm::fs::get_all_files;
fn get_artist(path: &PathBuf) -> Option<String> {
let ext = path.extension().unwrap();
@@ -80,7 +80,6 @@ pub struct AudioPlayer {
impl WindowLike for AudioPlayer {
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
//
match message {
WindowMessage::Init(dimensions) => {
self.dimensions = dimensions;
@@ -110,11 +109,34 @@ impl WindowLike for AudioPlayer {
} else {
return WindowMessageResponse::DoNothing;
}
} else {
} else if key_press.is_regular() {
self.command += &key_press.key.to_string();
} else {
return WindowMessageResponse::DoNothing
}
WindowMessageResponse::JustRedraw
},
WindowMessage::Shortcut(shortcut) => {
match shortcut {
ShortcutType::ClipboardPaste(paste_string) => {
self.command += &paste_string.replace("\n", "");
WindowMessageResponse::JustRedraw
},
ShortcutType::ClipboardCopy => {
let internal_locked = self.internal.lock().unwrap();
let sink_len = internal_locked.sink.len();
if sink_len > 0 {
let queue = &internal_locked.queue;
let current = &queue[queue.len() - sink_len];
let current_name = current.0.file_name().unwrap().to_string_lossy().into_owned();
WindowMessageResponse::Request(WindowManagerRequest::ClipboardCopy(current_name))
} else {
WindowMessageResponse::DoNothing
}
},
_ => WindowMessageResponse::DoNothing,
}
},
_ => {
WindowMessageResponse::DoNothing
},
@@ -129,11 +151,15 @@ impl WindowLike for AudioPlayer {
let queue = &internal_locked.queue;
let current = &queue[queue.len() - sink_len];
let current_name = current.0.file_name().unwrap().to_string_lossy().into_owned();
instructions.push(DrawInstructions::Text([self.dimensions[0] / 2 - current_name.len() * MONO_WIDTH as usize / 2, 2], vec!["nimbus-romono".to_string(), "shippori-mincho".to_string()], current_name.clone(), theme_info.text, theme_info.background, Some(0), Some(MONO_WIDTH)));
let fonts = ["nimbus-roman".to_string(), "shippori-mincho".to_string()];
let cn_width = measure_text(&fonts, current_name.clone()).width;
instructions.push(DrawInstructions::Text([self.dimensions[0] / 2 - cn_width / 2, 2], fonts.to_vec(), current_name.clone(), theme_info.text, theme_info.background, Some(0), None));
if let Some(artist) = &current.2 {
let artist_string = "by ".to_string() + &artist;
instructions.push(DrawInstructions::Text([self.dimensions[0] / 2 - artist_string.len() * MONO_WIDTH as usize / 2, LINE_HEIGHT + 2], vec!["nimbus-romono".to_string()], artist_string, theme_info.text, theme_info.background, Some(0), Some(MONO_WIDTH)));
let as_width = measure_text(&fonts, artist_string.clone()).width;
instructions.push(DrawInstructions::Text([self.dimensions[0] / 2 - as_width / 2, LINE_HEIGHT + 2], fonts.to_vec(), artist_string, theme_info.text, theme_info.background, Some(0), None));
}
//in this case no chance of mincho so MONO_WIDTH method of calculating width is ok
let time_string = format!("{}/{}", format_seconds(internal_locked.sink.get_pos().as_secs()), format_seconds(current.1));
instructions.push(DrawInstructions::Text([self.dimensions[0] / 2 - time_string.len() * MONO_WIDTH as usize / 2, LINE_HEIGHT * 2 + 2], vec!["nimbus-romono".to_string()], time_string, theme_info.text, theme_info.background, Some(0), Some(MONO_WIDTH)));
} else {
@@ -232,8 +258,13 @@ impl AudioPlayer {
} else {
get_all_files(PathBuf::from(new_path))
};
let mut rng = SmallRng::seed_from_u64(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
queue.shuffle(&mut rng);
let mut seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_millis();
let mut q_weights: HashMap<PathBuf, u32> = HashMap::new();
for q in &queue {
seed = random_u32(seed);
q_weights.insert(q.to_path_buf(), seed);
}
queue.sort_by(|a, b| q_weights[a].cmp(&q_weights[b]));
if self.command.starts_with("p ") {
let mut locked_internal = self.internal.lock().unwrap();
(*locked_internal).sink.clear();

252
src/bin/draw.rs Normal file
View File

@@ -0,0 +1,252 @@
use std::vec::Vec;
use std::vec;
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
use ming_wm_lib::framebuffer_types::{ Dimensions, Point, RGBColor };
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::utils::{ hex_to_u8, HEX_CHARS, Substring };
use ming_wm_lib::ipc::listen;
enum DrawAction {
Line(Point, Option<Point>, usize, RGBColor),
Rect(Point, Option<Dimensions>, RGBColor),
Circle(Point, Option<usize>, RGBColor),
}
impl DrawAction {
fn name(&self) -> String {
match self {
DrawAction::Line(_, _, _, _) => "Line",
DrawAction::Rect(_, _, _) => "Rect",
DrawAction::Circle(_, _, _) => "Circle",
}.to_string()
}
}
#[derive(Default, PartialEq)]
enum Mode {
#[default]
Move,
Input,
}
#[derive(Default)]
struct Draw {
mode: Mode,
dimensions: Dimensions,
draw_actions: Vec<DrawAction>,
current_location: Point,
current_input: String,
current_color: RGBColor,
current_linewidth: usize,
current_action: Option<DrawAction>,
}
impl WindowLike for Draw {
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
match message {
WindowMessage::Init(dimensions) => {
self.dimensions = dimensions;
WindowMessageResponse::JustRedraw
},
WindowMessage::ChangeDimensions(dimensions) => {
self.dimensions = dimensions;
WindowMessageResponse::JustRedraw
},
WindowMessage::KeyPress(key_press) => {
if key_press.is_escape() && (self.current_action.is_some() || self.mode != Mode::Move) {
self.current_action = None;
self.mode = Mode::Move;
self.current_input = String::new();
WindowMessageResponse::JustRedraw
} else if self.mode == Mode::Input {
if key_press.is_backspace() && self.current_input.len() > 0 {
self.current_input = self.current_input.remove_last();
WindowMessageResponse::JustRedraw
} else if key_press.is_enter() {
//process current input
let mut parts = self.current_input.split(" ");
match parts.next().unwrap() {
"line" | "l" => {
self.current_action = Some(DrawAction::Line(self.current_location, None, self.current_linewidth, self.current_color));
},
"rect" | "r" => {
self.current_action = Some(DrawAction::Rect(self.current_location, None, self.current_color));
},
"circle" | "c" => {
self.current_action = Some(DrawAction::Circle(self.current_location, None, self.current_color));
},
"colour" | "color" | "co" => {
//hex to u8
if let Some(hex_color) = parts.next() {
if hex_color.len() == 6 && hex_color.chars().all(|c| HEX_CHARS.contains(&c)) {
let mut hex_chars = hex_color.chars();
self.current_color = [hex_to_u8(hex_chars.next().unwrap(), hex_chars.next().unwrap()), hex_to_u8(hex_chars.next().unwrap(), hex_chars.next().unwrap()), hex_to_u8(hex_chars.next().unwrap(), hex_chars.next().unwrap())];
}
}
},
"linewidth" | "lw" => {
if let Ok(linewidth) = parts.next().unwrap_or("").parse::<usize>() {
self.current_linewidth = linewidth;
}
},
"undo" | "u" => {
self.draw_actions.pop();
},
"clear" | "cl" => {
self.draw_actions = Vec::new();
},
_ => {},
};
self.mode = Mode::Move;
self.current_input = String::new();
WindowMessageResponse::JustRedraw
} else if key_press.is_regular() {
self.current_input += &key_press.key.to_string();
WindowMessageResponse::JustRedraw
} else {
WindowMessageResponse::DoNothing
}
} else if key_press.key == 'i' && self.current_action.is_none() {
self.mode = Mode::Input;
WindowMessageResponse::JustRedraw
} else if key_press.is_enter() {
if let Some(current_action) = &self.current_action {
self.draw_actions.push(match current_action {
DrawAction::Line(p, _, u, r) => DrawAction::Line(*p, Some(self.current_location), *u, *r),
DrawAction::Rect(p, _, r) => {
let d = [
if self.current_location[0] > p[0] {
self.current_location[0] - p[0]
} else {
p[0] - self.current_location[0]
},
if self.current_location[1] > p[1] {
self.current_location[1] - p[1]
} else {
p[1] - self.current_location[1]
}
];
//find top left corner
let tl = [
if p[0] < self.current_location[0] {
p[0]
} else {
self.current_location[0]
},
if p[1] < self.current_location[1] {
p[1]
} else {
self.current_location[1]
}
];
DrawAction::Rect(tl, Some(d), *r)
},
DrawAction::Circle(p, _, c) => {
let r = ((self.current_location[1] as f64 - p[1] as f64).powi(2) + (self.current_location[0] as f64 - p[0] as f64).powi(2)).sqrt();
DrawAction::Circle(*p, Some(r.round() as usize), *c)
},
});
self.current_action = None;
WindowMessageResponse::JustRedraw
} else {
WindowMessageResponse::DoNothing
}
} else if key_press.is_up_arrow() || key_press.key == 'k' {
if self.current_location[1] > 0 {
self.current_location[1] -= 1;
WindowMessageResponse::JustRedraw
} else {
WindowMessageResponse::DoNothing
}
} else if key_press.is_down_arrow() || key_press.key == 'j' {
if self.current_location[1] + 1 < self.dimensions[1] {
self.current_location[1] += 1;
WindowMessageResponse::JustRedraw
} else {
WindowMessageResponse::DoNothing
}
} else if key_press.is_left_arrow() || key_press.key == 'h' {
if self.current_location[0] > 0 {
self.current_location[0] -= 1;
WindowMessageResponse::JustRedraw
} else {
WindowMessageResponse::DoNothing
}
} else if key_press.is_right_arrow() || key_press.key == 'l' {
if self.current_location[0] + 1 < self.dimensions[0] {
self.current_location[0] += 1;
WindowMessageResponse::JustRedraw
} else {
WindowMessageResponse::DoNothing
}
} else {
WindowMessageResponse::DoNothing
}
},
_ => WindowMessageResponse::DoNothing,
}
}
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
let mut instructions = Vec::new();
//draw previous actions
for action in &self.draw_actions {
instructions.push(match action {
DrawAction::Line(p1, p2, lw, c) => DrawInstructions::Line(*p1, p2.unwrap(), *lw, *c),
DrawAction::Rect(p, d, c) => DrawInstructions::Rect(*p, d.unwrap(), *c),
DrawAction::Circle(p, r, c) => DrawInstructions::Circle(*p, r.unwrap(), *c),
});
}
//draw cursor (crosshair)
let crosshair_min_x = self.current_location[0].checked_sub(6).unwrap_or(0);
let crosshair_min_y = self.current_location[1].checked_sub(6).unwrap_or(0);
//^going over should be handled by the drawer, probably?
instructions.push(DrawInstructions::Line([crosshair_min_x, self.current_location[1]], [self.current_location[0] + 6, self.current_location[1]], 1, self.current_color));
instructions.push(DrawInstructions::Line([self.current_location[0], crosshair_min_y], [self.current_location[0], self.current_location[1] + 6], 1, self.current_color));
//draw info or current input
instructions.push(DrawInstructions::Text([2, self.dimensions[1] - 19], vec!["nimbus-roman".to_string()], if self.current_input == String::new() {
if let Some(current_action) = &self.current_action {
current_action.name()
} else if self.mode == Mode::Move {
"'i' to enter input mode".to_string()
} else {
"Awaiting input".to_string()
}
} else {
self.current_input.clone()
}, theme_info.text, theme_info.background, None, None));
instructions
}
//properties
fn title(&self) -> String {
"Draw".to_string()
}
fn subtype(&self) -> WindowLikeType {
WindowLikeType::Window
}
fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions {
[410, 410]
}
fn resizable(&self) -> bool {
true
}
}
impl Draw {
pub fn new() -> Self {
let mut d: Self = Default::default();
d.current_linewidth = 1;
d
}
}
pub fn main() {
listen(Draw::new());
}

View File

@@ -9,7 +9,7 @@ use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManager
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
use ming_wm_lib::utils::{ calc_actual_lines, calc_new_cursor_pos, Substring };
use ming_wm_lib::utils::{ calc_actual_lines, Substring };
use ming_wm_lib::dirs::home;
use ming_wm_lib::utils::{ get_rest_of_split, path_autocomplete };
use ming_wm_lib::ipc::listen;
@@ -19,6 +19,8 @@ const LINE_HEIGHT: usize = 18;
const PADDING: usize = 2;
const BAND_HEIGHT: usize = 19;
const WORD_END: [char; 8] = ['.', ',', ':', '[', ']', '{', '}', ' '];
struct FileInfo {
pub name: String,
pub path: String,
@@ -43,6 +45,12 @@ enum State {
//
}
impl State {
fn is_numberable(&self) -> bool {
*self == State::Maybeg || *self == State::Find || *self == State::BackFind
}
}
#[derive(Default, PartialEq)]
enum Mode {
#[default]
@@ -77,6 +85,7 @@ struct Malvim {
state: State,
mode: Mode,
command: Option<String>,
prev_command: Option<String>,
bottom_message: Option<String>,
maybe_num: Option<usize>,
files: Vec<FileInfo>,
@@ -103,10 +112,22 @@ impl WindowLike for Malvim {
self.mode = Mode::Command;
self.command = Some(String::new());
changed = false;
} else if (key_press.key == 'i' || key_press.key == 'A') && self.mode == Mode::Normal && self.state == State::None && self.files.len() > 0 {
if key_press.key == 'A' {
} else if (key_press.key == 'i' || key_press.key == 'A' || key_press.key == 'o' || key_press.key == 'O') && self.mode == Mode::Normal && self.state == State::None && self.files.len() > 0 {
let current_file = &mut self.files[self.current_file_index];
if key_press.key == 'A' {
current_file.cursor_pos = current_file.content[current_file.line_pos].chars().count();
} else if key_press.key == 'o' || key_press.key == 'O' {
let current_line = &current_file.content[current_file.line_pos];
let spaces = Malvim::calc_spaces(self.autoindent, current_line);
let n = if key_press.key == 'o' {
current_file.line_pos + 1
} else {
current_file.line_pos
};
current_file.content.insert(n, " ".repeat(spaces));
current_file.line_pos = n;
current_file.cursor_pos = spaces;
new = true;
}
self.mode = Mode::Insert;
changed = false;
@@ -120,20 +141,7 @@ impl WindowLike for Malvim {
let left = left.into_iter().map(|c| c.to_string()).collect::<Vec<String>>().join("");
let right = right.into_iter().map(|c| c.to_string()).collect::<Vec<String>>().join("");
current_file.content[current_file.line_pos] = left.to_string();
let spaces = if self.autoindent {
//find out how many spaces the line starts with, copy that to the new line
let mut spaces = 0;
for c in left.chars() {
if c == ' ' {
spaces += 1;
} else {
break;
}
}
spaces
} else {
0
};
let spaces = Malvim::calc_spaces(self.autoindent, &left);
current_file.content.insert(current_file.line_pos + 1, " ".repeat(spaces) + &right);
current_file.line_pos += 1;
current_file.cursor_pos = spaces;
@@ -168,15 +176,18 @@ impl WindowLike for Malvim {
self.state = State::None;
} else if self.state == State::MaybeDelete {
if key_press.key == 'd' {
for _ in 0..self.maybe_num.unwrap_or(1) {
current_file.content.remove(current_file.line_pos);
if current_file.content.len() == 0 {
current_file.content = vec![String::new()];
} else if current_file.line_pos == current_file.content.len() {
current_file.line_pos = current_file.content.len() - 1;
break;
}
}
let new_length = current_file.content[current_file.line_pos].chars().count();
current_file.cursor_pos = calc_new_cursor_pos(current_file.cursor_pos, new_length);
} else if key_press.key == 'w' || key_press.key == '$' {
current_file.cursor_pos = Malvim::calc_new_cursor_pos(current_file.cursor_pos, new_length);
} else if key_press.key == 'w' || key_press.key == 'W' || key_press.key == '$' {
let line = &current_file.content[current_file.line_pos];
let line_len = line.chars().count();
if line_len > 0 && current_file.cursor_pos < line_len {
@@ -184,18 +195,26 @@ impl WindowLike for Malvim {
let mut line_chars = line.chars().skip(current_file.cursor_pos).peekable();
//deref is Copy
let current_char = *line_chars.peek().unwrap();
let offset = if key_press.key == 'w' {
line_chars.position(|c| if current_char == ' ' {
let offset = if key_press.key == 'W' || key_press.key == 'w' {
line_chars.position(|c| if key_press.key == 'w' {
if WORD_END.contains(&current_char) {
c != current_char
} else {
WORD_END.contains(&c)
}
} else {
if current_char == ' ' {
c != ' '
} else {
c == ' '
}
}).unwrap_or(line_len - current_file.cursor_pos)
} else {
line_chars.count()
};
current_file.content[current_file.line_pos] = line.remove(current_file.cursor_pos, offset);
let new_length = current_file.content[current_file.line_pos].chars().count();
current_file.cursor_pos = calc_new_cursor_pos(current_file.cursor_pos, new_length);
current_file.cursor_pos = Malvim::calc_new_cursor_pos(current_file.cursor_pos, new_length);
}
}
self.state = State::None;
@@ -206,15 +225,21 @@ impl WindowLike for Malvim {
current_file.line_pos = current_file.content.len() - 1;
}
let new_length = current_file.content[current_file.line_pos].chars().count();
current_file.cursor_pos = calc_new_cursor_pos(current_file.cursor_pos, new_length);
current_file.cursor_pos = Malvim::calc_new_cursor_pos(current_file.cursor_pos, new_length);
}
changed = false;
self.state = State::None;
} else if self.state == State::Find || self.state == State::BackFind {
let old_pos = current_file.cursor_pos;
let find_pos = if self.state == State::Find {
} else if self.state == State::Find || self.state == State::BackFind || key_press.key == ';' || key_press.key == ',' {
let mut old_pos = current_file.cursor_pos;
let find_char = if self.state == State::Find || self.state == State::BackFind {
key_press.key
} else {
current_file.content[current_file.line_pos].chars().nth(old_pos).unwrap()
};
for _ in 0..self.maybe_num.unwrap_or(1) {
let find_pos = if self.state == State::Find || key_press.key == ';' {
if old_pos < current_file.content[current_file.line_pos].chars().count() {
let found_index = current_file.content[current_file.line_pos].chars().skip(old_pos + 1).position(|c| c == key_press.key);
let found_index = current_file.content[current_file.line_pos].chars().skip(old_pos + 1).position(|c| c == find_char);
if let Some(found_index) = found_index {
old_pos + found_index + 1
} else {
@@ -226,7 +251,7 @@ impl WindowLike for Malvim {
} else {
//how does this work again? no idea
if old_pos != 0 {
let found_index = current_file.content[current_file.line_pos].chars().rev().skip(current_length - old_pos).position(|c| c == key_press.key);
let found_index = current_file.content[current_file.line_pos].chars().rev().skip(current_length - old_pos).position(|c| c == find_char);
if let Some(found_index) = found_index {
old_pos - found_index - 1
} else {
@@ -237,6 +262,8 @@ impl WindowLike for Malvim {
}
};
current_file.cursor_pos = find_pos;
old_pos = current_file.cursor_pos;
}
changed = false;
self.state = State::None;
} else if key_press.key == 'x' {
@@ -262,7 +289,7 @@ impl WindowLike for Malvim {
current_file.line_pos = current_file.line_pos.checked_sub(self.maybe_num.unwrap_or(1)).unwrap_or(0);
}
let new_length = current_file.content[current_file.line_pos].chars().count();
current_file.cursor_pos = calc_new_cursor_pos(current_file.cursor_pos, new_length);
current_file.cursor_pos = Malvim::calc_new_cursor_pos(current_file.cursor_pos, new_length);
changed = false;
} else if key_press.key == 'l' || key_press.is_right_arrow() {
if current_length > 0 {
@@ -295,7 +322,7 @@ impl WindowLike for Malvim {
} else if key_press.key == 'G' {
current_file.line_pos = current_file.content.len() - 1;
let new_length = current_file.content[current_file.line_pos].chars().count();
current_file.cursor_pos = calc_new_cursor_pos(current_file.cursor_pos, new_length);
current_file.cursor_pos = Malvim::calc_new_cursor_pos(current_file.cursor_pos, new_length);
changed = false;
} else if key_press.key == 'f' {
self.state = State::Find;
@@ -392,7 +419,7 @@ impl WindowLike for Malvim {
changed = false;
}
//reset maybe_num if not num
if !numbered && self.state != State::Maybeg {
if !numbered && !self.state.is_numberable() {
self.maybe_num = None;
}
} else if self.mode == Mode::Command {
@@ -400,7 +427,8 @@ impl WindowLike for Malvim {
let command = self.command.clone().unwrap_or("".to_string());
if key_press.is_enter() {
new = self.process_command();
self.command = None;
self.prev_command = self.command.take();
//line above does same as `self.command = None`
self.mode = Mode::Normal;
} else if key_press.key == '\t' { //tab
let mut parts = command.split(" ").skip(1);
@@ -421,7 +449,13 @@ impl WindowLike for Malvim {
}
} else if key_press.is_backspace() {
if command.len() > 0 {
self.command = Some(command[..command.len() - 1].to_string());
self.command = Some(command.remove_last());
}
} else if key_press.is_arrow() {
if key_press.is_up_arrow() {
self.command = self.prev_command.clone();
} else if key_press.is_down_arrow() {
self.command = Some(String::new());
}
} else {
self.command = Some(command.to_string() + &key_press.key.to_string());
@@ -457,11 +491,21 @@ impl WindowLike for Malvim {
ShortcutType::ClipboardPaste(copy_string) => {
if self.mode == Mode::Insert {
let current_file = &mut self.files[self.current_file_index];
for (i, cs) in copy_string.split("\n").enumerate() {
if i == 0 {
//modify current line
let line = &current_file.content[current_file.line_pos];
current_file.content[current_file.line_pos] = line.substring(0, current_file.cursor_pos).to_string() + &copy_string + line.substring(current_file.cursor_pos, line.chars().count());
current_file.content[current_file.line_pos] = line.substring(0, current_file.cursor_pos).to_string() + &cs + line.substring(current_file.cursor_pos, line.chars().count());
current_file.cursor_pos += copy_string.len();
} else {
//insert a new line
current_file.content.insert(current_file.line_pos + 1, cs.to_string());
current_file.line_pos += 1;
current_file.cursor_pos = cs.chars().count();
}
}
self.calc_top_line_pos();
self.calc_current(); //too over zealous but whatever
self.calc_current();
self.files[self.current_file_index].changed = true;
WindowMessageResponse::JustRedraw
} else {
@@ -581,6 +625,34 @@ impl Malvim {
Default::default()
}
fn calc_spaces(autoindent: bool, left: &str) -> usize {
if autoindent {
let mut spaces = 0;
for c in left.chars() {
if c == ' ' {
spaces += 1;
} else {
break;
}
}
spaces
} else {
0
}
}
fn calc_new_cursor_pos(cursor_pos: usize, new_length: usize) -> usize {
if cursor_pos >= new_length {
if new_length == 0 {
0
} else {
new_length - 1
}
} else {
cursor_pos
}
}
fn calc_top_line_pos(&mut self) {
if self.files.len() == 0 {
return;

View File

@@ -7,7 +7,7 @@ use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLik
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::utils::{ u8_to_hex, hex_to_u8, HEX_CHARS };
use ming_wm_lib::utils::{ random_u32, u8_to_hex, hex_to_u8, HEX_CHARS };
use ming_wm_lib::ipc::listen;
//16x16 with 40 mines
@@ -252,11 +252,7 @@ impl Minesweeper {
//https://en.wikipedia.org/wiki/Xorshift
//from 0 to 15
pub fn random(&mut self) -> usize {
let mut x = self.random_seed;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
self.random_seed = x;
self.random_seed = random_u32(self.random_seed);
self.random_seed as usize % 16
}

View File

@@ -3,15 +3,16 @@ use std::vec;
use std::sync::mpsc::{ channel, Receiver, Sender };
use std::thread;
use std::process::{ Child, Stdio };
use std::process::Command;
use std::io::{ Read, Write };
use std::time::Duration;
use std::path::PathBuf;
use std::fmt;
use pty_process::blocking;
use linux::pty::open_pty;
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, ShortcutType };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType };
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::utils::{ concat_paths, path_autocomplete, Substring };
@@ -46,6 +47,11 @@ fn strip_ansi_escape_codes(line: String) -> String {
new_line
}
fn bytes_to_string(bytes: Vec<u8>) -> String {
let bytes_len = bytes.len();
String::from_utf8(bytes).unwrap_or("?".repeat(bytes_len))
}
#[derive(Default, PartialEq)]
enum Mode {
#[default]
@@ -78,6 +84,7 @@ pub struct Terminal {
current_path: String,
running_process: Option<Child>,
process_current_line: Vec<u8>, //bytes of line
output: String, //current or previous running output of command
pty_outerr_rx: Option<Receiver<u8>>,
pty_in_tx: Option<Sender<String>>,
history: Vec<String>,
@@ -116,6 +123,7 @@ impl WindowLike for Terminal {
self.history_index = None;
self.mode = self.process_command();
self.current_input = String::new();
self.output = String::new();
} else if key_press.key == '\t' { //tab
//autocomplete assuming it's a file system path
//...mostly working
@@ -144,12 +152,17 @@ impl WindowLike for Terminal {
loop {
if let Ok(ci) = self.pty_outerr_rx.as_mut().unwrap().recv_timeout(Duration::from_millis(5)) {
if char::from(ci) == '\n' {
let pcl_len = self.process_current_line.len();
self.lines.push(strip_ansi_escape_codes(String::from_utf8(self.process_current_line.clone()).unwrap_or("?".repeat(pcl_len))));
let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()));
self.output += &append_line;
self.output += "\n";
self.lines.push(append_line);
self.process_current_line = Vec::new();
} else if char::from(ci) == '\r' {
//for now, ignore
//
} else if char::from(ci) == '\t' {
//for now, interpret as space
self.process_current_line.push(b' ');
} else {
self.process_current_line.push(ci);
}
@@ -163,7 +176,14 @@ impl WindowLike for Terminal {
//process exited
self.pty_outerr_rx = None;
self.mode = Mode::Input;
if self.process_current_line.len() > 0 {
//add to lines
let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()));
self.output += &append_line;
self.lines.push(append_line);
//only need to reset if not empty
self.process_current_line = Vec::new();
}
changed = true;
} else {
if key_press.key == 'i' {
@@ -184,8 +204,9 @@ impl WindowLike for Terminal {
} else if key_press.is_enter() {
let _ = self.pty_in_tx.as_mut().unwrap().send(self.current_stdin_input.clone());
self.mode = Mode::Running;
let pcl_len = self.process_current_line.len();
self.lines.push(strip_ansi_escape_codes(String::from_utf8(self.process_current_line.clone()).unwrap_or("?".repeat(pcl_len))) + &self.current_stdin_input);
let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()) + &self.current_stdin_input);
self.output += &append_line;
self.lines.push(append_line);
self.current_stdin_input = String::new();
self.process_current_line = Vec::new();
} else if key_press.is_backspace() {
@@ -207,6 +228,12 @@ impl WindowLike for Terminal {
//kills and running_process is now None
let _ = self.running_process.take().unwrap().kill();
self.mode = Mode::Input;
if self.process_current_line.len() > 0 {
let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()));
self.output += &append_line;
self.lines.push(append_line);
self.process_current_line = Vec::new();
}
WindowMessageResponse::JustRedraw
} else if self.mode == Mode::Input && (key_press.key == 'p' || key_press.key == 'n') {
//only the last command is saved unlike other terminals. good enough for me
@@ -228,6 +255,7 @@ impl WindowLike for Terminal {
},
WindowMessage::Shortcut(shortcut) => {
match shortcut {
ShortcutType::ClipboardCopy => WindowMessageResponse::Request(WindowManagerRequest::ClipboardCopy(self.output.clone())),
ShortcutType::ClipboardPaste(copy_string) => {
if self.mode == Mode::Input || self.mode == Mode::Stdin {
if self.mode == Mode::Input {
@@ -331,11 +359,13 @@ impl Terminal {
}
Mode::Input
} else {
let (pty, pts) = blocking::open().unwrap();
self.running_process = Some(blocking::Command::new("sh").arg("-c").arg(&self.current_input).current_dir(&self.current_path).stdin(Stdio::piped()).spawn(pts).unwrap());
let (pty, pts) = open_pty().unwrap();
let mut cmd = Command::new("sh");
let cmd = cmd.arg("-c").arg(&self.current_input).current_dir(&self.current_path).stdin(Stdio::piped());
self.running_process = Some(pts.attach_and_spawn(cmd).unwrap());
let (tx1, rx1) = channel();
thread::spawn(move || {
for ci in pty.bytes() {
for ci in pty.file.bytes() {
if let Ok(ci) = ci {
tx1.send(ci).unwrap();
} else {
@@ -378,8 +408,7 @@ impl Terminal {
//must_add_current_line will be false
"$ ".to_string() + &self.current_input + ""
} else {
let pcl_len = self.process_current_line.len();
strip_ansi_escape_codes(String::from_utf8(self.process_current_line.clone()).unwrap_or("?".repeat(pcl_len))) + &self.current_stdin_input.clone() + ""
strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()) + &self.current_stdin_input.clone() + "")
}
} else {
self.lines[line_num].clone()

View File

@@ -1,20 +1,21 @@
use std::process::{ Command, Stdio };
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use std::io::{ stdin, stdout, BufReader, BufRead, Write };
use std::io::{ stdin, stdout, Write };
use std::process::exit;
use std::env;
use linux::fb::Framebuffer;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use termion::event::Key;
use linux::raw::RawStdout;
use linux::keys::{ RawStdin, Key };
use linux::input::{ Input, EventType };
use wm::framebuffer::{ FramebufferWriter, FramebufferInfo };
use wm::window_manager::WindowManager;
use ming_wm_lib::window_manager_types::KeyChar;
use ming_wm_lib::messages::*;
use ming_wm::framebuffer::{ FramebufferWriter, FramebufferInfo };
use ming_wm::window_manager::WindowManager;
include!(concat!(env!("OUT_DIR"), "/password.rs"));
const CLEAR_ALL: &'static str = "\x1b[2J";
const HIDE_CURSOR: &'static str = "\x1b[?25l";
@@ -30,10 +31,10 @@ fn key_to_char(key: Key) -> Option<KeyChar> {
Key::Ctrl(c) => Some(KeyChar::Ctrl(c)),
Key::Backspace => Some(KeyChar::Press('𐘁')),
Key::Esc => Some(KeyChar::Press('𐘃')),
Key::Up => Some(KeyChar::Press('𐙘')),
Key::Down => Some(KeyChar::Press('𐘞')),
Key::Left => Some(KeyChar::Press('𐙣')),
Key::Right => Some(KeyChar::Press('𐙥')),
Key::ArrowUp => Some(KeyChar::Press('𐙘')),
Key::ArrowDown => Some(KeyChar::Press('𐘞')),
Key::ArrowLeft => Some(KeyChar::Press('𐙣')),
Key::ArrowRight => Some(KeyChar::Press('𐙥')),
_ => None,
}
}
@@ -71,15 +72,16 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
writer.init(framebuffer_info.clone());
let mut wm: WindowManager = WindowManager::new(writer, framebuffer, dimensions, rotate, grayscale);
let mut wm: WindowManager = WindowManager::new(writer, framebuffer, dimensions, rotate, grayscale, env!("CARGO_PKG_VERSION").to_string(), PASSWORD_HASH);
let mut stdout = stdout().into_raw_mode().unwrap();
let mut stdout = RawStdout::new(stdout());
stdout.enter_raw_mode().unwrap();
write!(stdout, "{}", CLEAR_ALL).unwrap();
write!(stdout.stdout, "{}", CLEAR_ALL).unwrap();
write!(stdout, "{}", HIDE_CURSOR).unwrap();
write!(stdout.stdout, "{}", HIDE_CURSOR).unwrap();
stdout.flush().unwrap();
stdout.stdout.flush().unwrap();
wm.draw(None, false);
@@ -89,9 +91,9 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
//read key presses
thread::spawn(move || {
let stdin = stdin().lock();
for c in stdin.keys() {
if let Some(kc) = key_to_char(c.unwrap()) {
let stdin = RawStdin::new(stdin());
for c in stdin {
if let Some(kc) = key_to_char(c) {
//do not allow exit when locked unless debugging
//if kc == KeyChar::Alt('E') {
if kc == KeyChar::Alt('E') {
@@ -108,21 +110,19 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
//read touchscreen presses (hopefully)
thread::spawn(move || {
//spawn evtest, parse it for touch coords
if touch {
let mut evtest = Command::new("evtest").arg("/dev/input/by-path/first-touchscreen").stdout(Stdio::piped()).spawn().unwrap();
let reader = BufReader::new(evtest.stdout.as_mut().unwrap());
let mut events = Input::new("/dev/input/by-path/first-touchscreen").unwrap(); //panics in threads don't matter in this case
let mut x: Option<usize> = None;
let mut y: Option<usize> = None;
for line in reader.lines() {
let line = line.unwrap();
if line.contains("ABS_X), value ") || line.contains("ABS_Y), value ") {
let value: Vec<_> = line.split("), value ").collect();
let value = value[value.len() - 1].parse::<usize>().unwrap();
if line.contains("ABS_X") {
x = Some(value);
loop {
let event = events.next();
if let Some(event) = event {
//ABS_X = 0, ABS_Y = 1
if event.type_ == EventType::EV_ABS && (event.code == 0 || event.code == 1) {
if event.code == 0 {
x = Some(event.value as usize); //event.value is u16 so this should be fine. unless usize is u8, lmao
} else {
y = Some(value);
y = Some(event.value as usize);
}
if x.is_some() && y.is_some() {
let (x2, y2) = if rotate {
@@ -140,6 +140,7 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
y = None;
}
}
}
thread::sleep(Duration::from_millis(1));
}
}
@@ -152,15 +153,18 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
for message in rx {
match message {
ThreadMessage::KeyChar(kc) => wm.handle_message(WindowManagerMessage::KeyChar(kc.clone())),
ThreadMessage::Touch(x, y) => wm.handle_message(WindowManagerMessage::Touch(x, y)),
ThreadMessage::Touch(x, y) => {
wm.handle_message(WindowManagerMessage::Touch(x, y));
println!(" "); //without any stdout, on my phone, for some reason the framebuffer doesn't get redrawn to the screen
},
ThreadMessage::Clear => {
write!(stdout, "{}", CLEAR_ALL).unwrap();
stdout.flush().unwrap();
write!(stdout.stdout, "{}", CLEAR_ALL).unwrap();
stdout.stdout.flush().unwrap();
},
ThreadMessage::Exit => {
if !wm.locked {
write!(stdout, "{}", SHOW_CURSOR).unwrap();
stdout.suspend_raw_mode().unwrap();
write!(stdout.stdout, "{}", SHOW_CURSOR).unwrap();
stdout.exit_raw_mode().unwrap();
exit(0);
}
},

View File

@@ -1,72 +0,0 @@
use std::fs::{ read_dir, File };
use std::path::PathBuf;
use std::io::Read;
use std::collections::HashMap;
use ming_wm_lib::dirs;
use ming_wm_lib::utils::get_rest_of_split;
fn get_font_char(dir: &str, c: char) -> Option<(char, Vec<Vec<u8>>, u8)> {
let c = if c == '/' { '𐘋' } else if c == '\\' { '𐚆' } else if c == '.' { '𐘅' } else { c };
if let Ok(mut file) = File::open(dir.to_string() + "/" + &c.to_string() + ".alpha") {
let mut ch: Vec<Vec<u8>> = Vec::new();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
let lines: Vec<&str> = contents.split("\n").collect();
for l in 1..lines.len() {
//.unwrap_or(0) is important because zeroes are just empty
ch.push(lines[l].split(",").map(|n| n.parse().unwrap_or(0)).collect());
}
return Some((c, ch, lines[0].parse().unwrap()));
}
None
}
pub fn get_font_char_from_fonts(fonts: &[String], c: char) -> (char, Vec<Vec<u8>>, u8) {
for font in fonts {
let p = dirs::exe_dir(Some(&("ming_bmps/".to_string() + &font))).to_string_lossy().to_string();
if let Some(font_char) = get_font_char(&p, c) {
return font_char;
}
}
let p = dirs::exe_dir(Some(&("ming_bmps/".to_string() + &fonts[0]))).to_string_lossy().to_string();
//so a ? char should be in every font. otherwise will just return blank
get_font_char(&p, '?').unwrap_or(('?', vec![vec![0]], 0))
}
pub fn get_all_files(dir: PathBuf) -> Vec<PathBuf> {
let mut files = Vec::new();
for entry in read_dir(dir).unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
files.extend(get_all_files(path));
} else {
files.push(path);
}
}
files
}
//Category, Vec<Display name, file name>
pub type ExeWindowInfos = HashMap<String, Vec<(String, String)>>;
//well, doesn't actually look to see if its executable. Just if it contains a _ and has no file extension, and is a file
pub fn get_all_executable_windows() -> ExeWindowInfos {
let mut exes = HashMap::new();
for entry in read_dir(dirs::exe_dir(None)).unwrap() {
let pb = entry.unwrap().path();
if pb.is_file() && pb.extension().is_none() {
let parts = pb.file_stem().unwrap().to_string_lossy().to_string();
let mut parts = parts.split('_');
let category = parts.next().unwrap();
let display = get_rest_of_split(&mut parts, Some(" "));
let file_name = pb.file_name().unwrap().to_string_lossy().to_string();
if display != String::new() && category.starts_with("ming") {
let pair = (display, file_name);
exes.entry(category.to_string()).and_modify(|v: &mut Vec<(String, String)>| (*v).push(pair.clone())).or_insert(vec![pair]);
}
}
}
exes
}

12
wm/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "wm"
version = "1.2.0"
repository = "https://github.com/stjet/ming-wm"
license = "GPL-3.0-or-later"
edition = "2021"
[dependencies]
ming-wm-lib = { path = "../ming-wm-lib" }
linux = { path = "../linux" }
bitcoin_hashes = { version = "0.16.0", default-features = false }
bmp-rust = "0.5.0"

View File

@@ -7,12 +7,13 @@ use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::dirs::exe_dir;
use crate::components::Component;
use crate::components::paragraph::Paragraph;
use ming_wm_lib::components::Component;
use ming_wm_lib::components::paragraph::Paragraph;
pub struct About {
dimensions: Dimensions,
components: Vec<Box<dyn Component<()> + Send>>,
version: String,
}
impl WindowLike for About {
@@ -40,7 +41,7 @@ impl WindowLike for About {
//properties
fn title(&self) -> String {
"About".to_string()
"About".to_string() + " - v" + &self.version
}
fn subtype(&self) -> WindowLikeType {
@@ -53,10 +54,11 @@ impl WindowLike for About {
}
impl About {
pub fn new() -> Self {
pub fn new(version: String) -> Self {
Self {
dimensions: [0, 0],
components: Vec::new(),
version,
}
}
}

View File

@@ -8,8 +8,8 @@ use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
use ming_wm_lib::dirs::exe_dir;
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use crate::components::paragraph::Paragraph;
use crate::components::Component;
use ming_wm_lib::components::paragraph::Paragraph;
use ming_wm_lib::components::Component;
pub struct Help {
dimensions: Dimensions,

View File

@@ -1,19 +1,18 @@
use std::vec;
use std::vec::Vec;
use std::time::{ SystemTime, UNIX_EPOCH }; //for psuedo-randomness
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest };
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
use blake2::{ Blake2b512, Digest };
include!(concat!(env!("OUT_DIR"), "/password.rs"));
//const PASSWORD_HASH: [u8; 64] = [220, 88, 183, 188, 240, 27, 107, 181, 58, 191, 198, 170, 114, 38, 7, 148, 6, 179, 75, 128, 231, 171, 172, 220, 85, 38, 36, 113, 116, 146, 70, 197, 163, 179, 158, 192, 130, 53, 247, 48, 47, 209, 95, 96, 179, 211, 4, 122, 254, 127, 21, 165, 139, 199, 151, 226, 216, 176, 123, 41, 194, 221, 58, 69];
use bitcoin_hashes::Sha512;
pub struct LockScreen {
dimensions: Dimensions,
input_password: String,
password_hash: [u8; 64],
lines: [String; 3],
}
impl WindowLike for LockScreen {
@@ -26,9 +25,7 @@ impl WindowLike for LockScreen {
WindowMessage::KeyPress(key_press) => {
if key_press.is_enter() {
//check password
let mut hasher = Blake2b512::new();
hasher.update((self.input_password.clone() + "salt?sorrycryptographers").as_bytes());
if hasher.finalize() == PASSWORD_HASH.into() {
if Sha512::hash((self.input_password.clone() + "salt?sorrycryptographers!1!").as_bytes()).to_byte_array() == self.password_hash {
WindowMessageResponse::Request(WindowManagerRequest::Unlock)
} else {
self.input_password = String::new();
@@ -54,9 +51,11 @@ impl WindowLike for LockScreen {
fn draw(&self, _theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
vec![
DrawInstructions::Rect([0, 0], self.dimensions, [0, 0, 0]),
DrawInstructions::Text([4, 4], vec!["nimbus-roman".to_string()], "The bulldozer outside the kitchen window was quite a big one.".to_string(), [255, 255, 255], [0, 0, 0], None, None),
DrawInstructions::Text([4, 4 + 16], vec!["nimbus-roman".to_string()], "\"Yellow,\" he thought, and stomped off back to his bedroom to get dressed.".to_string(), [255, 255, 255], [0, 0, 0], None, None),
DrawInstructions::Text([4, 4 + 16 * 2], vec!["nimbus-roman".to_string()], "He stared at it.".to_string(), [255, 255, 255], [0, 0, 0], None, None),
DrawInstructions::Text([4, 4], vec!["nimbus-roman".to_string()], self.lines[0].clone(), [255, 255, 255], [0, 0, 0], None, None),
//He is my brother.
DrawInstructions::Text([4, 4 + 16], vec!["nimbus-roman".to_string()], self.lines[1].clone(), [255, 255, 255], [0, 0, 0], None, None),
//But I must kill him and keep strong to do it.
DrawInstructions::Text([4, 4 + 16 * 2], vec!["nimbus-roman".to_string()], self.lines[2].clone(), [255, 255, 255], [0, 0, 0], None, None),
DrawInstructions::Text([4, 4 + 16 * 3], vec!["nimbus-roman".to_string()], "Password: ".to_string(), [255, 255, 255], [0, 0, 0], None, None),
DrawInstructions::Text([80, 4 + 16 * 3], vec!["nimbus-roman".to_string()], "*".repeat(self.input_password.len()), [255, 255, 255], [0, 0, 0], None, None),
]
@@ -73,10 +72,25 @@ impl WindowLike for LockScreen {
}
impl LockScreen {
pub fn new() -> Self {
pub fn new(password_hash: [u8; 64]) -> Self {
let possible_lines = [
[
"\"He took about forty pounds,\" the old man said aloud.".to_string(),
"He took my harpoon too and all the rope, he thought, and now my fish bleeds again and there will be others.".to_string(),
"He did not like to look at the fish anymore since it had been mutilated.".to_string()
],
[
"The bulldozer outside the kitchen window was quite a big one.".to_string(),
"\"Yellow,\" he thought, and stomped off back to his bedroom to get dressed.".to_string(),
"He stared at it.".to_string()
],
];
let rand_index = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as usize % possible_lines.len();
Self {
dimensions: [0, 0],
input_password: String::new(),
password_hash,
lines: possible_lines[rand_index].clone(),
}
}
}

View File

@@ -7,8 +7,8 @@ use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManager
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::utils::point_inside;
use crate::components::Component;
use crate::components::press_button::PressButton;
use ming_wm_lib::components::Component;
use ming_wm_lib::components::press_button::PressButton;
//seems like framebuffer only updates if (real) key is pressed...
//on mobile, volume down button seems to work but is annoying

View File

@@ -7,9 +7,9 @@ use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManager
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::dirs::exe_dir;
use ming_wm_lib::components::Component;
use ming_wm_lib::components::highlight_button::HighlightButton;
use crate::fs::{ ExeWindowInfos, get_all_executable_windows };
use crate::components::Component;
use crate::components::highlight_button::HighlightButton;
static CATEGORIES: [&'static str; 9] = ["About", "Utils", "Games", "Editing", "Files", "Internet", "Misc", "Help", "Lock"];

View File

@@ -6,8 +6,9 @@ use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLik
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType, InfoType, WindowsVec };
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use crate::components::Component;
use crate::components::toggle_button::ToggleButton;
use ming_wm_lib::fonts::measure_text;
use ming_wm_lib::components::Component;
use ming_wm_lib::components::toggle_button::ToggleButton;
const PADDING: usize = 4;
const META_WIDTH: usize = 175; //of the window button
@@ -78,7 +79,25 @@ impl WindowLike for Taskbar {
break;
}
let info = &self.windows_in_workspace[wi];
let name = &info.1;
let max_text_width = META_WIDTH - PADDING * 2;
//horiz_spacing is by default 1 per char, which measure_text doesn't take into account
let to_measure = info.1.clone();
let to_measure_len = to_measure.chars().count();
let name = if measure_text(&["nimbus-roman".to_string()], to_measure).width + to_measure_len > max_text_width {
let mut current = String::new();
for c in info.1.chars() {
//horiz_spacing is 1 by default
let to_measure = current.clone() + &c.to_string() + "...";
let to_measure_len = to_measure.chars().count();
if measure_text(&["nimbus-roman".to_string()], to_measure).width + to_measure_len > max_text_width {
break;
}
current += &c.to_string();
}
current + "..."
} else {
info.1.clone()
};
let mut b = ToggleButton::new(name.to_string() + "-window", [PADDING * 2 + 44 + (META_WIDTH + PADDING) * wi, PADDING], [META_WIDTH, self.dimensions[1] - (PADDING * 2)], name.to_string(), TaskbarMessage::Nothing, TaskbarMessage::Nothing);
b.inverted = info.0 == self.focused_id;
instructions.extend(b.draw(theme_info));
@@ -123,5 +142,3 @@ impl Taskbar {
}
}
}

View File

@@ -4,9 +4,7 @@ use std::vec::Vec;
use bmp_rust::bmp::BMP;
use ming_wm_lib::framebuffer_types::*;
use crate::fs::get_font_char_from_fonts;
type FontChar = (char, Vec<Vec<u8>>, u8);
use ming_wm_lib::fonts::{ CachedFontCharGetter, FontCharInfo };
fn color_with_alpha(color: RGBColor, bg_color: RGBColor, alpha: u8) -> RGBColor {
/*let factor: f32 = alpha as f32 / 255.0;
@@ -47,6 +45,7 @@ pub struct FramebufferInfo {
//currently doesn't check if writing onto next line accidentally
pub struct FramebufferWriter {
info: FramebufferInfo,
fc_getter: CachedFontCharGetter,
buffer: Vec<u8>,
saved_buffer: Option<Vec<u8>>,
rotate_buffer: Option<Vec<u8>>,
@@ -57,6 +56,7 @@ impl FramebufferWriter {
pub fn new(grayscale: bool) -> Self {
Self {
info: Default::default(),
fc_getter: CachedFontCharGetter::new(128), //an arbitrary high-ish number for max cache size
buffer: Vec::new(),
saved_buffer: None,
rotate_buffer: None,
@@ -108,6 +108,7 @@ impl FramebufferWriter {
.copy_from_slice(&color[..]);
}
//straight horizontal line
fn _draw_line(&mut self, start_pos: usize, bytes: &[u8]) {
self.buffer[start_pos..(start_pos + bytes.len())]
.copy_from_slice(bytes);
@@ -127,12 +128,11 @@ impl FramebufferWriter {
}
}
pub fn draw_char(&mut self, top_left: Point, char_info: &FontChar, color: RGBColor, bg_color: RGBColor) {
pub fn draw_char(&mut self, top_left: Point, char_info: &FontCharInfo, color: RGBColor, bg_color: RGBColor) {
let mut start_pos;
for row in 0..char_info.1.len() {
//char_info.2 is vertical offset
start_pos = ((top_left[1] + row + char_info.2 as usize) * self.info.stride + top_left[0]) * self.info.bytes_per_pixel;
for col in &char_info.1[row] {
for row in 0..char_info.height {
start_pos = ((top_left[1] + row + char_info.top_offset as usize) * self.info.stride + top_left[0]) * self.info.bytes_per_pixel;
for col in &char_info.data[row] {
if col > &0 {
if start_pos + 3 < self.info.byte_len {
self._draw_pixel(start_pos, color_with_alpha(color, bg_color, *col));
@@ -147,8 +147,10 @@ impl FramebufferWriter {
pub fn draw_pixel(&mut self, point: Point, color: RGBColor) {
let start_pos = (point[1] * self.info.stride + point[0]) * self.info.bytes_per_pixel;
if self.info.byte_len > start_pos {
self._draw_pixel(start_pos, color);
}
}
//shapes
@@ -223,8 +225,8 @@ impl FramebufferWriter {
if c == ' ' {
top_left[0] += mono_width.unwrap_or(5) as usize;
} else {
let char_info = get_font_char_from_fonts(&fonts, c);
let char_width = char_info.1[0].len();
let char_info = self.fc_getter.get(&fonts, c);
let char_width = char_info.width;
let add_after: usize;
if let Some(mono_width) = mono_width {
let mono_width = mono_width as usize;
@@ -244,6 +246,34 @@ impl FramebufferWriter {
}
}
//line
pub fn draw_line(&mut self, start: Point, end: Point, width: usize, color: RGBColor) {
//leftmost point
let lm;
let rm;
if start[0] < end[0] || (start[0] == end[0] && end[1] > start[1]) {
lm = start;
rm = end;
} else {
lm = end;
rm = start;
};
let dx = (rm[0] - lm[0]) as f64; //will not be negative
let dy = rm[1] as f64 - lm[1] as f64; //surely no point will be big enough to lose data here?
let use_x = dy.abs() < dx;
//slope
let m = if use_x { dy / dx } else { dx / dy };
for ci in 0..if use_x { dx as usize } else { dy.abs() as usize } {
let i = if !use_x && dy < 0.0 { -(ci as f64) } else { ci as f64 };
let ix = if use_x { i } else { (m * i).round() };
let iy = if use_x { (m * i).round() } else { i };
for j in 0..width {
self.draw_pixel([(lm[0] as f64 + ix) as usize + j, (lm[1] as f64 + iy) as usize], color);
}
}
}
//bmps
//reverse is workaround for when my bmp lib returns rgba instead of bgra

29
wm/src/fs.rs Normal file
View File

@@ -0,0 +1,29 @@
use std::fs::read_dir;
use std::collections::HashMap;
use ming_wm_lib::dirs;
use ming_wm_lib::utils::get_rest_of_split;
//Category, Vec<Display name, file name>
pub type ExeWindowInfos = HashMap<String, Vec<(String, String)>>;
//well, doesn't actually look to see if its executable. Just if it contains a _ and has no file extension, and is a file
pub fn get_all_executable_windows() -> ExeWindowInfos {
let mut exes = HashMap::new();
for entry in read_dir(dirs::exe_dir(None)).unwrap() {
let pb = entry.unwrap().path();
if pb.is_file() && pb.extension().is_none() {
let parts = pb.file_stem().unwrap().to_string_lossy().to_string();
let mut parts = parts.split('_');
let category = parts.next().unwrap();
let display = get_rest_of_split(&mut parts, Some(" "));
let file_name = pb.file_name().unwrap().to_string_lossy().to_string();
if display != String::new() && category.starts_with("ming") {
let pair = (display, file_name);
exes.entry(category.to_string()).and_modify(|v: &mut Vec<(String, String)>| (*v).push(pair.clone())).or_insert(vec![pair]);
}
}
}
exes
}

View File

@@ -1,6 +1,7 @@
pub use linux;
pub mod framebuffer;
pub mod window_manager;
pub mod components;
pub mod fs;
mod proxy_window_like;
mod essential;

View File

@@ -24,7 +24,6 @@ use crate::essential::start_menu::StartMenu;
use crate::essential::about::About;
use crate::essential::help::Help;
use crate::essential::onscreen_keyboard::OnscreenKeyboard;
//use crate::logging::log;
//todo: a lot of the usize should be changed to u16
@@ -40,6 +39,7 @@ struct WindowLikeInfo {
id: usize,
window_like: WindowBox,
top_left: Point,
old_top_left: Point,
dimensions: Dimensions,
workspace: Workspace,
fullscreen: bool,
@@ -51,6 +51,7 @@ impl fmt::Debug for WindowLikeInfo {
}
}
pub struct WindowManager {
writer: RefCell<FramebufferWriter>,
rotate: bool,
@@ -65,12 +66,14 @@ pub struct WindowManager {
current_workspace: u8,
framebuffer: Framebuffer,
clipboard: Option<String>,
version: String,
password_hash: [u8; 64],
}
//1 is up, 2 is down
impl WindowManager {
pub fn new(writer: FramebufferWriter, framebuffer: Framebuffer, dimensions: Dimensions, rotate: bool, grayscale: bool) -> Self {
pub fn new(writer: FramebufferWriter, framebuffer: Framebuffer, dimensions: Dimensions, rotate: bool, grayscale: bool, version: String, password_hash: [u8; 64]) -> Self {
//println!("bg: {}x{}", dimensions[0], dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT);
let mut wm = WindowManager {
writer: RefCell::new(writer),
@@ -86,6 +89,8 @@ impl WindowManager {
current_workspace: 0,
framebuffer,
clipboard: None,
version,
password_hash,
};
wm.lock();
wm.change_theme();
@@ -103,6 +108,7 @@ impl WindowManager {
id,
window_like,
top_left,
old_top_left: top_left,
dimensions,
workspace: if subtype == WindowLikeType::Window {
Workspace::Workspace(self.current_workspace)
@@ -136,7 +142,7 @@ impl WindowManager {
fn lock(&mut self) {
self.locked = true;
self.window_infos = Vec::new();
self.add_window_like(Box::new(LockScreen::new()), [0, 0], None);
self.add_window_like(Box::new(LockScreen::new(self.password_hash)), [0, 0], None);
}
fn unlock(&mut self) {
@@ -456,17 +462,20 @@ impl WindowManager {
let window_like = &self.window_infos[focused_index].window_like;
if window_like.subtype() == WindowLikeType::Window && window_like.resizable() {
//toggle fullscreen
self.window_infos[focused_index].fullscreen ^= true;
let window_info = &mut self.window_infos[focused_index];
window_info.fullscreen ^= true;
//todo: send message to window about resize
let new_dimensions;
if self.window_infos[focused_index].fullscreen {
if window_info.fullscreen {
new_dimensions = [self.dimensions[0], self.dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT];
self.window_infos[focused_index].top_left = [0, INDICATOR_HEIGHT];
redraw_ids = Some(vec![self.window_infos[focused_index].id]);
window_info.old_top_left = window_info.top_left;
window_info.top_left = [0, INDICATOR_HEIGHT];
redraw_ids = Some(vec![window_info.id]);
} else {
new_dimensions = self.window_infos[focused_index].dimensions;
window_info.top_left = window_info.old_top_left;
new_dimensions = window_info.dimensions;
}
self.window_infos[focused_index].window_like.handle_message(WindowMessage::ChangeDimensions([new_dimensions[0], new_dimensions[1] - WINDOW_TOP_HEIGHT]));
window_info.window_like.handle_message(WindowMessage::ChangeDimensions([new_dimensions[0], new_dimensions[1] - WINDOW_TOP_HEIGHT]));
press_response = WindowMessageResponse::JustRedraw;
}
}
@@ -588,7 +597,7 @@ impl WindowManager {
}
let w: Option<WindowBox> = match w.as_str() {
"StartMenu" => Some(Box::new(StartMenu::new())),
"About" => Some(Box::new(About::new())),
"About" => Some(Box::new(About::new(self.version.clone()))),
"Help" => Some(Box::new(Help::new())),
_ => Some(Box::new(ProxyWindowLike::new(&w))),
};
@@ -642,6 +651,13 @@ impl WindowManager {
[top_left[0], top_left[1] + if is_window { WINDOW_TOP_HEIGHT } else { 0 }]
}
fn prevent_overflow_dimensions(dimensions: Dimensions, window_dimensions: Dimensions, top_left: Point) -> Dimensions {
[
min(dimensions[0], window_dimensions[0] - top_left[0]),
min(dimensions[1], window_dimensions[1] - top_left[1]),
]
}
//another issue with a huge vector of draw instructions; it takes up heap memory
pub fn draw(&mut self, maybe_redraw_ids: Option<Vec<usize>>, use_saved_buffer: bool) {
let theme_info = get_theme_info(&self.theme).unwrap();
@@ -688,6 +704,7 @@ impl WindowManager {
DrawInstructions::Text(top_left, fonts, text, color, bg_color, horiz_spacing, mono_width) => DrawInstructions::Text(WindowManager::get_true_top_left(top_left, is_window), fonts.clone(), text.clone(), *color, *bg_color, *horiz_spacing, *mono_width),
DrawInstructions::Bmp(top_left, path, reverse) => DrawInstructions::Bmp(WindowManager::get_true_top_left(top_left, is_window), path.to_string(), *reverse),
DrawInstructions::Gradient(top_left, dimensions, start_color, end_color, steps) => DrawInstructions::Gradient(WindowManager::get_true_top_left(top_left, is_window), *dimensions, *start_color, *end_color, *steps),
DrawInstructions::Line(start, end, width, color) => DrawInstructions::Line(WindowManager::get_true_top_left(start, is_window), WindowManager::get_true_top_left(end, is_window), *width, *color),
}
}).collect();
//draw window background
@@ -723,10 +740,7 @@ impl WindowManager {
match instruction {
DrawInstructions::Rect(top_left, dimensions, color) => {
//try and prevent overflows out of the window
let true_dimensions = [
min(dimensions[0], window_dimensions[0] - top_left[0]),
min(dimensions[1], window_dimensions[1] - top_left[1]),
];
let true_dimensions = WindowManager::prevent_overflow_dimensions(dimensions, window_dimensions, top_left);
window_writer.draw_rect(top_left, true_dimensions, color);
},
DrawInstructions::Circle(centre, radius, color) => {
@@ -740,7 +754,11 @@ impl WindowManager {
window_writer.draw_bmp(top_left, path, reverse);
},
DrawInstructions::Gradient(top_left, dimensions, start_color, end_color, steps) => {
window_writer.draw_gradient(top_left, dimensions, start_color, end_color, steps);
let true_dimensions = WindowManager::prevent_overflow_dimensions(dimensions, window_dimensions, top_left);
window_writer.draw_gradient(top_left, true_dimensions, start_color, end_color, steps);
},
DrawInstructions::Line(start, end, width, color) => {
window_writer.draw_line(start, end, width, color);
},
}
}