Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
497beb7bb0 | ||
|
|
40f6795163 | ||
|
|
10daa9982b | ||
|
|
08c2358bdc | ||
|
|
2c4455f623 | ||
|
|
ec5cba13c8 | ||
|
|
4c4c9a1b35 | ||
|
|
6cc8e06e89 | ||
|
|
dfe065d26a | ||
|
|
c1afd3f33e | ||
|
|
724ffbd494 | ||
|
|
7c6a7d6b6d | ||
|
|
667b4cd2d9 | ||
|
|
c5a41244b4 | ||
|
|
d32b82a2bb |
43
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ming-wm"
|
name = "ming-wm"
|
||||||
version = "1.0.2"
|
version = "1.2.2"
|
||||||
repository = "https://github.com/stjet/ming-wm"
|
repository = "https://github.com/stjet/ming-wm"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@@ -9,38 +9,47 @@ default-run = "ming"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["linux"]
|
members = [ "wm", "linux" ]
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
len_zero = "allow"
|
||||||
|
comparison_to_empty = "allow"
|
||||||
|
manual_saturating_arithmetic = "allow"
|
||||||
|
result_unit_err = "allow"
|
||||||
|
needless_borrow = "allow"
|
||||||
|
needless_borrows_for_generic_args = "allow"
|
||||||
|
redundant_static_lifetimes = "allow"
|
||||||
|
collapsible_else_if = "allow"
|
||||||
|
too_many_arguments = "allow"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bmp-rust = "0.5.0"
|
bmp-rust = "0.5.0"
|
||||||
blake2 = { version = "0.10.6", default-features = false }
|
bitcoin_hashes = { version = "0.16.0", default-features = false }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ming-wm-lib = { path = "ming-wm-lib" }
|
ming-wm-lib = { path = "ming-wm-lib" }
|
||||||
blake2 = { version = "0.10.6", default-features = false }
|
wm = { path = "wm", optional = true }
|
||||||
linux = { path = "linux" }
|
linux = { path = "linux", optional = true }
|
||||||
termion = { version = "4.0.3", optional = true }
|
|
||||||
rodio = { version = "0.19.0", default-features = false, features = [ "flac", "mp3", "symphonia-vorbis", "wav" ], 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 }
|
id3 = { version = "1.10.0", optional = true }
|
||||||
mp4ameta = { version = "0.11.0", optional = true }
|
mp4ameta = { version = "0.11.0", optional = true }
|
||||||
metaflac = { version = "0.2.5", optional = true }
|
metaflac = { version = "0.2.5", optional = true }
|
||||||
bmp-rust = "0.5.0"
|
|
||||||
pty-process = { version = "0.5.1", optional = true }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "main", "terminal" ]
|
default = [ "wm", "terminal" ]
|
||||||
main = [ "termion" ]
|
terminal = [ "linux" ]
|
||||||
terminal = [ "pty-process" ]
|
audio_player = [ "id3", "mp4ameta", "metaflac", "rodio" ]
|
||||||
audio_player = [ "id3", "mp4ameta", "metaflac", "rand", "rodio" ]
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "ming"
|
name = "ming"
|
||||||
path = "src/bin/main.rs"
|
path = "src/bin/wm.rs"
|
||||||
required-features = [ "main" ]
|
required-features = [ "wm" ]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "mingFiles_Audio_Player"
|
name = "mingFiles_Audio_Player"
|
||||||
@@ -67,3 +76,7 @@ path = "src/bin/malvim.rs"
|
|||||||
[[bin]]
|
[[bin]]
|
||||||
name = "mingGames_Reversi"
|
name = "mingGames_Reversi"
|
||||||
path = "src/bin/reversi.rs"
|
path = "src/bin/reversi.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "mingUtils_Draw"
|
||||||
|
path = "src/bin/draw.rs"
|
||||||
|
|||||||
16
README.md
@@ -1,8 +1,10 @@
|
|||||||
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.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
https://github.com/user-attachments/assets/5a598287-27d5-40f2-9b5f-9c1e67cab2f5
|
||||||
|
|
||||||
The [Koxinga web browser](https://github.com/stjet/koxinga) can be separately installed.
|
The [Koxinga web browser](https://github.com/stjet/koxinga) can be separately installed.
|
||||||
|
|
||||||

|

|
||||||
@@ -49,9 +51,9 @@ Usage for most of the included windows and window-likes are included in `docs/wi
|
|||||||
|
|
||||||
## Running on Mobile Linux
|
## 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
|
ming touch
|
||||||
@@ -73,12 +75,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.
|
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
|
## 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.
|
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!
|
Obviously, don't run the executable with `sudo` or `doas`, or as the root user!
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
BIN
bmps/arhants1440x842.bmp
Normal file
|
After Width: | Height: | Size: 4.6 MiB |
|
Before Width: | Height: | Size: 4.6 MiB |
|
Before Width: | Height: | Size: 70 B After Width: | Height: | Size: 70 B |
|
Before Width: | Height: | Size: 250 B After Width: | Height: | Size: 250 B |
|
Before Width: | Height: | Size: 250 B After Width: | Height: | Size: 250 B |
|
Before Width: | Height: | Size: 582 B After Width: | Height: | Size: 534 B |
|
Before Width: | Height: | Size: 630 B After Width: | Height: | Size: 534 B |
|
Before Width: | Height: | Size: 470 B After Width: | Height: | Size: 374 B |
|
Before Width: | Height: | Size: 438 B After Width: | Height: | Size: 374 B |
|
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 378 B |
BIN
bmps/shippori-mincho/ー6.bmp
Normal file
|
After Width: | Height: | Size: 94 B |
BIN
bmps/shippori-mincho/中0.bmp
Normal file
|
After Width: | Height: | Size: 534 B |
BIN
bmps/shippori-mincho/合0.bmp
Normal file
|
After Width: | Height: | Size: 630 B |
17
build.rs
@@ -4,7 +4,7 @@ use std::env;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use blake2::{ Blake2b512, Digest };
|
use bitcoin_hashes::Sha512;
|
||||||
use bmp_rust::bmp::BMP;
|
use bmp_rust::bmp::BMP;
|
||||||
|
|
||||||
fn font_chars_to_alphas(dir: &str) {
|
fn font_chars_to_alphas(dir: &str) {
|
||||||
@@ -15,8 +15,7 @@ fn font_chars_to_alphas(dir: &str) {
|
|||||||
if file_name.len() < 2 {
|
if file_name.len() < 2 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if file_name[1] == "bmp" {
|
if file_name[1] == "bmp" && !path.is_dir() {
|
||||||
if !path.is_dir() {
|
|
||||||
let mut ch: Vec<Vec<String>> = Vec::new();
|
let mut ch: Vec<Vec<String>> = Vec::new();
|
||||||
let b = BMP::new_from_file(&path.clone().into_os_string().into_string().unwrap()).unwrap();
|
let b = BMP::new_from_file(&path.clone().into_os_string().into_string().unwrap()).unwrap();
|
||||||
let dib_header = b.get_dib_header().unwrap();
|
let dib_header = b.get_dib_header().unwrap();
|
||||||
@@ -36,7 +35,7 @@ fn font_chars_to_alphas(dir: &str) {
|
|||||||
ch.push(row);
|
ch.push(row);
|
||||||
}
|
}
|
||||||
let ch: Vec<String> = ch.into_iter().map(|row| {
|
let ch: Vec<String> = ch.into_iter().map(|row| {
|
||||||
row.join(",")
|
row.join(",").replace(",,,,", ":").replace(",,,", ";").replace(",,", ".")
|
||||||
}).collect();
|
}).collect();
|
||||||
let chars: Vec<char> = file_name[0].chars().collect();
|
let chars: Vec<char> = file_name[0].chars().collect();
|
||||||
File::create(dir.to_string() + "/" + &chars[0].to_string() + ".alpha").unwrap().write_all(
|
File::create(dir.to_string() + "/" + &chars[0].to_string() + ".alpha").unwrap().write_all(
|
||||||
@@ -45,16 +44,14 @@ fn font_chars_to_alphas(dir: &str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
//hash + "salt" password and add to build
|
//hash + "salt" password and add to build
|
||||||
let password = read_to_string("password.env").unwrap_or("password".to_string()).replace("\n", "") + "salt?sorrycryptographers";
|
let password = read_to_string("password.env").unwrap_or("password".to_string()).replace("\n", "") + "salt?sorrycryptographers!1!";
|
||||||
let mut hasher = Blake2b512::new();
|
let hash = Sha512::hash(password.as_bytes()).to_byte_array();
|
||||||
hasher.update(password.as_bytes());
|
|
||||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||||
let dest_path = Path::new(&out_dir).join("password.rs");
|
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
|
//process bmps
|
||||||
for entry in read_dir("./bmps").unwrap() {
|
for entry in read_dir("./bmps").unwrap() {
|
||||||
let path = entry.unwrap().path();
|
let path = entry.unwrap().path();
|
||||||
@@ -64,7 +61,9 @@ fn main() {
|
|||||||
}
|
}
|
||||||
//copy bmp folders to target
|
//copy bmp folders to target
|
||||||
let profile = env::var_os("PROFILE").unwrap().to_string_lossy().to_string();
|
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();
|
Command::new("cp").arg("-r").arg("./bmps").arg(format!("./target/{}/ming_bmps", profile)).output().unwrap();
|
||||||
//also copy the docs folder
|
//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();
|
Command::new("cp").arg("-r").arg("./docs").arg(format!("./target/{}/ming_docs", profile)).output().unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 122 KiB |
@@ -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.
|
Expect to see more dependencies in Cargo.toml eliminated soon.
|
||||||
|
|
||||||
PS:
|
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
|
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!
|
||||||
|
|||||||
3
docs/system/integration.md
Normal 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.
|
||||||
@@ -16,5 +16,9 @@
|
|||||||
- Alt+J: Move window to bottom edge
|
- Alt+J: Move window to bottom edge
|
||||||
- Alt+K: Move window to top edge
|
- Alt+K: Move window to top edge
|
||||||
- Alt+L: Move window to right edge
|
- Alt+L: Move window to right edge
|
||||||
|
- Alt+n: Expand window width
|
||||||
|
- Alt+m: Expand window height
|
||||||
|
- Alt+N: Shrink window width
|
||||||
|
- Alt+M: Shrink window height
|
||||||
- Alt+1, Alt+2, ..., Alt+[n], ..., Alt+9: Switch to workspace [n]
|
- Alt+1, Alt+2, ..., Alt+[n], ..., Alt+9: Switch to workspace [n]
|
||||||
- Alt+shift+1, Alt+shift+2, ..., Alt+shift+[n], ..., Alt+shift+9: Move window to workspace [n]
|
- Alt+shift+1, Alt+shift+2, ..., Alt+shift+[n], ..., Alt+shift+9: Move window to workspace [n]
|
||||||
|
|||||||
@@ -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.
|
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!
|
## Hello, World!
|
||||||
|
|
||||||
A minimal example using `ming-wm-lib`.
|
A minimal example using `ming-wm-lib`.
|
||||||
|
|||||||
@@ -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.
|
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
|
## Playlists
|
||||||
|
|
||||||
Example playlist file:
|
Example playlist file:
|
||||||
|
|||||||
15
docs/window-likes/draw.md
Normal 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
|
||||||
@@ -12,26 +12,35 @@ It is probably best to read a Vim tutorial for the basics. All supportd keystrok
|
|||||||
- `t[abe] <file>`, `[tab]n`, `[tab]p`
|
- `t[abe] <file>`, `[tab]n`, `[tab]p`
|
||||||
- `q[uit]`
|
- `q[uit]`
|
||||||
- `w[rite]`
|
- `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
|
### Supported in Normal Mode
|
||||||
|
|
||||||
- `:`
|
- `:`
|
||||||
- `i`
|
- `i`
|
||||||
|
- `o`, `O`
|
||||||
- `A`
|
- `A`
|
||||||
- `r`
|
- `r`
|
||||||
- `dd`
|
- `dd`
|
||||||
- `dw`
|
- `<number>dd`
|
||||||
|
- `dw` (`dw` is not identical to vim's behaviour), `dW`
|
||||||
|
- `d$`
|
||||||
- `G`
|
- `G`
|
||||||
- `gg`
|
- `gg`
|
||||||
- `<number>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`
|
- `x`
|
||||||
- `h` (or left arrow), `j` (or down arrow), `k` (or up arrow), `l` (or right arrow)
|
- `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`
|
- `<num>h`, `<num>j` (or down arrow), `<num>k` (or up arrow), `<num>l`
|
||||||
- `0`, `^`, `$`
|
- `0`, `^`, `$`
|
||||||
|
- `%`
|
||||||
|
|
||||||
### Malvim Specific
|
### Malvim Specific
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ To get sudo to read from stdin, the `-S` option will need to be used (eg, `sudo
|
|||||||
|
|
||||||
## Copy / Paste
|
## 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
|
## Notes
|
||||||
|
|
||||||
|
|||||||
1
install
@@ -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_File_Explorer /usr/local/bin/mingFiles_File_Explorer
|
||||||
cp ./target/release/mingFiles_Audio_Player /usr/local/bin/mingFiles_Audio_Player
|
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/mingEditing_Malvim /usr/local/bin/mingEditing_Malvim
|
||||||
|
cp ./target/release/mingUtils_Draw /usr/local/bin/mingUtils_Draw
|
||||||
|
|||||||
@@ -4,5 +4,18 @@ version = "0.1.0"
|
|||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
len_zero = "allow"
|
||||||
|
comparison_to_empty = "allow"
|
||||||
|
manual_saturating_arithmetic = "allow"
|
||||||
|
result_unit_err = "allow"
|
||||||
|
needless_borrow = "allow"
|
||||||
|
needless_borrows_for_generic_args = "allow"
|
||||||
|
redundant_static_lifetimes = "allow"
|
||||||
|
collapsible_else_if = "allow"
|
||||||
|
too_many_arguments = "allow"
|
||||||
|
useless_conversion = "allow"
|
||||||
|
unnecessary_mut_passed = "allow"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|||||||
108
linux/src/input.rs
Normal 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
@@ -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 let Ok(n) = n {
|
||||||
|
//Alt+<char> sends Esc+<char>
|
||||||
|
Key::Alt(char::from(n))
|
||||||
|
} else {
|
||||||
|
Key::Esc
|
||||||
|
}
|
||||||
|
},
|
||||||
|
127 => {
|
||||||
|
Key::Backspace
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
Key::Char(char::from(first))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,5 @@
|
|||||||
pub mod fb;
|
pub mod fb;
|
||||||
|
pub mod raw;
|
||||||
|
pub mod keys;
|
||||||
|
pub mod pty;
|
||||||
|
pub mod input;
|
||||||
|
|||||||
55
linux/src/pty.rs
Normal 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
@@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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_File_Explorer ~/.local/bin/mingFiles_File_Explorer
|
||||||
cp ./target/release/mingFiles_Audio_Player ~/.local/bin/mingFiles_Audio_Player
|
cp ./target/release/mingFiles_Audio_Player ~/.local/bin/mingFiles_Audio_Player
|
||||||
cp ./target/release/mingEditing_Malvim ~/.local/bin/mingEditing_Malvim
|
cp ./target/release/mingEditing_Malvim ~/.local/bin/mingEditing_Malvim
|
||||||
|
cp ./target/release/mingUtils_Draw ~/.local/bin/mingUtils_Draw
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ming-wm-lib"
|
name = "ming-wm-lib"
|
||||||
version = "0.1.5"
|
version = "0.2.3"
|
||||||
repository = "https://github.com/stjet/ming-wm"
|
repository = "https://github.com/stjet/ming-wm"
|
||||||
description = "library for building windows for ming-wm in rust"
|
description = "library for building windows for ming-wm in rust"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
len_zero = "allow"
|
||||||
|
comparison_to_empty = "allow"
|
||||||
|
manual_saturating_arithmetic = "allow"
|
||||||
|
result_unit_err = "allow"
|
||||||
|
needless_borrow = "allow"
|
||||||
|
needless_borrows_for_generic_args = "allow"
|
||||||
|
redundant_static_lifetimes = "allow"
|
||||||
|
collapsible_else_if = "allow"
|
||||||
|
too_many_arguments = "allow"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ use std::vec;
|
|||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
use crate::components::Component;
|
use crate::components::Component;
|
||||||
use ming_wm_lib::framebuffer_types::{ Dimensions, Point };
|
use crate::framebuffer_types::{ Dimensions, Point };
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use crate::themes::ThemeInfo;
|
||||||
use ming_wm_lib::messages::WindowMessage;
|
use crate::messages::WindowMessage;
|
||||||
use ming_wm_lib::window_manager_types::DrawInstructions;
|
use crate::window_manager_types::DrawInstructions;
|
||||||
|
|
||||||
pub struct HighlightButton<T> {
|
pub struct HighlightButton<T> {
|
||||||
name_: String,
|
name_: String,
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use crate::themes::ThemeInfo;
|
||||||
use ming_wm_lib::messages::WindowMessage;
|
use crate::messages::WindowMessage;
|
||||||
use ming_wm_lib::window_manager_types::DrawInstructions;
|
use crate::window_manager_types::DrawInstructions;
|
||||||
|
|
||||||
pub mod toggle_button;
|
pub mod toggle_button;
|
||||||
pub mod highlight_button;
|
pub mod highlight_button;
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
use std::vec;
|
use std::vec;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
|
use crate::framebuffer_types::{ Dimensions, Point };
|
||||||
use ming_wm_lib::framebuffer_types::{ Dimensions, Point };
|
use crate::themes::ThemeInfo;
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use crate::messages::WindowMessage;
|
||||||
use ming_wm_lib::messages::WindowMessage;
|
use crate::window_manager_types::DrawInstructions;
|
||||||
use ming_wm_lib::window_manager_types::DrawInstructions;
|
use crate::utils::calc_actual_lines;
|
||||||
use ming_wm_lib::utils::calc_actual_lines;
|
|
||||||
use crate::components::Component;
|
use crate::components::Component;
|
||||||
|
|
||||||
const MONO_WIDTH: u8 = 10;
|
const MONO_WIDTH: u8 = 10;
|
||||||
@@ -2,10 +2,10 @@ use std::vec;
|
|||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
use crate::components::Component;
|
use crate::components::Component;
|
||||||
use ming_wm_lib::framebuffer_types::{ Dimensions, Point };
|
use crate::framebuffer_types::{ Dimensions, Point };
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use crate::themes::ThemeInfo;
|
||||||
use ming_wm_lib::messages::WindowMessage;
|
use crate::messages::WindowMessage;
|
||||||
use ming_wm_lib::window_manager_types::DrawInstructions;
|
use crate::window_manager_types::DrawInstructions;
|
||||||
|
|
||||||
const MONO_WIDTH: u8 = 10;
|
const MONO_WIDTH: u8 = 10;
|
||||||
|
|
||||||
@@ -2,10 +2,10 @@ use std::vec;
|
|||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
use crate::components::Component;
|
use crate::components::Component;
|
||||||
use ming_wm_lib::framebuffer_types::{ Dimensions, Point };
|
use crate::framebuffer_types::{ Dimensions, Point };
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use crate::themes::ThemeInfo;
|
||||||
use ming_wm_lib::messages::WindowMessage;
|
use crate::messages::WindowMessage;
|
||||||
use ming_wm_lib::window_manager_types::DrawInstructions;
|
use crate::window_manager_types::DrawInstructions;
|
||||||
|
|
||||||
pub struct ToggleButton<T> {
|
pub struct ToggleButton<T> {
|
||||||
name_: String,
|
name_: String,
|
||||||
126
ming-wm-lib/src/fonts.rs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
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 lines.iter().skip(1) {
|
||||||
|
//.unwrap_or(0) is important because zeroes are just empty
|
||||||
|
ch.push(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,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn measure_text(fonts: &[String], text: &str, horiz_spacing: Option<usize>) -> 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 + horiz_spacing.unwrap_or(1);
|
||||||
|
}
|
||||||
|
width -= horiz_spacing.unwrap_or(1);
|
||||||
|
MeasureInfo {
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn measure_text_with_cache(fc_getter: &mut CachedFontCharGetter, fonts: &[String], text: &str, horiz_spacing: Option<usize>) -> MeasureInfo {
|
||||||
|
let mut height = 0;
|
||||||
|
let mut width = 0;
|
||||||
|
for c in text.chars() {
|
||||||
|
let i = fc_getter.get(fonts, c);
|
||||||
|
let c_height = i.top_offset as usize + i.height;
|
||||||
|
if c_height > height {
|
||||||
|
height = c_height;
|
||||||
|
}
|
||||||
|
width += i.width + horiz_spacing.unwrap_or(1);
|
||||||
|
}
|
||||||
|
width -= horiz_spacing.unwrap_or(1);
|
||||||
|
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 {
|
||||||
|
Self {
|
||||||
|
max_cache_size,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,22 +58,24 @@ pub fn listen(mut window_like: impl WindowLike) {
|
|||||||
let arg = &parts.collect::<Vec<&str>>().join(" ");
|
let arg = &parts.collect::<Vec<&str>>().join(" ");
|
||||||
let output = match method {
|
let output = match method {
|
||||||
"handle_message" => {
|
"handle_message" => {
|
||||||
format!("{}", &window_like.handle_message(WindowMessage::deserialize(arg).unwrap()).serialize())
|
//newlines allowed for ClipboardCopy, but represented by the Linear A char
|
||||||
|
window_like.handle_message(WindowMessage::deserialize(arg).unwrap()).serialize().to_string()
|
||||||
},
|
},
|
||||||
"draw" => {
|
"draw" => {
|
||||||
format!("{}", &window_like.draw(&ThemeInfo::deserialize(arg).unwrap()).serialize().replace("\n", ""))
|
//newlines never allowed
|
||||||
|
window_like.draw(&ThemeInfo::deserialize(arg).unwrap()).serialize().replace("\n", "").to_string()
|
||||||
},
|
},
|
||||||
"title" => {
|
"title" => {
|
||||||
format!("{}", window_like.title())
|
window_like.title().to_string()
|
||||||
},
|
},
|
||||||
"resizable" => {
|
"resizable" => {
|
||||||
format!("{}", window_like.resizable())
|
window_like.resizable().to_string()
|
||||||
},
|
},
|
||||||
"subtype" => {
|
"subtype" => {
|
||||||
format!("{}", &window_like.subtype().serialize())
|
window_like.subtype().serialize().to_string()
|
||||||
},
|
},
|
||||||
"ideal_dimensions" => {
|
"ideal_dimensions" => {
|
||||||
format!("{}", &window_like.ideal_dimensions(Dimensions::deserialize(arg).unwrap()).serialize())
|
window_like.ideal_dimensions(Dimensions::deserialize(arg).unwrap()).serialize().to_string()
|
||||||
},
|
},
|
||||||
_ => String::new(),
|
_ => String::new(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ pub mod themes;
|
|||||||
pub mod serialize;
|
pub mod serialize;
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
pub mod ipc;
|
pub mod ipc;
|
||||||
|
pub mod components;
|
||||||
|
pub mod fonts;
|
||||||
pub mod dirs;
|
pub mod dirs;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
|
|||||||
@@ -44,11 +44,7 @@ pub enum WindowMessageResponse {
|
|||||||
|
|
||||||
impl WindowMessageResponse {
|
impl WindowMessageResponse {
|
||||||
pub fn is_key_char_request(&self) -> bool {
|
pub fn is_key_char_request(&self) -> bool {
|
||||||
if let WindowMessageResponse::Request(WindowManagerRequest::DoKeyChar(_)) = self {
|
matches!(self, WindowMessageResponse::Request(WindowManagerRequest::DoKeyChar(_)))
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ fn option_to_string<T: Display>(option: &Option<T>) -> String {
|
|||||||
fn get_color(serialized: &str) -> Result<[u8; 3], ()> {
|
fn get_color(serialized: &str) -> Result<[u8; 3], ()> {
|
||||||
let rgb = serialized.split("\x1F");
|
let rgb = serialized.split("\x1F");
|
||||||
let mut color = [0; 3];
|
let mut color = [0; 3];
|
||||||
let mut c_i = 0;
|
|
||||||
//won't return error if rgb is 0, 1, or 2 elements.
|
//won't return error if rgb is 0, 1, or 2 elements.
|
||||||
//I guess that's okay, since it doesn't panic either
|
//I guess that's okay, since it doesn't panic either
|
||||||
for c in rgb {
|
//c_i is the loop counter. enumerate(), you are awesome
|
||||||
|
for (c_i, c) in rgb.enumerate() {
|
||||||
if c_i == 3 {
|
if c_i == 3 {
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,6 @@ fn get_color(serialized: &str) -> Result<[u8; 3], ()> {
|
|||||||
} else {
|
} else {
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
c_i += 1;
|
|
||||||
}
|
}
|
||||||
Ok(color)
|
Ok(color)
|
||||||
}
|
}
|
||||||
@@ -68,7 +67,7 @@ fn get_two_array(serialized: &str) -> Result<[usize; 2], ()> {
|
|||||||
}
|
}
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
return Ok(a);
|
Ok(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Serializable {
|
pub trait Serializable {
|
||||||
@@ -84,12 +83,11 @@ impl Serializable for ThemeInfo {
|
|||||||
}
|
}
|
||||||
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
||||||
//strip newline at the end
|
//strip newline at the end
|
||||||
let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized };
|
let serialized = serialized.strip_suffix("\n").unwrap_or(serialized);
|
||||||
let mut theme_info: ThemeInfo = Default::default();
|
let mut theme_info: ThemeInfo = Default::default();
|
||||||
let arrays = serialized.split(":");
|
let arrays = serialized.split(":");
|
||||||
let mut a_i = 0;
|
|
||||||
//won't error or panic if less than 9... rest will just be black by default I guess
|
//won't error or panic if less than 9... rest will just be black by default I guess
|
||||||
for a in arrays {
|
for (a_i, a) in arrays.enumerate() {
|
||||||
if a_i == 9 {
|
if a_i == 9 {
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
@@ -127,7 +125,6 @@ impl Serializable for ThemeInfo {
|
|||||||
if a_i == 8 {
|
if a_i == 8 {
|
||||||
return Ok(theme_info);
|
return Ok(theme_info);
|
||||||
}
|
}
|
||||||
a_i += 1;
|
|
||||||
}
|
}
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
@@ -149,7 +146,7 @@ impl Serializable for WindowMessageResponse {
|
|||||||
WindowMessageResponse::Request(req) => {
|
WindowMessageResponse::Request(req) => {
|
||||||
let req = match req {
|
let req = match req {
|
||||||
WindowManagerRequest::OpenWindow(name) => format!("OpenWindow/{}", name),
|
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::CloseStartMenu => "CloseStartMenu".to_string(),
|
||||||
WindowManagerRequest::Unlock => "Unlock".to_string(),
|
WindowManagerRequest::Unlock => "Unlock".to_string(),
|
||||||
WindowManagerRequest::Lock => "Lock".to_string(),
|
WindowManagerRequest::Lock => "Lock".to_string(),
|
||||||
@@ -165,7 +162,7 @@ impl Serializable for WindowMessageResponse {
|
|||||||
}
|
}
|
||||||
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
||||||
//strip newline at the end
|
//strip newline at the end
|
||||||
let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized };
|
let serialized = serialized.strip_suffix("\n").unwrap_or(serialized);
|
||||||
let mut parts = serialized.split("/");
|
let mut parts = serialized.split("/");
|
||||||
match parts.next().unwrap_or("Invalid") {
|
match parts.next().unwrap_or("Invalid") {
|
||||||
"JustRedraw" => Ok(WindowMessageResponse::JustRedraw),
|
"JustRedraw" => Ok(WindowMessageResponse::JustRedraw),
|
||||||
@@ -220,10 +217,11 @@ impl Serializable for DrawInstructions {
|
|||||||
match self {
|
match self {
|
||||||
//use \x1E (record separator) because it won't be in strings. it better fucking not be at least
|
//use \x1E (record separator) because it won't be in strings. it better fucking not be at least
|
||||||
DrawInstructions::Rect(p, d, c) => format!("Rect/{}\x1E{}\x1E{}", array_to_string(p), array_to_string(d), array_to_string(c)),
|
DrawInstructions::Rect(p, d, c) => format!("Rect/{}\x1E{}\x1E{}", array_to_string(p), array_to_string(d), array_to_string(c)),
|
||||||
DrawInstructions::Text(p, vs, s, c1, c2, ou1, ou2) => format!("Text/{}\x1E{}\x1E{}\x1E{}\x1E{}\x1E{}\x1E{}", array_to_string(p), array_to_string(&vs), s, array_to_string(c1), array_to_string(c2), option_to_string(ou1), option_to_string(ou2)),
|
DrawInstructions::Text(p, vs, s, c1, c2, ou1, ou2) => format!("Text/{}\x1E{}\x1E{}\x1E{}\x1E{}\x1E{}\x1E{}", array_to_string(p), array_to_string(vs), s, array_to_string(c1), array_to_string(c2), option_to_string(ou1), option_to_string(ou2)),
|
||||||
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::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::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::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, ()> {
|
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
||||||
@@ -405,6 +403,35 @@ impl Serializable for DrawInstructions {
|
|||||||
let c = get_color(arg.unwrap())?;
|
let c = get_color(arg.unwrap())?;
|
||||||
Ok(DrawInstructions::Circle(p, u.unwrap(), c))
|
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(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,12 +444,12 @@ impl Serializable for DrawInstructionsVec {
|
|||||||
if self.len() == 0 {
|
if self.len() == 0 {
|
||||||
return "empty".to_string();
|
return "empty".to_string();
|
||||||
}
|
}
|
||||||
let collected: Vec<_> = self.into_iter().map(|ins| ins.serialize()).collect();
|
let collected: Vec<_> = self.iter().map(|ins| ins.serialize()).collect();
|
||||||
collected.join("\x1D")
|
collected.join("\x1D")
|
||||||
}
|
}
|
||||||
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
||||||
//strip newline at the end
|
//strip newline at the end
|
||||||
let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized };
|
let serialized = serialized.strip_suffix("\n").unwrap_or(serialized);
|
||||||
if serialized == "empty" {
|
if serialized == "empty" {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
@@ -477,7 +504,7 @@ impl Serializable for WindowLikeType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
||||||
let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized };
|
let serialized = serialized.strip_suffix("\n").unwrap_or(serialized);
|
||||||
match serialized {
|
match serialized {
|
||||||
"LockScreen" => Ok(WindowLikeType::LockScreen),
|
"LockScreen" => Ok(WindowLikeType::LockScreen),
|
||||||
"Window" => Ok(WindowLikeType::Window),
|
"Window" => Ok(WindowLikeType::Window),
|
||||||
@@ -504,7 +531,7 @@ impl Serializable for Dimensions {
|
|||||||
}
|
}
|
||||||
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
||||||
//strip newline at the end
|
//strip newline at the end
|
||||||
let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized };
|
let serialized = serialized.strip_suffix("\n").unwrap_or(serialized);
|
||||||
let d = get_two_array(serialized)?;
|
let d = get_two_array(serialized)?;
|
||||||
Ok(d)
|
Ok(d)
|
||||||
}
|
}
|
||||||
@@ -565,7 +592,7 @@ impl Serializable for WindowMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
||||||
let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized };
|
let serialized = serialized.strip_suffix("\n").unwrap_or(serialized);
|
||||||
let mut parts = serialized.split("/");
|
let mut parts = serialized.split("/");
|
||||||
match parts.next().unwrap_or("Invalid") {
|
match parts.next().unwrap_or("Invalid") {
|
||||||
"Init" => {
|
"Init" => {
|
||||||
@@ -649,7 +676,7 @@ impl Serializable for WindowMessage {
|
|||||||
"FullscreenWindow" => Some(ShortcutType::FullscreenWindow),
|
"FullscreenWindow" => Some(ShortcutType::FullscreenWindow),
|
||||||
"HalfWidthWindow" => Some(ShortcutType::HalfWidthWindow),
|
"HalfWidthWindow" => Some(ShortcutType::HalfWidthWindow),
|
||||||
"ClipboardCopy" => Some(ShortcutType::ClipboardCopy),
|
"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,
|
_ => None,
|
||||||
};
|
};
|
||||||
if let Some(shortcut) = shortcut {
|
if let Some(shortcut) = shortcut {
|
||||||
@@ -674,8 +701,7 @@ impl Serializable for WindowMessage {
|
|||||||
}
|
}
|
||||||
let mut w_tuple: (usize, String) = Default::default();
|
let mut w_tuple: (usize, String) = Default::default();
|
||||||
let mut w_vec = Vec::new();
|
let mut w_vec = Vec::new();
|
||||||
let mut i = 0;
|
for (i, a) in arg2.unwrap().split("\x1F").enumerate() {
|
||||||
for a in arg2.unwrap().split("\x1F") {
|
|
||||||
if i % 2 == 0 {
|
if i % 2 == 0 {
|
||||||
if let Ok(n) = a.parse() {
|
if let Ok(n) = a.parse() {
|
||||||
w_tuple.0 = n;
|
w_tuple.0 = n;
|
||||||
@@ -684,16 +710,15 @@ impl Serializable for WindowMessage {
|
|||||||
w_tuple.1 = a.to_string();
|
w_tuple.1 = a.to_string();
|
||||||
w_vec.push(w_tuple.clone());
|
w_vec.push(w_tuple.clone());
|
||||||
}
|
}
|
||||||
i += 1;
|
|
||||||
}
|
}
|
||||||
let arg2 = parts2.next();
|
let arg2 = parts2.next();
|
||||||
if arg2.is_none() {
|
if arg2.is_none() {
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
if let Ok(n) = arg2.unwrap().parse() {
|
if let Ok(n) = arg2.unwrap().parse() {
|
||||||
return Ok(WindowMessage::Info(InfoType::WindowsInWorkspace(w_vec, n)));
|
Ok(WindowMessage::Info(InfoType::WindowsInWorkspace(w_vec, n)))
|
||||||
} else {
|
} else {
|
||||||
return Err(());
|
Err(())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Focus" => Ok(WindowMessage::Focus),
|
"Focus" => Ok(WindowMessage::Focus),
|
||||||
|
|||||||
@@ -11,15 +11,17 @@ pub enum Themes {
|
|||||||
//Parchment,
|
//Parchment,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Themes {
|
impl std::str::FromStr for Themes {
|
||||||
pub fn from_str(name: &str) -> Option<Self> {
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
||||||
match name {
|
match name {
|
||||||
"Standard" => Some(Themes::Standard),
|
"Standard" => Ok(Themes::Standard),
|
||||||
"Night" => Some(Themes::Night),
|
"Night" => Ok(Themes::Night),
|
||||||
"Industrial" => Some(Themes::Industrial),
|
"Industrial" => Ok(Themes::Industrial),
|
||||||
"Forest" => Some(Themes::Forest),
|
"Forest" => Ok(Themes::Forest),
|
||||||
"Royal" => Some(Themes::Royal),
|
"Royal" => Ok(Themes::Royal),
|
||||||
_ => None,
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,6 +106,6 @@ pub fn get_theme_info(theme: &Themes) -> Option<ThemeInfo> {
|
|||||||
return Some(pair.1);
|
return Some(pair.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return None;
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,26 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::fs::read_dir;
|
use std::fs::read_dir;
|
||||||
|
|
||||||
|
use crate::fonts::measure_text;
|
||||||
use crate::framebuffer_types::{ Dimensions, Point };
|
use crate::framebuffer_types::{ Dimensions, Point };
|
||||||
|
|
||||||
pub fn min(one: usize, two: usize) -> usize {
|
pub fn min(one: usize, two: usize) -> usize {
|
||||||
if one > two { two } else { one }
|
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 {
|
pub trait Substring {
|
||||||
fn substring(&self, start: usize, end: usize) -> &str;
|
fn substring(&self, start: usize, end: usize) -> &str;
|
||||||
fn remove(&self, index: usize, len: usize) -> String;
|
fn remove(&self, index: usize, len: usize) -> String;
|
||||||
fn remove_last(&self) -> String;
|
fn remove_last(&self) -> String;
|
||||||
|
fn find_substring(&self, substr: &str) -> Option<usize>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Substring for String {
|
impl Substring for String {
|
||||||
@@ -29,6 +39,14 @@ impl Substring for String {
|
|||||||
byte_end += char_length;
|
byte_end += char_length;
|
||||||
}
|
}
|
||||||
&self[byte_start..byte_end]
|
&self[byte_start..byte_end]
|
||||||
|
/*
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut chars = self.chars().skip(start);
|
||||||
|
for _i in 0..(end - start) {
|
||||||
|
result += &chars.next().unwrap().to_string();
|
||||||
|
}
|
||||||
|
result
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, index: usize, len: usize) -> String {
|
fn remove(&self, index: usize, len: usize) -> String {
|
||||||
@@ -38,13 +56,26 @@ impl Substring for String {
|
|||||||
fn remove_last(&self) -> String {
|
fn remove_last(&self) -> String {
|
||||||
self.substring(0, self.chars().count() - 1).to_string()
|
self.substring(0, self.chars().count() - 1).to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_substring(&self, substr: &str) -> Option<usize> {
|
||||||
|
//slightly inefficient
|
||||||
|
let substr_len = substr.chars().count();
|
||||||
|
let self_len = self.chars().count();
|
||||||
|
if substr_len <= self_len {
|
||||||
|
for start in 0..=(self_len - substr_len) {
|
||||||
|
if self.substring(start, start + substr_len) == substr {
|
||||||
|
return Some(start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//the tuple is first, line #, actual line
|
//the tuple is first, line #, actual line
|
||||||
pub fn calc_actual_lines<'a>(lines: impl Iterator<Item = &'a String>, max_chars_per_line: usize, one_extra: bool) -> Vec<(bool, usize, String)> {
|
pub fn calc_actual_lines<'a>(lines: impl Iterator<Item = &'a String>, max_chars_per_line: usize, one_extra: bool) -> Vec<(bool, usize, String)> {
|
||||||
let mut actual_lines = Vec::new();
|
let mut actual_lines = Vec::new();
|
||||||
let mut line_num = 0;
|
for (line_num, real_line) in lines.enumerate() {
|
||||||
for real_line in lines {
|
|
||||||
let mut line = real_line.to_string() + if one_extra { " " } else { "" };
|
let mut line = real_line.to_string() + if one_extra { " " } else { "" };
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
loop {
|
loop {
|
||||||
@@ -62,20 +93,24 @@ pub fn calc_actual_lines<'a>(lines: impl Iterator<Item = &'a String>, max_chars_
|
|||||||
}
|
}
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
line_num += 1;
|
|
||||||
}
|
}
|
||||||
actual_lines
|
actual_lines
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calc_new_cursor_pos(cursor_pos: usize, new_length: usize) -> usize {
|
/// truncate to ... if too long (uses `measure_text`)
|
||||||
if cursor_pos >= new_length {
|
pub fn trunc_words(fonts: &[String], to_measure: String, horiz_spacing: Option<usize>, max_text_width: usize) -> String {
|
||||||
if new_length == 0 {
|
if measure_text(fonts, &to_measure, horiz_spacing).width > max_text_width {
|
||||||
0
|
let mut current = String::new();
|
||||||
} else {
|
for c in to_measure.chars() {
|
||||||
new_length - 1
|
let to_measure = current.clone() + &c.to_string() + "...";
|
||||||
|
if measure_text(fonts, &to_measure, horiz_spacing).width > max_text_width {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
current += &c.to_string();
|
||||||
|
}
|
||||||
|
current + "..."
|
||||||
} else {
|
} else {
|
||||||
cursor_pos
|
to_measure.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +167,7 @@ pub fn hex_to_u8(c1: char, c2: char) -> u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_hex(c: char) -> bool {
|
pub fn is_hex(c: char) -> bool {
|
||||||
HEX_CHARS.iter().position(|hc| hc == &c).is_some()
|
HEX_CHARS.iter().any(|hc| hc == &c)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn point_inside(point: Point, top_left: Point, size: Dimensions) -> bool {
|
pub fn point_inside(point: Point, top_left: Point, size: Dimensions) -> bool {
|
||||||
@@ -194,3 +229,16 @@ pub fn path_autocomplete(current_path: &str, partial_path: &str) -> Option<Strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,18 @@ pub enum KeyChar {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum DrawInstructions {
|
pub enum DrawInstructions {
|
||||||
|
/// Top left point, dimensions, colour
|
||||||
Rect(Point, Dimensions, RGBColor),
|
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),
|
Gradient(Point, Dimensions, RGBColor, RGBColor, usize),
|
||||||
|
/// Top left point, path to file, reverse
|
||||||
Bmp(Point, String, bool),
|
Bmp(Point, String, bool),
|
||||||
|
/// Centre point, radius, colour
|
||||||
Circle(Point, usize, RGBColor),
|
Circle(Point, usize, RGBColor),
|
||||||
|
/// Start point, end point, line width, line colour
|
||||||
|
Line(Point, Point, usize, RGBColor),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
|||||||
@@ -2,25 +2,25 @@ use std::vec::Vec;
|
|||||||
use std::vec;
|
use std::vec;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fs::{ read_to_string, File };
|
use std::fs::{ read_to_string, File };
|
||||||
use std::time::{ Duration, SystemTime, UNIX_EPOCH };
|
use std::time::{ Duration, SystemTime, UNIX_EPOCH };
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::sync::{ Arc, Mutex };
|
use std::sync::{ Arc, Mutex };
|
||||||
|
|
||||||
use rodio::{ Decoder, OutputStream, Sink, Source };
|
use rodio::{ Decoder, OutputStream, Sink, Source };
|
||||||
use rand::{ SeedableRng, prelude::SliceRandom, rngs::SmallRng };
|
|
||||||
use id3::TagLike;
|
use id3::TagLike;
|
||||||
use mp4ameta;
|
use mp4ameta;
|
||||||
use metaflac;
|
use metaflac;
|
||||||
|
|
||||||
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
|
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::framebuffer_types::Dimensions;
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
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::dirs::home;
|
||||||
use ming_wm_lib::ipc::listen;
|
use ming_wm_lib::ipc::listen;
|
||||||
use ming_wm::fs::get_all_files;
|
|
||||||
|
|
||||||
fn get_artist(path: &PathBuf) -> Option<String> {
|
fn get_artist(path: &PathBuf) -> Option<String> {
|
||||||
let ext = path.extension().unwrap();
|
let ext = path.extension().unwrap();
|
||||||
@@ -80,7 +80,6 @@ pub struct AudioPlayer {
|
|||||||
|
|
||||||
impl WindowLike for AudioPlayer {
|
impl WindowLike for AudioPlayer {
|
||||||
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
|
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
|
||||||
//
|
|
||||||
match message {
|
match message {
|
||||||
WindowMessage::Init(dimensions) => {
|
WindowMessage::Init(dimensions) => {
|
||||||
self.dimensions = dimensions;
|
self.dimensions = dimensions;
|
||||||
@@ -110,11 +109,34 @@ impl WindowLike for AudioPlayer {
|
|||||||
} else {
|
} else {
|
||||||
return WindowMessageResponse::DoNothing;
|
return WindowMessageResponse::DoNothing;
|
||||||
}
|
}
|
||||||
} else {
|
} else if key_press.is_regular() {
|
||||||
self.command += &key_press.key.to_string();
|
self.command += &key_press.key.to_string();
|
||||||
|
} else {
|
||||||
|
return WindowMessageResponse::DoNothing
|
||||||
}
|
}
|
||||||
WindowMessageResponse::JustRedraw
|
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
|
WindowMessageResponse::DoNothing
|
||||||
},
|
},
|
||||||
@@ -129,11 +151,15 @@ impl WindowLike for AudioPlayer {
|
|||||||
let queue = &internal_locked.queue;
|
let queue = &internal_locked.queue;
|
||||||
let current = &queue[queue.len() - sink_len];
|
let current = &queue[queue.len() - sink_len];
|
||||||
let current_name = current.0.file_name().unwrap().to_string_lossy().into_owned();
|
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, ¤t_name, None).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) = ¤t.2 {
|
if let Some(artist) = ¤t.2 {
|
||||||
let artist_string = "by ".to_string() + &artist;
|
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, None).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));
|
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)));
|
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 {
|
} else {
|
||||||
@@ -232,8 +258,13 @@ impl AudioPlayer {
|
|||||||
} else {
|
} else {
|
||||||
get_all_files(PathBuf::from(new_path))
|
get_all_files(PathBuf::from(new_path))
|
||||||
};
|
};
|
||||||
let mut rng = SmallRng::seed_from_u64(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
|
let mut seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_millis();
|
||||||
queue.shuffle(&mut rng);
|
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 ") {
|
if self.command.starts_with("p ") {
|
||||||
let mut locked_internal = self.internal.lock().unwrap();
|
let mut locked_internal = self.internal.lock().unwrap();
|
||||||
(*locked_internal).sink.clear();
|
(*locked_internal).sink.clear();
|
||||||
|
|||||||
254
src/bin/draw.rs
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
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 {
|
||||||
|
//apparently this is legal. thanks clippy
|
||||||
|
Self {
|
||||||
|
current_linewidth: 1,
|
||||||
|
..Default::default() //no comma here allowed though??
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
listen(Draw::new());
|
||||||
|
}
|
||||||
@@ -61,8 +61,7 @@ impl WindowLike for FileExplorer {
|
|||||||
WindowMessageResponse::JustRedraw
|
WindowMessageResponse::JustRedraw
|
||||||
},
|
},
|
||||||
WindowMessage::KeyPress(key_press) => {
|
WindowMessage::KeyPress(key_press) => {
|
||||||
self.state = State::List;
|
if key_press.is_enter() && self.state == State::List {
|
||||||
if key_press.is_enter() {
|
|
||||||
if self.current_dir_contents.len() > 0 {
|
if self.current_dir_contents.len() > 0 {
|
||||||
let selected_entry = &self.current_dir_contents[self.position];
|
let selected_entry = &self.current_dir_contents[self.position];
|
||||||
if !selected_entry.is_file {
|
if !selected_entry.is_file {
|
||||||
@@ -75,6 +74,7 @@ impl WindowLike for FileExplorer {
|
|||||||
}
|
}
|
||||||
WindowMessageResponse::DoNothing
|
WindowMessageResponse::DoNothing
|
||||||
} else if key_press.key == 'j' || key_press.is_down_arrow() || key_press.key == 'k' || key_press.is_up_arrow() {
|
} else if key_press.key == 'j' || key_press.is_down_arrow() || key_press.key == 'k' || key_press.is_up_arrow() {
|
||||||
|
self.state = State::List;
|
||||||
if key_press.key == 'j' || key_press.is_down_arrow() {
|
if key_press.key == 'j' || key_press.is_down_arrow() {
|
||||||
//down
|
//down
|
||||||
if self.position == self.current_dir_contents.len() - 1 {
|
if self.position == self.current_dir_contents.len() - 1 {
|
||||||
@@ -103,9 +103,17 @@ impl WindowLike for FileExplorer {
|
|||||||
};
|
};
|
||||||
WindowMessageResponse::JustRedraw
|
WindowMessageResponse::JustRedraw
|
||||||
} else if key_press.key == 'i' {
|
} else if key_press.key == 'i' {
|
||||||
|
if self.state == State::Info {
|
||||||
|
self.state = State::List;
|
||||||
|
} else {
|
||||||
self.state = State::Info;
|
self.state = State::Info;
|
||||||
let selected_entry = &self.current_dir_contents[self.position];
|
let selected_entry = &self.current_dir_contents[self.position];
|
||||||
self.metadata = Some(metadata(&selected_entry.path).unwrap());
|
self.metadata = Some(metadata(&selected_entry.path).unwrap());
|
||||||
|
}
|
||||||
|
WindowMessageResponse::JustRedraw
|
||||||
|
} else if self.state == State::Info {
|
||||||
|
//pressing any key to exit info mode
|
||||||
|
self.state = State::List;
|
||||||
WindowMessageResponse::JustRedraw
|
WindowMessageResponse::JustRedraw
|
||||||
} else {
|
} else {
|
||||||
WindowMessageResponse::DoNothing
|
WindowMessageResponse::DoNothing
|
||||||
@@ -119,7 +127,7 @@ impl WindowLike for FileExplorer {
|
|||||||
let mut instructions = Vec::new();
|
let mut instructions = Vec::new();
|
||||||
if self.state == State::List {
|
if self.state == State::List {
|
||||||
//top bar with path name
|
//top bar with path name
|
||||||
instructions.push(DrawInstructions::Text([5, 0], vec!["nimbus-roman".to_string(), "shippori-mincho".to_string()], "Current: ".to_string() + &self.current_path.to_string_lossy().to_string(), theme_info.text, theme_info.background, None, None));
|
instructions.push(DrawInstructions::Text([5, 0], vec!["nimbus-roman".to_string(), "shippori-mincho".to_string()], "Current: ".to_string() + self.current_path.to_string_lossy().as_ref(), theme_info.text, theme_info.background, None, None));
|
||||||
//the actual files and directories
|
//the actual files and directories
|
||||||
let mut start_y = HEIGHT;
|
let mut start_y = HEIGHT;
|
||||||
let mut i = self.top_position;
|
let mut i = self.top_position;
|
||||||
@@ -133,10 +141,10 @@ impl WindowLike for FileExplorer {
|
|||||||
}
|
}
|
||||||
//unwrap_or not used because "Arguments passed to unwrap_or are eagerly evaluated", apparently
|
//unwrap_or not used because "Arguments passed to unwrap_or are eagerly evaluated", apparently
|
||||||
let name = entry.override_name.clone();
|
let name = entry.override_name.clone();
|
||||||
let name = if name.is_none() {
|
let name = if let Some(name) = name {
|
||||||
entry.path.file_name().unwrap().to_os_string().into_string().unwrap()
|
name
|
||||||
} else {
|
} else {
|
||||||
name.unwrap()
|
entry.path.file_name().unwrap().to_os_string().into_string().unwrap()
|
||||||
};
|
};
|
||||||
instructions.push(DrawInstructions::Text([5, start_y + 4], vec!["nimbus-roman".to_string(), "shippori-mincho".to_string()], name, if is_selected { theme_info.top_text } else { theme_info.text }, if is_selected { theme_info.top } else { theme_info.background }, None, None));
|
instructions.push(DrawInstructions::Text([5, start_y + 4], vec!["nimbus-roman".to_string(), "shippori-mincho".to_string()], name, if is_selected { theme_info.top_text } else { theme_info.text }, if is_selected { theme_info.top } else { theme_info.background }, None, None));
|
||||||
start_y += HEIGHT;
|
start_y += HEIGHT;
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ use std::vec::Vec;
|
|||||||
use std::vec;
|
use std::vec;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fs::{ read_to_string, write };
|
use std::fs::{ read_to_string, write };
|
||||||
|
|
||||||
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType };
|
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType };
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use ming_wm_lib::themes::ThemeInfo;
|
||||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||||
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
|
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::dirs::home;
|
||||||
use ming_wm_lib::utils::path_autocomplete;
|
use ming_wm_lib::utils::{ get_rest_of_split, path_autocomplete };
|
||||||
use ming_wm_lib::ipc::listen;
|
use ming_wm_lib::ipc::listen;
|
||||||
|
|
||||||
const MONO_WIDTH: u8 = 10;
|
const MONO_WIDTH: u8 = 10;
|
||||||
@@ -18,6 +19,8 @@ const LINE_HEIGHT: usize = 18;
|
|||||||
const PADDING: usize = 2;
|
const PADDING: usize = 2;
|
||||||
const BAND_HEIGHT: usize = 19;
|
const BAND_HEIGHT: usize = 19;
|
||||||
|
|
||||||
|
const WORD_END: [char; 8] = ['.', ',', ':', '[', ']', '{', '}', ' '];
|
||||||
|
|
||||||
struct FileInfo {
|
struct FileInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
@@ -42,6 +45,12 @@ enum State {
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
fn is_numberable(&self) -> bool {
|
||||||
|
*self == State::Maybeg || *self == State::Find || *self == State::BackFind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq)]
|
#[derive(Default, PartialEq)]
|
||||||
enum Mode {
|
enum Mode {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -76,6 +85,7 @@ struct Malvim {
|
|||||||
state: State,
|
state: State,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
command: Option<String>,
|
command: Option<String>,
|
||||||
|
prev_command: Option<String>,
|
||||||
bottom_message: Option<String>,
|
bottom_message: Option<String>,
|
||||||
maybe_num: Option<usize>,
|
maybe_num: Option<usize>,
|
||||||
files: Vec<FileInfo>,
|
files: Vec<FileInfo>,
|
||||||
@@ -102,10 +112,22 @@ impl WindowLike for Malvim {
|
|||||||
self.mode = Mode::Command;
|
self.mode = Mode::Command;
|
||||||
self.command = Some(String::new());
|
self.command = Some(String::new());
|
||||||
changed = false;
|
changed = false;
|
||||||
} else if (key_press.key == 'i' || key_press.key == 'A') && self.mode == Mode::Normal && self.state == State::None && self.files.len() > 0 {
|
} 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 {
|
||||||
if key_press.key == 'A' {
|
|
||||||
let current_file = &mut self.files[self.current_file_index];
|
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();
|
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 = ¤t_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;
|
self.mode = Mode::Insert;
|
||||||
changed = false;
|
changed = false;
|
||||||
@@ -116,23 +138,10 @@ impl WindowLike for Malvim {
|
|||||||
if key_press.is_enter() {
|
if key_press.is_enter() {
|
||||||
let mut line: Vec<char> = line.chars().collect();
|
let mut line: Vec<char> = line.chars().collect();
|
||||||
let (left, right) = line.split_at_mut(current_file.cursor_pos);
|
let (left, right) = line.split_at_mut(current_file.cursor_pos);
|
||||||
let left = left.into_iter().map(|c| c.to_string()).collect::<Vec<String>>().join("");
|
let left = left.iter_mut().map(|c| c.to_string()).collect::<Vec<String>>().join("");
|
||||||
let right = right.into_iter().map(|c| c.to_string()).collect::<Vec<String>>().join("");
|
let right = right.iter_mut().map(|c| c.to_string()).collect::<Vec<String>>().join("");
|
||||||
current_file.content[current_file.line_pos] = left.to_string();
|
current_file.content[current_file.line_pos] = left.to_string();
|
||||||
let spaces = if self.autoindent {
|
let spaces = Malvim::calc_spaces(self.autoindent, &left);
|
||||||
//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
|
|
||||||
};
|
|
||||||
current_file.content.insert(current_file.line_pos + 1, " ".repeat(spaces) + &right);
|
current_file.content.insert(current_file.line_pos + 1, " ".repeat(spaces) + &right);
|
||||||
current_file.line_pos += 1;
|
current_file.line_pos += 1;
|
||||||
current_file.cursor_pos = spaces;
|
current_file.cursor_pos = spaces;
|
||||||
@@ -167,29 +176,45 @@ impl WindowLike for Malvim {
|
|||||||
self.state = State::None;
|
self.state = State::None;
|
||||||
} else if self.state == State::MaybeDelete {
|
} else if self.state == State::MaybeDelete {
|
||||||
if key_press.key == 'd' {
|
if key_press.key == 'd' {
|
||||||
|
for _ in 0..self.maybe_num.unwrap_or(1) {
|
||||||
current_file.content.remove(current_file.line_pos);
|
current_file.content.remove(current_file.line_pos);
|
||||||
if current_file.content.len() == 0 {
|
if current_file.content.len() == 0 {
|
||||||
current_file.content = vec![String::new()];
|
current_file.content = vec![String::new()];
|
||||||
} else if current_file.line_pos == current_file.content.len() {
|
} else if current_file.line_pos == current_file.content.len() {
|
||||||
current_file.line_pos = current_file.content.len() - 1;
|
current_file.line_pos = current_file.content.len() - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let new_length = current_file.content[current_file.line_pos].chars().count();
|
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);
|
||||||
} else if key_press.key == 'w' {
|
} else if key_press.key == 'w' || key_press.key == 'W' || key_press.key == '$' {
|
||||||
let line = ¤t_file.content[current_file.line_pos];
|
let line = ¤t_file.content[current_file.line_pos];
|
||||||
let line_len = line.chars().count();
|
let line_len = line.chars().count();
|
||||||
if line_len > 0 && current_file.cursor_pos < line_len {
|
if line_len > 0 && current_file.cursor_pos < line_len {
|
||||||
//offset until space or eol
|
//offset until space or eol
|
||||||
let mut line_chars = line.chars().skip(current_file.cursor_pos).peekable();
|
let mut line_chars = line.chars().skip(current_file.cursor_pos).peekable();
|
||||||
let current_char = line_chars.peek().unwrap().clone();
|
//deref is Copy
|
||||||
let offset = line_chars.position(|c| if current_char == ' ' {
|
let current_char = *line_chars.peek().unwrap();
|
||||||
|
let offset = if key_press.key == 'W' || key_press.key == 'w' {
|
||||||
|
line_chars.position(|c| if key_press.key == 'w' {
|
||||||
|
if WORD_END.contains(¤t_char) {
|
||||||
|
c != current_char
|
||||||
|
} else {
|
||||||
|
WORD_END.contains(&c)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if current_char == ' ' {
|
||||||
c != ' '
|
c != ' '
|
||||||
} else {
|
} else {
|
||||||
c == ' '
|
c == ' '
|
||||||
}).unwrap_or(line_len - current_file.cursor_pos);
|
}
|
||||||
|
}).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);
|
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();
|
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;
|
self.state = State::None;
|
||||||
@@ -200,15 +225,21 @@ impl WindowLike for Malvim {
|
|||||||
current_file.line_pos = current_file.content.len() - 1;
|
current_file.line_pos = current_file.content.len() - 1;
|
||||||
}
|
}
|
||||||
let new_length = current_file.content[current_file.line_pos].chars().count();
|
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;
|
changed = false;
|
||||||
self.state = State::None;
|
self.state = State::None;
|
||||||
} else if self.state == State::Find || self.state == State::BackFind {
|
} else if self.state == State::Find || self.state == State::BackFind || key_press.key == ';' || key_press.key == ',' {
|
||||||
let old_pos = current_file.cursor_pos;
|
let mut old_pos = current_file.cursor_pos;
|
||||||
let find_pos = if self.state == State::Find {
|
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() {
|
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 {
|
if let Some(found_index) = found_index {
|
||||||
old_pos + found_index + 1
|
old_pos + found_index + 1
|
||||||
} else {
|
} else {
|
||||||
@@ -220,7 +251,7 @@ impl WindowLike for Malvim {
|
|||||||
} else {
|
} else {
|
||||||
//how does this work again? no idea
|
//how does this work again? no idea
|
||||||
if old_pos != 0 {
|
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 {
|
if let Some(found_index) = found_index {
|
||||||
old_pos - found_index - 1
|
old_pos - found_index - 1
|
||||||
} else {
|
} else {
|
||||||
@@ -231,6 +262,8 @@ impl WindowLike for Malvim {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
current_file.cursor_pos = find_pos;
|
current_file.cursor_pos = find_pos;
|
||||||
|
old_pos = current_file.cursor_pos;
|
||||||
|
}
|
||||||
changed = false;
|
changed = false;
|
||||||
self.state = State::None;
|
self.state = State::None;
|
||||||
} else if key_press.key == 'x' {
|
} else if key_press.key == 'x' {
|
||||||
@@ -240,6 +273,8 @@ impl WindowLike for Malvim {
|
|||||||
if current_length == 1 {
|
if current_length == 1 {
|
||||||
current_file.cursor_pos = 0;
|
current_file.cursor_pos = 0;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
changed = false;
|
||||||
}
|
}
|
||||||
} else if key_press.key == 'h' || key_press.is_left_arrow() {
|
} else if key_press.key == 'h' || key_press.is_left_arrow() {
|
||||||
current_file.cursor_pos = current_file.cursor_pos.checked_sub(self.maybe_num.unwrap_or(1)).unwrap_or(0);
|
current_file.cursor_pos = current_file.cursor_pos.checked_sub(self.maybe_num.unwrap_or(1)).unwrap_or(0);
|
||||||
@@ -254,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);
|
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();
|
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;
|
changed = false;
|
||||||
} else if key_press.key == 'l' || key_press.is_right_arrow() {
|
} else if key_press.key == 'l' || key_press.is_right_arrow() {
|
||||||
if current_length > 0 {
|
if current_length > 0 {
|
||||||
@@ -287,7 +322,7 @@ impl WindowLike for Malvim {
|
|||||||
} else if key_press.key == 'G' {
|
} else if key_press.key == 'G' {
|
||||||
current_file.line_pos = current_file.content.len() - 1;
|
current_file.line_pos = current_file.content.len() - 1;
|
||||||
let new_length = current_file.content[current_file.line_pos].chars().count();
|
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;
|
changed = false;
|
||||||
} else if key_press.key == 'f' {
|
} else if key_press.key == 'f' {
|
||||||
self.state = State::Find;
|
self.state = State::Find;
|
||||||
@@ -295,7 +330,88 @@ impl WindowLike for Malvim {
|
|||||||
} else if key_press.key == 'F' {
|
} else if key_press.key == 'F' {
|
||||||
self.state = State::BackFind;
|
self.state = State::BackFind;
|
||||||
changed = false;
|
changed = false;
|
||||||
} else if key_press.key.is_digit(10) {
|
} else if key_press.key == '%' {
|
||||||
|
let current_l = ¤t_file.content[current_file.line_pos];
|
||||||
|
if current_file.cursor_pos < current_l.len() {
|
||||||
|
let current_c = current_l.chars().nth(current_file.cursor_pos).unwrap();
|
||||||
|
let pairs = HashMap::from([
|
||||||
|
('(', (')', true)),
|
||||||
|
(')', ('(', false)),
|
||||||
|
('[', (']', true)),
|
||||||
|
(']', ('[', false)),
|
||||||
|
('"', ('"', true)), //could be either, really
|
||||||
|
('{', ('}', true)),
|
||||||
|
('}', ('{', false)),
|
||||||
|
('<', ('>', true)),
|
||||||
|
('>', ('<', false)),
|
||||||
|
//
|
||||||
|
]);
|
||||||
|
if let Some((corres, forwards)) = pairs.get(¤t_c) {
|
||||||
|
let mut count = 0;
|
||||||
|
let content_len = current_file.content.len();
|
||||||
|
let lines: Vec<&String> = if *forwards {
|
||||||
|
current_file.content.iter().skip(current_file.line_pos).collect()
|
||||||
|
} else {
|
||||||
|
current_file.content.iter().rev().skip(content_len - current_file.line_pos - 1).collect()
|
||||||
|
};
|
||||||
|
let end = if *forwards {
|
||||||
|
content_len - current_file.line_pos
|
||||||
|
} else {
|
||||||
|
current_file.line_pos + 1
|
||||||
|
};
|
||||||
|
'outer: for i in 0..end {
|
||||||
|
let line = if i == 0 {
|
||||||
|
let l = lines[i];
|
||||||
|
let l_len = l.len();
|
||||||
|
if *forwards {
|
||||||
|
if current_file.cursor_pos + 1 < l_len {
|
||||||
|
l.substring(current_file.cursor_pos + 1, l_len)
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
&l.substring(0, current_file.cursor_pos).chars().rev().collect::<String>()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if *forwards {
|
||||||
|
lines[i]
|
||||||
|
} else {
|
||||||
|
&lines[i].chars().rev().collect::<String>()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (c_i, c) in line.chars().enumerate() {
|
||||||
|
if c == current_c {
|
||||||
|
count += 1;
|
||||||
|
} else if &c == corres {
|
||||||
|
if count == 0 {
|
||||||
|
if *forwards {
|
||||||
|
current_file.line_pos += i;
|
||||||
|
} else {
|
||||||
|
current_file.line_pos -= i;
|
||||||
|
};
|
||||||
|
current_file.cursor_pos = if i == 0 {
|
||||||
|
if *forwards {
|
||||||
|
current_file.cursor_pos + c_i + 1
|
||||||
|
} else {
|
||||||
|
current_file.cursor_pos - c_i - 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if *forwards {
|
||||||
|
c_i
|
||||||
|
} else {
|
||||||
|
line.chars().count() - c_i - 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
count -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed = false;
|
||||||
|
} else if key_press.key.is_ascii_digit() {
|
||||||
self.maybe_num = Some(self.maybe_num.unwrap_or(0) * 10 + key_press.key.to_digit(10).unwrap() as usize);
|
self.maybe_num = Some(self.maybe_num.unwrap_or(0) * 10 + key_press.key.to_digit(10).unwrap() as usize);
|
||||||
numbered = true;
|
numbered = true;
|
||||||
changed = false;
|
changed = false;
|
||||||
@@ -303,7 +419,7 @@ impl WindowLike for Malvim {
|
|||||||
changed = false;
|
changed = false;
|
||||||
}
|
}
|
||||||
//reset maybe_num if not num
|
//reset maybe_num if not num
|
||||||
if !numbered && self.state != State::Maybeg {
|
if !numbered && !self.state.is_numberable() {
|
||||||
self.maybe_num = None;
|
self.maybe_num = None;
|
||||||
}
|
}
|
||||||
} else if self.mode == Mode::Command {
|
} else if self.mode == Mode::Command {
|
||||||
@@ -311,7 +427,8 @@ impl WindowLike for Malvim {
|
|||||||
let command = self.command.clone().unwrap_or("".to_string());
|
let command = self.command.clone().unwrap_or("".to_string());
|
||||||
if key_press.is_enter() {
|
if key_press.is_enter() {
|
||||||
new = self.process_command();
|
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;
|
self.mode = Mode::Normal;
|
||||||
} else if key_press.key == '\t' { //tab
|
} else if key_press.key == '\t' { //tab
|
||||||
let mut parts = command.split(" ").skip(1);
|
let mut parts = command.split(" ").skip(1);
|
||||||
@@ -332,7 +449,13 @@ impl WindowLike for Malvim {
|
|||||||
}
|
}
|
||||||
} else if key_press.is_backspace() {
|
} else if key_press.is_backspace() {
|
||||||
if command.len() > 0 {
|
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 {
|
} else {
|
||||||
self.command = Some(command.to_string() + &key_press.key.to_string());
|
self.command = Some(command.to_string() + &key_press.key.to_string());
|
||||||
@@ -368,11 +491,21 @@ impl WindowLike for Malvim {
|
|||||||
ShortcutType::ClipboardPaste(copy_string) => {
|
ShortcutType::ClipboardPaste(copy_string) => {
|
||||||
if self.mode == Mode::Insert {
|
if self.mode == Mode::Insert {
|
||||||
let current_file = &mut self.files[self.current_file_index];
|
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 = ¤t_file.content[current_file.line_pos];
|
let line = ¤t_file.content[current_file.line_pos];
|
||||||
current_file.content[current_file.line_pos] = line.substring(0, current_file.cursor_pos).to_string() + ©_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();
|
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_top_line_pos();
|
||||||
self.calc_current(); //too over zealous but whatever
|
self.calc_current();
|
||||||
self.files[self.current_file_index].changed = true;
|
self.files[self.current_file_index].changed = true;
|
||||||
WindowMessageResponse::JustRedraw
|
WindowMessageResponse::JustRedraw
|
||||||
} else {
|
} else {
|
||||||
@@ -401,7 +534,7 @@ impl WindowLike for Malvim {
|
|||||||
let mut used_width = 0;
|
let mut used_width = 0;
|
||||||
for file_index in 0..self.files.len() {
|
for file_index in 0..self.files.len() {
|
||||||
let file_info = &self.files[file_index];
|
let file_info = &self.files[file_index];
|
||||||
let future_used_width = used_width + 4 + (file_info.name.len() + if file_info.changed { 2 } else { 0 }) * MONO_WIDTH as usize;
|
let future_used_width = used_width + 4 + (file_info.name.len() + if file_info.changed { 2 } else { 0 }) * MONO_WIDTH as usize + 15;
|
||||||
//just cut off when too many file tabs open to fit
|
//just cut off when too many file tabs open to fit
|
||||||
if future_used_width > self.dimensions[0] {
|
if future_used_width > self.dimensions[0] {
|
||||||
break;
|
break;
|
||||||
@@ -455,12 +588,11 @@ impl WindowLike for Malvim {
|
|||||||
//bottom blue band stuff
|
//bottom blue band stuff
|
||||||
//write mode
|
//write mode
|
||||||
instructions.push(DrawInstructions::Text([0, self.dimensions[1] - BAND_HEIGHT * 2 + 2], vec!["nimbus-romono".to_string()], self.mode.to_string(), theme_info.top_text, theme_info.top, Some(0), Some(MONO_WIDTH)));
|
instructions.push(DrawInstructions::Text([0, self.dimensions[1] - BAND_HEIGHT * 2 + 2], vec!["nimbus-romono".to_string()], self.mode.to_string(), theme_info.top_text, theme_info.top, Some(0), Some(MONO_WIDTH)));
|
||||||
let file_status;
|
let file_status = if self.files.len() > 0 {
|
||||||
if self.files.len() > 0 {
|
self.files[self.current_file_index].name.clone()
|
||||||
file_status = self.files[self.current_file_index].name.clone();
|
|
||||||
} else {
|
} else {
|
||||||
file_status = "No file open".to_string();
|
"No file open".to_string()
|
||||||
}
|
};
|
||||||
instructions.push(DrawInstructions::Text([self.dimensions[0] - file_status.len() * (MONO_WIDTH as usize), self.dimensions[1] - BAND_HEIGHT * 2 + 2], vec!["nimbus-romono".to_string()], file_status, theme_info.top_text, theme_info.top, Some(0), Some(MONO_WIDTH)));
|
instructions.push(DrawInstructions::Text([self.dimensions[0] - file_status.len() * (MONO_WIDTH as usize), self.dimensions[1] - BAND_HEIGHT * 2 + 2], vec!["nimbus-romono".to_string()], file_status, theme_info.top_text, theme_info.top, Some(0), Some(MONO_WIDTH)));
|
||||||
//write command or bottom message
|
//write command or bottom message
|
||||||
if self.mode == Mode::Command {
|
if self.mode == Mode::Command {
|
||||||
@@ -493,6 +625,34 @@ impl Malvim {
|
|||||||
Default::default()
|
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) {
|
fn calc_top_line_pos(&mut self) {
|
||||||
if self.files.len() == 0 {
|
if self.files.len() == 0 {
|
||||||
return;
|
return;
|
||||||
@@ -590,15 +750,43 @@ impl Malvim {
|
|||||||
}
|
}
|
||||||
} else if self.files.len() == 0 {
|
} else if self.files.len() == 0 {
|
||||||
self.bottom_message = Some("No files are open, so can only do :e(dit)".to_string());
|
self.bottom_message = Some("No files are open, so can only do :e(dit)".to_string());
|
||||||
} else if first.starts_with("/") {
|
} else if let Some(s_first) = first.strip_prefix("/") {
|
||||||
let current_file = &mut self.files[self.current_file_index];
|
let current_file = &mut self.files[self.current_file_index];
|
||||||
if current_file.content.len() > 0 {
|
if current_file.content.len() > 0 {
|
||||||
let found_line_no = current_file.content.iter().skip(current_file.line_pos + 1).position(|line| {
|
let p1 = if arg == "" {
|
||||||
line.contains(&first[1..])
|
String::new()
|
||||||
});
|
} else {
|
||||||
if let Some(found_line_no) = found_line_no {
|
" ".to_string() + arg
|
||||||
current_file.line_pos = found_line_no + current_file.line_pos + 1;
|
};
|
||||||
current_file.cursor_pos = 0;
|
let rest = get_rest_of_split(&mut parts, Some(" "));
|
||||||
|
let rest = if rest == "" {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
" ".to_string() + &rest
|
||||||
|
};
|
||||||
|
let query = s_first.to_string() + &p1 + &rest;
|
||||||
|
let mut lines = current_file.content.iter().skip(current_file.line_pos);
|
||||||
|
for i in 0..(current_file.content.len() - current_file.line_pos) {
|
||||||
|
let line = if i == 0 {
|
||||||
|
let l = lines.next().unwrap();
|
||||||
|
let l_len = l.len();
|
||||||
|
if (current_file.cursor_pos + 1) < l_len {
|
||||||
|
l.substring(current_file.cursor_pos + 1, l_len)
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lines.next().unwrap()
|
||||||
|
};
|
||||||
|
if let Some(found_index) = line.to_string().find_substring(&query) {
|
||||||
|
current_file.line_pos += i;
|
||||||
|
current_file.cursor_pos = if i == 0 {
|
||||||
|
current_file.cursor_pos + found_index + 1
|
||||||
|
} else {
|
||||||
|
found_index
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if first == "x" || first == "w" || first == "write" || first == "q" || first == "quit" {
|
} else if first == "x" || first == "w" || first == "write" || first == "q" || first == "quit" {
|
||||||
|
|||||||
@@ -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::messages::{ WindowMessage, WindowMessageResponse };
|
||||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
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;
|
use ming_wm_lib::ipc::listen;
|
||||||
|
|
||||||
//16x16 with 40 mines
|
//16x16 with 40 mines
|
||||||
@@ -65,15 +65,15 @@ impl WindowLike for Minesweeper {
|
|||||||
self.first_char = '\0';
|
self.first_char = '\0';
|
||||||
WindowMessageResponse::DoNothing
|
WindowMessageResponse::DoNothing
|
||||||
} else if self.first_char == '\0' {
|
} else if self.first_char == '\0' {
|
||||||
if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() {
|
if HEX_CHARS.iter().any(|c| c == &key_press.key) {
|
||||||
self.first_char = key_press.key;
|
self.first_char = key_press.key;
|
||||||
}
|
}
|
||||||
WindowMessageResponse::DoNothing
|
WindowMessageResponse::DoNothing
|
||||||
} else if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() {
|
} else if HEX_CHARS.iter().any(|c| c == &key_press.key) {
|
||||||
let u = hex_to_u8(self.first_char, key_press.key) as usize;
|
let u = hex_to_u8(self.first_char, key_press.key) as usize;
|
||||||
let y = u / 16;
|
let y = u / 16;
|
||||||
let x = u % 16;
|
let x = u % 16;
|
||||||
if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() {
|
if HEX_CHARS.iter().any(|c| c == &key_press.key) {
|
||||||
if self.state == State::BeforePlaying {
|
if self.state == State::BeforePlaying {
|
||||||
loop {
|
loop {
|
||||||
self.new_tiles();
|
self.new_tiles();
|
||||||
@@ -252,15 +252,11 @@ impl Minesweeper {
|
|||||||
//https://en.wikipedia.org/wiki/Xorshift
|
//https://en.wikipedia.org/wiki/Xorshift
|
||||||
//from 0 to 15
|
//from 0 to 15
|
||||||
pub fn random(&mut self) -> usize {
|
pub fn random(&mut self) -> usize {
|
||||||
let mut x = self.random_seed;
|
self.random_seed = random_u32(self.random_seed);
|
||||||
x ^= x << 13;
|
|
||||||
x ^= x >> 17;
|
|
||||||
x ^= x << 5;
|
|
||||||
self.random_seed = x;
|
|
||||||
self.random_seed as usize % 16
|
self.random_seed as usize % 16
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_adjacent_tiles(&self, x: usize, y: usize, mut action: impl FnMut(usize, usize) -> (), if_mine: bool) {
|
pub fn on_adjacent_tiles(&self, x: usize, y: usize, mut action: impl FnMut(usize, usize), if_mine: bool) {
|
||||||
if y > 0 {
|
if y > 0 {
|
||||||
//above
|
//above
|
||||||
if self.tiles[y - 1][x].mine == if_mine {
|
if self.tiles[y - 1][x].mine == if_mine {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
|
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
|
||||||
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
|
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
|
||||||
@@ -101,13 +102,11 @@ impl WindowLike for Reversi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if white_tiles == black_tiles {
|
self.state = match white_tiles.cmp(&black_tiles) {
|
||||||
self.state = State::Tie;
|
Ordering::Equal => State::Tie,
|
||||||
} else if white_tiles > black_tiles {
|
Ordering::Greater => State::WhiteWin,
|
||||||
self.state = State::WhiteWin;
|
Ordering::Less => State::BlackWin,
|
||||||
} else {
|
};
|
||||||
self.state = State::BlackWin;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.current_number = None;
|
self.current_number = None;
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ use std::vec;
|
|||||||
use std::sync::mpsc::{ channel, Receiver, Sender };
|
use std::sync::mpsc::{ channel, Receiver, Sender };
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::process::{ Child, Stdio };
|
use std::process::{ Child, Stdio };
|
||||||
|
use std::process::Command;
|
||||||
use std::io::{ Read, Write };
|
use std::io::{ Read, Write };
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::fmt;
|
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::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::framebuffer_types::Dimensions;
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use ming_wm_lib::themes::ThemeInfo;
|
||||||
use ming_wm_lib::utils::{ concat_paths, path_autocomplete, Substring };
|
use ming_wm_lib::utils::{ concat_paths, path_autocomplete, Substring };
|
||||||
@@ -46,6 +47,11 @@ fn strip_ansi_escape_codes(line: String) -> String {
|
|||||||
new_line
|
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)]
|
#[derive(Default, PartialEq)]
|
||||||
enum Mode {
|
enum Mode {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -78,6 +84,7 @@ pub struct Terminal {
|
|||||||
current_path: String,
|
current_path: String,
|
||||||
running_process: Option<Child>,
|
running_process: Option<Child>,
|
||||||
process_current_line: Vec<u8>, //bytes of line
|
process_current_line: Vec<u8>, //bytes of line
|
||||||
|
output: String, //current or previous running output of command
|
||||||
pty_outerr_rx: Option<Receiver<u8>>,
|
pty_outerr_rx: Option<Receiver<u8>>,
|
||||||
pty_in_tx: Option<Sender<String>>,
|
pty_in_tx: Option<Sender<String>>,
|
||||||
history: Vec<String>,
|
history: Vec<String>,
|
||||||
@@ -116,6 +123,7 @@ impl WindowLike for Terminal {
|
|||||||
self.history_index = None;
|
self.history_index = None;
|
||||||
self.mode = self.process_command();
|
self.mode = self.process_command();
|
||||||
self.current_input = String::new();
|
self.current_input = String::new();
|
||||||
|
self.output = String::new();
|
||||||
} else if key_press.key == '\t' { //tab
|
} else if key_press.key == '\t' { //tab
|
||||||
//autocomplete assuming it's a file system path
|
//autocomplete assuming it's a file system path
|
||||||
//...mostly working
|
//...mostly working
|
||||||
@@ -131,7 +139,7 @@ impl WindowLike for Terminal {
|
|||||||
self.prev();
|
self.prev();
|
||||||
} else if key_press.is_down_arrow() {
|
} else if key_press.is_down_arrow() {
|
||||||
self.next();
|
self.next();
|
||||||
} else {
|
} else if key_press.is_regular() {
|
||||||
self.current_input += &key_press.key.to_string();
|
self.current_input += &key_press.key.to_string();
|
||||||
}
|
}
|
||||||
self.calc_actual_lines();
|
self.calc_actual_lines();
|
||||||
@@ -141,29 +149,37 @@ impl WindowLike for Terminal {
|
|||||||
Mode::Running => {
|
Mode::Running => {
|
||||||
//update
|
//update
|
||||||
let mut changed = false;
|
let mut changed = false;
|
||||||
loop {
|
while let Ok(ci) = self.pty_outerr_rx.as_mut().unwrap().recv_timeout(Duration::from_millis(5)) {
|
||||||
if let Ok(ci) = self.pty_outerr_rx.as_mut().unwrap().recv_timeout(Duration::from_millis(5)) {
|
|
||||||
if char::from(ci) == '\n' {
|
if char::from(ci) == '\n' {
|
||||||
let pcl_len = self.process_current_line.len();
|
let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()));
|
||||||
self.lines.push(strip_ansi_escape_codes(String::from_utf8(self.process_current_line.clone()).unwrap_or("?".repeat(pcl_len))));
|
self.output += &append_line;
|
||||||
|
self.output += "\n";
|
||||||
|
self.lines.push(append_line);
|
||||||
self.process_current_line = Vec::new();
|
self.process_current_line = Vec::new();
|
||||||
} else if char::from(ci) == '\r' {
|
} else if char::from(ci) == '\r' {
|
||||||
//for now, ignore
|
//for now, ignore
|
||||||
//
|
//
|
||||||
|
} else if char::from(ci) == '\t' {
|
||||||
|
//for now, interpret as space
|
||||||
|
self.process_current_line.push(b' ');
|
||||||
} else {
|
} else {
|
||||||
self.process_current_line.push(ci);
|
self.process_current_line.push(ci);
|
||||||
}
|
}
|
||||||
changed = true;
|
changed = true;
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let running_process = self.running_process.as_mut().unwrap();
|
let running_process = self.running_process.as_mut().unwrap();
|
||||||
if let Some(_status) = running_process.try_wait().unwrap() {
|
if let Some(_status) = running_process.try_wait().unwrap() {
|
||||||
//process exited
|
//process exited
|
||||||
self.pty_outerr_rx = None;
|
self.pty_outerr_rx = None;
|
||||||
self.mode = Mode::Input;
|
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();
|
self.process_current_line = Vec::new();
|
||||||
|
}
|
||||||
changed = true;
|
changed = true;
|
||||||
} else {
|
} else {
|
||||||
if key_press.key == 'i' {
|
if key_press.key == 'i' {
|
||||||
@@ -184,8 +200,9 @@ impl WindowLike for Terminal {
|
|||||||
} else if key_press.is_enter() {
|
} else if key_press.is_enter() {
|
||||||
let _ = self.pty_in_tx.as_mut().unwrap().send(self.current_stdin_input.clone());
|
let _ = self.pty_in_tx.as_mut().unwrap().send(self.current_stdin_input.clone());
|
||||||
self.mode = Mode::Running;
|
self.mode = Mode::Running;
|
||||||
let pcl_len = self.process_current_line.len();
|
let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()) + &self.current_stdin_input);
|
||||||
self.lines.push(strip_ansi_escape_codes(String::from_utf8(self.process_current_line.clone()).unwrap_or("?".repeat(pcl_len))) + &self.current_stdin_input);
|
self.output += &append_line;
|
||||||
|
self.lines.push(append_line);
|
||||||
self.current_stdin_input = String::new();
|
self.current_stdin_input = String::new();
|
||||||
self.process_current_line = Vec::new();
|
self.process_current_line = Vec::new();
|
||||||
} else if key_press.is_backspace() {
|
} else if key_press.is_backspace() {
|
||||||
@@ -207,6 +224,12 @@ impl WindowLike for Terminal {
|
|||||||
//kills and running_process is now None
|
//kills and running_process is now None
|
||||||
let _ = self.running_process.take().unwrap().kill();
|
let _ = self.running_process.take().unwrap().kill();
|
||||||
self.mode = Mode::Input;
|
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
|
WindowMessageResponse::JustRedraw
|
||||||
} else if self.mode == Mode::Input && (key_press.key == 'p' || key_press.key == 'n') {
|
} 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
|
//only the last command is saved unlike other terminals. good enough for me
|
||||||
@@ -228,6 +251,7 @@ impl WindowLike for Terminal {
|
|||||||
},
|
},
|
||||||
WindowMessage::Shortcut(shortcut) => {
|
WindowMessage::Shortcut(shortcut) => {
|
||||||
match shortcut {
|
match shortcut {
|
||||||
|
ShortcutType::ClipboardCopy => WindowMessageResponse::Request(WindowManagerRequest::ClipboardCopy(self.output.clone())),
|
||||||
ShortcutType::ClipboardPaste(copy_string) => {
|
ShortcutType::ClipboardPaste(copy_string) => {
|
||||||
if self.mode == Mode::Input || self.mode == Mode::Stdin {
|
if self.mode == Mode::Input || self.mode == Mode::Stdin {
|
||||||
if self.mode == Mode::Input {
|
if self.mode == Mode::Input {
|
||||||
@@ -331,11 +355,13 @@ impl Terminal {
|
|||||||
}
|
}
|
||||||
Mode::Input
|
Mode::Input
|
||||||
} else {
|
} else {
|
||||||
let (pty, pts) = blocking::open().unwrap();
|
let (pty, pts) = open_pty().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 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();
|
let (tx1, rx1) = channel();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
for ci in pty.bytes() {
|
for ci in pty.file.bytes() {
|
||||||
if let Ok(ci) = ci {
|
if let Ok(ci) = ci {
|
||||||
tx1.send(ci).unwrap();
|
tx1.send(ci).unwrap();
|
||||||
} else {
|
} else {
|
||||||
@@ -347,13 +373,9 @@ impl Terminal {
|
|||||||
let mut stdin = self.running_process.as_mut().unwrap().stdin.take().unwrap();
|
let mut stdin = self.running_process.as_mut().unwrap().stdin.take().unwrap();
|
||||||
let (tx2, rx2) = channel();
|
let (tx2, rx2) = channel();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
loop {
|
while let Ok(write_line) = rx2.recv() {
|
||||||
if let Ok(write_line) = rx2.recv() {
|
|
||||||
let write_line: String = write_line + "\n";
|
let write_line: String = write_line + "\n";
|
||||||
stdin.write(write_line.as_bytes()).unwrap();
|
stdin.write_all(write_line.as_bytes()).unwrap();
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.pty_outerr_rx = Some(rx1);
|
self.pty_outerr_rx = Some(rx1);
|
||||||
@@ -378,8 +400,7 @@ impl Terminal {
|
|||||||
//must_add_current_line will be false
|
//must_add_current_line will be false
|
||||||
"$ ".to_string() + &self.current_input + "█"
|
"$ ".to_string() + &self.current_input + "█"
|
||||||
} else {
|
} else {
|
||||||
let pcl_len = self.process_current_line.len();
|
strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()) + &self.current_stdin_input.clone() + "█")
|
||||||
strip_ansi_escape_codes(String::from_utf8(self.process_current_line.clone()).unwrap_or("?".repeat(pcl_len))) + &self.current_stdin_input.clone() + "█"
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.lines[line_num].clone()
|
self.lines[line_num].clone()
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
use std::process::{ Command, Stdio };
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::io::{ stdin, stdout, BufReader, BufRead, Write };
|
use std::io::{ stdin, stdout, Write };
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use linux::fb::Framebuffer;
|
use linux::fb::Framebuffer;
|
||||||
use termion::input::TermRead;
|
use linux::raw::RawStdout;
|
||||||
use termion::raw::IntoRawMode;
|
use linux::keys::{ RawStdin, Key };
|
||||||
use termion::event::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::window_manager_types::KeyChar;
|
||||||
use ming_wm_lib::messages::*;
|
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 CLEAR_ALL: &'static str = "\x1b[2J";
|
||||||
const HIDE_CURSOR: &'static str = "\x1b[?25l";
|
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::Ctrl(c) => Some(KeyChar::Ctrl(c)),
|
||||||
Key::Backspace => Some(KeyChar::Press('𐘁')),
|
Key::Backspace => Some(KeyChar::Press('𐘁')),
|
||||||
Key::Esc => Some(KeyChar::Press('𐘃')),
|
Key::Esc => Some(KeyChar::Press('𐘃')),
|
||||||
Key::Up => Some(KeyChar::Press('𐙘')),
|
Key::ArrowUp => Some(KeyChar::Press('𐙘')),
|
||||||
Key::Down => Some(KeyChar::Press('𐘞')),
|
Key::ArrowDown => Some(KeyChar::Press('𐘞')),
|
||||||
Key::Left => Some(KeyChar::Press('𐙣')),
|
Key::ArrowLeft => Some(KeyChar::Press('𐙣')),
|
||||||
Key::Right => Some(KeyChar::Press('𐙥')),
|
Key::ArrowRight => Some(KeyChar::Press('𐙥')),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,15 +72,16 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
|||||||
|
|
||||||
writer.init(framebuffer_info.clone());
|
writer.init(framebuffer_info.clone());
|
||||||
|
|
||||||
let mut wm: WindowManager = WindowManager::new(writer, framebuffer, dimensions, 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);
|
wm.draw(None, false);
|
||||||
|
|
||||||
@@ -89,9 +91,9 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
|||||||
|
|
||||||
//read key presses
|
//read key presses
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let stdin = stdin().lock();
|
let stdin = RawStdin::new(stdin());
|
||||||
for c in stdin.keys() {
|
for c in stdin {
|
||||||
if let Some(kc) = key_to_char(c.unwrap()) {
|
if let Some(kc) = key_to_char(c) {
|
||||||
//do not allow exit when locked unless debugging
|
//do not allow exit when locked unless debugging
|
||||||
//if kc == KeyChar::Alt('E') {
|
//if kc == KeyChar::Alt('E') {
|
||||||
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)
|
//read touchscreen presses (hopefully)
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
//spawn evtest, parse it for touch coords
|
|
||||||
if touch {
|
if touch {
|
||||||
let mut evtest = Command::new("evtest").arg("/dev/input/by-path/first-touchscreen").stdout(Stdio::piped()).spawn().unwrap();
|
let mut events = Input::new("/dev/input/by-path/first-touchscreen").unwrap(); //panics in threads don't matter in this case
|
||||||
let reader = BufReader::new(evtest.stdout.as_mut().unwrap());
|
|
||||||
let mut x: Option<usize> = None;
|
let mut x: Option<usize> = None;
|
||||||
let mut y: Option<usize> = None;
|
let mut y: Option<usize> = None;
|
||||||
for line in reader.lines() {
|
loop {
|
||||||
let line = line.unwrap();
|
let event = events.next();
|
||||||
if line.contains(&"ABS_X), value ") || line.contains(&"ABS_Y), value ") {
|
if let Some(event) = event {
|
||||||
let value: Vec<_> = line.split("), value ").collect();
|
//ABS_X = 0, ABS_Y = 1
|
||||||
let value = value[value.len() - 1].parse::<usize>().unwrap();
|
if event.type_ == EventType::EV_ABS && (event.code == 0 || event.code == 1) {
|
||||||
if line.contains(&"ABS_X") {
|
if event.code == 0 {
|
||||||
x = Some(value);
|
x = Some(event.value as usize); //event.value is u16 so this should be fine. unless usize is u8, lmao
|
||||||
} else {
|
} else {
|
||||||
y = Some(value);
|
y = Some(event.value as usize);
|
||||||
}
|
}
|
||||||
if x.is_some() && y.is_some() {
|
if x.is_some() && y.is_some() {
|
||||||
let (x2, y2) = if rotate {
|
let (x2, y2) = if rotate {
|
||||||
@@ -140,6 +140,7 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
|||||||
y = None;
|
y = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
thread::sleep(Duration::from_millis(1));
|
thread::sleep(Duration::from_millis(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,15 +153,18 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
|||||||
for message in rx {
|
for message in rx {
|
||||||
match message {
|
match message {
|
||||||
ThreadMessage::KeyChar(kc) => wm.handle_message(WindowManagerMessage::KeyChar(kc.clone())),
|
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 => {
|
ThreadMessage::Clear => {
|
||||||
write!(stdout, "{}", CLEAR_ALL).unwrap();
|
write!(stdout.stdout, "{}", CLEAR_ALL).unwrap();
|
||||||
stdout.flush().unwrap();
|
stdout.stdout.flush().unwrap();
|
||||||
},
|
},
|
||||||
ThreadMessage::Exit => {
|
ThreadMessage::Exit => {
|
||||||
if !wm.locked {
|
if !wm.locked {
|
||||||
write!(stdout, "{}", SHOW_CURSOR).unwrap();
|
write!(stdout.stdout, "{}", SHOW_CURSOR).unwrap();
|
||||||
stdout.suspend_raw_mode().unwrap();
|
stdout.exit_raw_mode().unwrap();
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
72
src/fs.rs
@@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
15
wm/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "wm"
|
||||||
|
version = "1.2.0"
|
||||||
|
repository = "https://github.com/stjet/ming-wm"
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[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"
|
||||||
@@ -7,12 +7,13 @@ use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
|
|||||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use ming_wm_lib::themes::ThemeInfo;
|
||||||
use ming_wm_lib::dirs::exe_dir;
|
use ming_wm_lib::dirs::exe_dir;
|
||||||
use crate::components::Component;
|
use ming_wm_lib::components::Component;
|
||||||
use crate::components::paragraph::Paragraph;
|
use ming_wm_lib::components::paragraph::Paragraph;
|
||||||
|
|
||||||
pub struct About {
|
pub struct About {
|
||||||
dimensions: Dimensions,
|
dimensions: Dimensions,
|
||||||
components: Vec<Box<dyn Component<()> + Send>>,
|
components: Vec<Box<dyn Component<()> + Send>>,
|
||||||
|
version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowLike for About {
|
impl WindowLike for About {
|
||||||
@@ -40,7 +41,7 @@ impl WindowLike for About {
|
|||||||
|
|
||||||
//properties
|
//properties
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
"About".to_string()
|
"About".to_string() + " - v" + &self.version
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subtype(&self) -> WindowLikeType {
|
fn subtype(&self) -> WindowLikeType {
|
||||||
@@ -53,10 +54,11 @@ impl WindowLike for About {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl About {
|
impl About {
|
||||||
pub fn new() -> Self {
|
pub fn new(version: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
dimensions: [0, 0],
|
dimensions: [0, 0],
|
||||||
components: Vec::new(),
|
components: Vec::new(),
|
||||||
|
version,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ impl WindowLike for DesktopBackground {
|
|||||||
}
|
}
|
||||||
} else if line.len() > 1 {
|
} else if line.len() > 1 {
|
||||||
//first character of line is either r or any other character, but is not part of the path
|
//first character of line is either r or any other character, but is not part of the path
|
||||||
return vec![DrawInstructions::Bmp([0, 0], line[1..].to_string(), line.chars().next().unwrap() == 'r')];
|
return vec![DrawInstructions::Bmp([0, 0], line[1..].to_string(), line.starts_with('r'))];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,8 +8,8 @@ use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
|
|||||||
use ming_wm_lib::dirs::exe_dir;
|
use ming_wm_lib::dirs::exe_dir;
|
||||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use ming_wm_lib::themes::ThemeInfo;
|
||||||
use crate::components::paragraph::Paragraph;
|
use ming_wm_lib::components::paragraph::Paragraph;
|
||||||
use crate::components::Component;
|
use ming_wm_lib::components::Component;
|
||||||
|
|
||||||
pub struct Help {
|
pub struct Help {
|
||||||
dimensions: Dimensions,
|
dimensions: Dimensions,
|
||||||
@@ -1,19 +1,18 @@
|
|||||||
use std::vec;
|
use std::vec;
|
||||||
use std::vec::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::framebuffer_types::Dimensions;
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use ming_wm_lib::themes::ThemeInfo;
|
||||||
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest };
|
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest };
|
||||||
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
|
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
|
||||||
use blake2::{ Blake2b512, Digest };
|
use bitcoin_hashes::Sha512;
|
||||||
|
|
||||||
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];
|
|
||||||
|
|
||||||
pub struct LockScreen {
|
pub struct LockScreen {
|
||||||
dimensions: Dimensions,
|
dimensions: Dimensions,
|
||||||
input_password: String,
|
input_password: String,
|
||||||
|
password_hash: [u8; 64],
|
||||||
|
lines: [String; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowLike for LockScreen {
|
impl WindowLike for LockScreen {
|
||||||
@@ -26,9 +25,7 @@ impl WindowLike for LockScreen {
|
|||||||
WindowMessage::KeyPress(key_press) => {
|
WindowMessage::KeyPress(key_press) => {
|
||||||
if key_press.is_enter() {
|
if key_press.is_enter() {
|
||||||
//check password
|
//check password
|
||||||
let mut hasher = Blake2b512::new();
|
if Sha512::hash((self.input_password.clone() + "salt?sorrycryptographers!1!").as_bytes()).to_byte_array() == self.password_hash {
|
||||||
hasher.update((self.input_password.clone() + "salt?sorrycryptographers").as_bytes());
|
|
||||||
if hasher.finalize() == PASSWORD_HASH.into() {
|
|
||||||
WindowMessageResponse::Request(WindowManagerRequest::Unlock)
|
WindowMessageResponse::Request(WindowManagerRequest::Unlock)
|
||||||
} else {
|
} else {
|
||||||
self.input_password = String::new();
|
self.input_password = String::new();
|
||||||
@@ -54,9 +51,11 @@ impl WindowLike for LockScreen {
|
|||||||
fn draw(&self, _theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
|
fn draw(&self, _theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
|
||||||
vec![
|
vec![
|
||||||
DrawInstructions::Rect([0, 0], self.dimensions, [0, 0, 0]),
|
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], vec!["nimbus-roman".to_string()], self.lines[0].clone(), [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),
|
//He is my brother.
|
||||||
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 + 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([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),
|
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 {
|
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 {
|
Self {
|
||||||
dimensions: [0, 0],
|
dimensions: [0, 0],
|
||||||
input_password: String::new(),
|
input_password: String::new(),
|
||||||
|
password_hash,
|
||||||
|
lines: possible_lines[rand_index].clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,8 +7,8 @@ use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManager
|
|||||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use ming_wm_lib::themes::ThemeInfo;
|
||||||
use ming_wm_lib::utils::point_inside;
|
use ming_wm_lib::utils::point_inside;
|
||||||
use crate::components::Component;
|
use ming_wm_lib::components::Component;
|
||||||
use crate::components::press_button::PressButton;
|
use ming_wm_lib::components::press_button::PressButton;
|
||||||
|
|
||||||
//seems like framebuffer only updates if (real) key is pressed...
|
//seems like framebuffer only updates if (real) key is pressed...
|
||||||
//on mobile, volume down button seems to work but is annoying
|
//on mobile, volume down button seems to work but is annoying
|
||||||
@@ -7,9 +7,9 @@ use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManager
|
|||||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use ming_wm_lib::themes::ThemeInfo;
|
||||||
use ming_wm_lib::dirs::exe_dir;
|
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::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"];
|
static CATEGORIES: [&'static str; 9] = ["About", "Utils", "Games", "Editing", "Files", "Internet", "Misc", "Help", "Lock"];
|
||||||
|
|
||||||
@@ -115,13 +115,11 @@ impl WindowLike for StartMenu {
|
|||||||
];
|
];
|
||||||
let max_per_page = CATEGORIES.len();
|
let max_per_page = CATEGORIES.len();
|
||||||
let current_focus = self.get_focus_index().unwrap();
|
let current_focus = self.get_focus_index().unwrap();
|
||||||
let mut index = 0;
|
for (index, component) in self.components.iter().enumerate() {
|
||||||
for component in &self.components {
|
|
||||||
//supports multiple pages of window options per category
|
//supports multiple pages of window options per category
|
||||||
if (index >= max_per_page && current_focus >= max_per_page) || (index < max_per_page && current_focus < max_per_page) {
|
if (index >= max_per_page && current_focus >= max_per_page) || (index < max_per_page && current_focus < max_per_page) {
|
||||||
instructions.extend(component.draw(theme_info));
|
instructions.extend(component.draw(theme_info));
|
||||||
}
|
}
|
||||||
index += 1;
|
|
||||||
}
|
}
|
||||||
instructions
|
instructions
|
||||||
}
|
}
|
||||||
@@ -194,8 +192,7 @@ impl StartMenu {
|
|||||||
pub fn add_category_components(&mut self) {
|
pub fn add_category_components(&mut self) {
|
||||||
self.current_focus = "About".to_string();
|
self.current_focus = "About".to_string();
|
||||||
self.components = Vec::new();
|
self.components = Vec::new();
|
||||||
for c in 0..CATEGORIES.len() {
|
for (c, name) in CATEGORIES.iter().enumerate() {
|
||||||
let name = CATEGORIES[c];
|
|
||||||
self.components.push(Box::new(HighlightButton::new(
|
self.components.push(Box::new(HighlightButton::new(
|
||||||
name.to_string(), [42, self.y_each * c + 1], [self.dimensions[0] - 42 - 1, self.y_each], name.to_string(), StartMenuMessage::CategoryClick(name), StartMenuMessage::ChangeAcknowledge, c == 0
|
name.to_string(), [42, self.y_each * c + 1], [self.dimensions[0] - 42 - 1, self.y_each], name.to_string(), StartMenuMessage::CategoryClick(name), StartMenuMessage::ChangeAcknowledge, c == 0
|
||||||
)));
|
)));
|
||||||
@@ -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::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType, InfoType, WindowsVec };
|
||||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use ming_wm_lib::themes::ThemeInfo;
|
||||||
use crate::components::Component;
|
use ming_wm_lib::utils::trunc_words;
|
||||||
use crate::components::toggle_button::ToggleButton;
|
use ming_wm_lib::components::Component;
|
||||||
|
use ming_wm_lib::components::toggle_button::ToggleButton;
|
||||||
|
|
||||||
const PADDING: usize = 4;
|
const PADDING: usize = 4;
|
||||||
const META_WIDTH: usize = 175; //of the window button
|
const META_WIDTH: usize = 175; //of the window button
|
||||||
@@ -78,7 +79,8 @@ impl WindowLike for Taskbar {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let info = &self.windows_in_workspace[wi];
|
let info = &self.windows_in_workspace[wi];
|
||||||
let name = &info.1;
|
let max_text_width = META_WIDTH - PADDING * 2;
|
||||||
|
let name = trunc_words(&["nimbus-roman".to_string()], info.1.clone(), None, max_text_width);
|
||||||
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);
|
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;
|
b.inverted = info.0 == self.focused_id;
|
||||||
instructions.extend(b.draw(theme_info));
|
instructions.extend(b.draw(theme_info));
|
||||||
@@ -123,5 +125,3 @@ impl Taskbar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -4,9 +4,7 @@ use std::vec::Vec;
|
|||||||
use bmp_rust::bmp::BMP;
|
use bmp_rust::bmp::BMP;
|
||||||
|
|
||||||
use ming_wm_lib::framebuffer_types::*;
|
use ming_wm_lib::framebuffer_types::*;
|
||||||
use crate::fs::get_font_char_from_fonts;
|
use ming_wm_lib::fonts::{ CachedFontCharGetter, FontCharInfo };
|
||||||
|
|
||||||
type FontChar = (char, Vec<Vec<u8>>, u8);
|
|
||||||
|
|
||||||
fn color_with_alpha(color: RGBColor, bg_color: RGBColor, alpha: u8) -> RGBColor {
|
fn color_with_alpha(color: RGBColor, bg_color: RGBColor, alpha: u8) -> RGBColor {
|
||||||
/*let factor: f32 = alpha as f32 / 255.0;
|
/*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
|
//currently doesn't check if writing onto next line accidentally
|
||||||
pub struct FramebufferWriter {
|
pub struct FramebufferWriter {
|
||||||
info: FramebufferInfo,
|
info: FramebufferInfo,
|
||||||
|
fc_getter: CachedFontCharGetter,
|
||||||
buffer: Vec<u8>,
|
buffer: Vec<u8>,
|
||||||
saved_buffer: Option<Vec<u8>>,
|
saved_buffer: Option<Vec<u8>>,
|
||||||
rotate_buffer: Option<Vec<u8>>,
|
rotate_buffer: Option<Vec<u8>>,
|
||||||
@@ -57,6 +56,7 @@ impl FramebufferWriter {
|
|||||||
pub fn new(grayscale: bool) -> Self {
|
pub fn new(grayscale: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
info: Default::default(),
|
info: Default::default(),
|
||||||
|
fc_getter: CachedFontCharGetter::new(128), //an arbitrary high-ish number for max cache size
|
||||||
buffer: Vec::new(),
|
buffer: Vec::new(),
|
||||||
saved_buffer: None,
|
saved_buffer: None,
|
||||||
rotate_buffer: None,
|
rotate_buffer: None,
|
||||||
@@ -89,7 +89,7 @@ impl FramebufferWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.rotate_buffer = Some(output_array);
|
self.rotate_buffer = Some(output_array);
|
||||||
&self.rotate_buffer.as_ref().unwrap()
|
self.rotate_buffer.as_ref().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_buffer(&mut self) {
|
pub fn save_buffer(&mut self) {
|
||||||
@@ -108,6 +108,7 @@ impl FramebufferWriter {
|
|||||||
.copy_from_slice(&color[..]);
|
.copy_from_slice(&color[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//straight horizontal line
|
||||||
fn _draw_line(&mut self, start_pos: usize, bytes: &[u8]) {
|
fn _draw_line(&mut self, start_pos: usize, bytes: &[u8]) {
|
||||||
self.buffer[start_pos..(start_pos + bytes.len())]
|
self.buffer[start_pos..(start_pos + bytes.len())]
|
||||||
.copy_from_slice(bytes);
|
.copy_from_slice(bytes);
|
||||||
@@ -127,17 +128,14 @@ 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;
|
let mut start_pos;
|
||||||
for row in 0..char_info.1.len() {
|
for row in 0..char_info.height {
|
||||||
//char_info.2 is vertical offset
|
start_pos = ((top_left[1] + row + char_info.top_offset as usize) * self.info.stride + top_left[0]) * self.info.bytes_per_pixel;
|
||||||
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.data[row] {
|
||||||
for col in &char_info.1[row] {
|
if col > &0 && start_pos + 3 < self.info.byte_len {
|
||||||
if col > &0 {
|
|
||||||
if start_pos + 3 < self.info.byte_len {
|
|
||||||
self._draw_pixel(start_pos, color_with_alpha(color, bg_color, *col));
|
self._draw_pixel(start_pos, color_with_alpha(color, bg_color, *col));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
start_pos += self.info.bytes_per_pixel;
|
start_pos += self.info.bytes_per_pixel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,8 +145,10 @@ impl FramebufferWriter {
|
|||||||
|
|
||||||
pub fn draw_pixel(&mut self, point: Point, color: RGBColor) {
|
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;
|
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);
|
self._draw_pixel(start_pos, color);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//shapes
|
//shapes
|
||||||
|
|
||||||
@@ -223,8 +223,8 @@ impl FramebufferWriter {
|
|||||||
if c == ' ' {
|
if c == ' ' {
|
||||||
top_left[0] += mono_width.unwrap_or(5) as usize;
|
top_left[0] += mono_width.unwrap_or(5) as usize;
|
||||||
} else {
|
} else {
|
||||||
let char_info = get_font_char_from_fonts(&fonts, c);
|
let char_info = self.fc_getter.get(&fonts, c);
|
||||||
let char_width = char_info.1[0].len();
|
let char_width = char_info.width;
|
||||||
let add_after: usize;
|
let add_after: usize;
|
||||||
if let Some(mono_width) = mono_width {
|
if let Some(mono_width) = mono_width {
|
||||||
let mono_width = mono_width as usize;
|
let mono_width = mono_width as usize;
|
||||||
@@ -244,6 +244,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
|
//bmps
|
||||||
|
|
||||||
//reverse is workaround for when my bmp lib returns rgba instead of bgra
|
//reverse is workaround for when my bmp lib returns rgba instead of bgra
|
||||||
29
wm/src/fs.rs
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
|
pub use linux;
|
||||||
|
|
||||||
pub mod framebuffer;
|
pub mod framebuffer;
|
||||||
pub mod window_manager;
|
pub mod window_manager;
|
||||||
pub mod components;
|
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
mod proxy_window_like;
|
mod proxy_window_like;
|
||||||
mod essential;
|
mod essential;
|
||||||
@@ -88,7 +88,7 @@ impl ProxyWindowLike {
|
|||||||
if let Some(buffer) = buffer.stdout.as_mut() {
|
if let Some(buffer) = buffer.stdout.as_mut() {
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
let mut reader = BufReader::new(buffer);
|
let mut reader = BufReader::new(buffer);
|
||||||
if let Ok(_) = reader.read_line(&mut output) {
|
if reader.read_line(&mut output).is_ok() {
|
||||||
output
|
output
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
@@ -6,11 +6,12 @@ use std::boxed::Box;
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use linux::fb::Framebuffer;
|
use linux::fb::Framebuffer;
|
||||||
use ming_wm_lib::framebuffer_types::{ Point, Dimensions };
|
use ming_wm_lib::framebuffer_types::{ Point, Dimensions };
|
||||||
use ming_wm_lib::themes::{ Themes, get_theme_info };
|
use ming_wm_lib::themes::{ Themes, get_theme_info };
|
||||||
use ming_wm_lib::utils::{ min, point_inside };
|
use ming_wm_lib::utils::{ min, point_inside, trunc_words };
|
||||||
use ming_wm_lib::messages::*;
|
use ming_wm_lib::messages::*;
|
||||||
use ming_wm_lib::dirs::config_dir;
|
use ming_wm_lib::dirs::config_dir;
|
||||||
use ming_wm_lib::window_manager_types::*;
|
use ming_wm_lib::window_manager_types::*;
|
||||||
@@ -24,7 +25,6 @@ use crate::essential::start_menu::StartMenu;
|
|||||||
use crate::essential::about::About;
|
use crate::essential::about::About;
|
||||||
use crate::essential::help::Help;
|
use crate::essential::help::Help;
|
||||||
use crate::essential::onscreen_keyboard::OnscreenKeyboard;
|
use crate::essential::onscreen_keyboard::OnscreenKeyboard;
|
||||||
//use crate::logging::log;
|
|
||||||
|
|
||||||
//todo: a lot of the usize should be changed to u16
|
//todo: a lot of the usize should be changed to u16
|
||||||
|
|
||||||
@@ -40,6 +40,7 @@ struct WindowLikeInfo {
|
|||||||
id: usize,
|
id: usize,
|
||||||
window_like: WindowBox,
|
window_like: WindowBox,
|
||||||
top_left: Point,
|
top_left: Point,
|
||||||
|
old_top_left: Point,
|
||||||
dimensions: Dimensions,
|
dimensions: Dimensions,
|
||||||
workspace: Workspace,
|
workspace: Workspace,
|
||||||
fullscreen: bool,
|
fullscreen: bool,
|
||||||
@@ -51,6 +52,7 @@ impl fmt::Debug for WindowLikeInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct WindowManager {
|
pub struct WindowManager {
|
||||||
writer: RefCell<FramebufferWriter>,
|
writer: RefCell<FramebufferWriter>,
|
||||||
rotate: bool,
|
rotate: bool,
|
||||||
@@ -65,12 +67,14 @@ pub struct WindowManager {
|
|||||||
current_workspace: u8,
|
current_workspace: u8,
|
||||||
framebuffer: Framebuffer,
|
framebuffer: Framebuffer,
|
||||||
clipboard: Option<String>,
|
clipboard: Option<String>,
|
||||||
|
version: String,
|
||||||
|
password_hash: [u8; 64],
|
||||||
}
|
}
|
||||||
|
|
||||||
//1 is up, 2 is down
|
//1 is up, 2 is down
|
||||||
|
|
||||||
impl WindowManager {
|
impl WindowManager {
|
||||||
pub fn new(writer: FramebufferWriter, framebuffer: Framebuffer, dimensions: Dimensions, 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);
|
//println!("bg: {}x{}", dimensions[0], dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT);
|
||||||
let mut wm = WindowManager {
|
let mut wm = WindowManager {
|
||||||
writer: RefCell::new(writer),
|
writer: RefCell::new(writer),
|
||||||
@@ -86,6 +90,8 @@ impl WindowManager {
|
|||||||
current_workspace: 0,
|
current_workspace: 0,
|
||||||
framebuffer,
|
framebuffer,
|
||||||
clipboard: None,
|
clipboard: None,
|
||||||
|
version,
|
||||||
|
password_hash,
|
||||||
};
|
};
|
||||||
wm.lock();
|
wm.lock();
|
||||||
wm.change_theme();
|
wm.change_theme();
|
||||||
@@ -95,7 +101,7 @@ impl WindowManager {
|
|||||||
pub fn add_window_like(&mut self, mut window_like: Box<dyn WindowLike>, top_left: Point, dimensions: Option<Dimensions>) {
|
pub fn add_window_like(&mut self, mut window_like: Box<dyn WindowLike>, top_left: Point, dimensions: Option<Dimensions>) {
|
||||||
let subtype = window_like.subtype();
|
let subtype = window_like.subtype();
|
||||||
let dimensions = dimensions.unwrap_or(window_like.ideal_dimensions(self.dimensions));
|
let dimensions = dimensions.unwrap_or(window_like.ideal_dimensions(self.dimensions));
|
||||||
self.id_count = self.id_count + 1;
|
self.id_count += 1;
|
||||||
let id = self.id_count;
|
let id = self.id_count;
|
||||||
window_like.handle_message(WindowMessage::Init(dimensions));
|
window_like.handle_message(WindowMessage::Init(dimensions));
|
||||||
let dimensions = if subtype == WindowLikeType::Window { [dimensions[0], dimensions[1] + WINDOW_TOP_HEIGHT] } else { dimensions };
|
let dimensions = if subtype == WindowLikeType::Window { [dimensions[0], dimensions[1] + WINDOW_TOP_HEIGHT] } else { dimensions };
|
||||||
@@ -103,6 +109,7 @@ impl WindowManager {
|
|||||||
id,
|
id,
|
||||||
window_like,
|
window_like,
|
||||||
top_left,
|
top_left,
|
||||||
|
old_top_left: top_left,
|
||||||
dimensions,
|
dimensions,
|
||||||
workspace: if subtype == WindowLikeType::Window {
|
workspace: if subtype == WindowLikeType::Window {
|
||||||
Workspace::Workspace(self.current_workspace)
|
Workspace::Workspace(self.current_workspace)
|
||||||
@@ -136,7 +143,7 @@ impl WindowManager {
|
|||||||
fn lock(&mut self) {
|
fn lock(&mut self) {
|
||||||
self.locked = true;
|
self.locked = true;
|
||||||
self.window_infos = Vec::new();
|
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) {
|
fn unlock(&mut self) {
|
||||||
@@ -154,7 +161,7 @@ impl WindowManager {
|
|||||||
file.read_to_string(&mut contents).unwrap();
|
file.read_to_string(&mut contents).unwrap();
|
||||||
let lines: Vec<&str> = contents.split("\n").collect();
|
let lines: Vec<&str> = contents.split("\n").collect();
|
||||||
if lines.len() > self.current_workspace.into() {
|
if lines.len() > self.current_workspace.into() {
|
||||||
self.theme = Themes::from_str(lines[self.current_workspace as usize]).unwrap_or(Default::default());
|
self.theme = Themes::from_str(lines[self.current_workspace as usize]).unwrap_or_default();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,8 +169,9 @@ impl WindowManager {
|
|||||||
//if off_only is true, also handle request
|
//if off_only is true, also handle request
|
||||||
//written confusingly but it works I promise
|
//written confusingly but it works I promise
|
||||||
fn toggle_start_menu(&mut self, off_only: bool) -> WindowMessageResponse {
|
fn toggle_start_menu(&mut self, off_only: bool) -> WindowMessageResponse {
|
||||||
let start_menu_exists = self.window_infos.iter().find(|w| w.window_like.subtype() == WindowLikeType::StartMenu).is_some();
|
let start_menu_exists = self.window_infos.iter().any(|w| w.window_like.subtype() == WindowLikeType::StartMenu);
|
||||||
if (start_menu_exists && off_only) || !off_only {
|
//if (start_menu_exists && off_only) || !off_only {
|
||||||
|
if start_menu_exists || !off_only {
|
||||||
let taskbar_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::Taskbar).unwrap();
|
let taskbar_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::Taskbar).unwrap();
|
||||||
self.focused_id = self.window_infos[taskbar_index].id;
|
self.focused_id = self.window_infos[taskbar_index].id;
|
||||||
if off_only {
|
if off_only {
|
||||||
@@ -456,17 +464,20 @@ impl WindowManager {
|
|||||||
let window_like = &self.window_infos[focused_index].window_like;
|
let window_like = &self.window_infos[focused_index].window_like;
|
||||||
if window_like.subtype() == WindowLikeType::Window && window_like.resizable() {
|
if window_like.subtype() == WindowLikeType::Window && window_like.resizable() {
|
||||||
//toggle fullscreen
|
//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
|
//todo: send message to window about resize
|
||||||
let new_dimensions;
|
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];
|
new_dimensions = [self.dimensions[0], self.dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT];
|
||||||
self.window_infos[focused_index].top_left = [0, INDICATOR_HEIGHT];
|
window_info.old_top_left = window_info.top_left;
|
||||||
redraw_ids = Some(vec![self.window_infos[focused_index].id]);
|
window_info.top_left = [0, INDICATOR_HEIGHT];
|
||||||
|
redraw_ids = Some(vec![window_info.id]);
|
||||||
} else {
|
} 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;
|
press_response = WindowMessageResponse::JustRedraw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -588,7 +599,7 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
let w: Option<WindowBox> = match w.as_str() {
|
let w: Option<WindowBox> = match w.as_str() {
|
||||||
"StartMenu" => Some(Box::new(StartMenu::new())),
|
"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())),
|
"Help" => Some(Box::new(Help::new())),
|
||||||
_ => Some(Box::new(ProxyWindowLike::new(&w))),
|
_ => Some(Box::new(ProxyWindowLike::new(&w))),
|
||||||
};
|
};
|
||||||
@@ -642,6 +653,13 @@ impl WindowManager {
|
|||||||
[top_left[0], top_left[1] + if is_window { WINDOW_TOP_HEIGHT } else { 0 }]
|
[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
|
//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) {
|
pub fn draw(&mut self, maybe_redraw_ids: Option<Vec<usize>>, use_saved_buffer: bool) {
|
||||||
let theme_info = get_theme_info(&self.theme).unwrap();
|
let theme_info = get_theme_info(&self.theme).unwrap();
|
||||||
@@ -667,8 +685,7 @@ impl WindowManager {
|
|||||||
});
|
});
|
||||||
//these are needed to decide when to snapshot
|
//these are needed to decide when to snapshot
|
||||||
let max_index = if redraw_ids.len() > 0 { redraw_ids.len() } else { maybe_length } - 1;
|
let max_index = if redraw_ids.len() > 0 { redraw_ids.len() } else { maybe_length } - 1;
|
||||||
let mut w_index = 0;
|
for (w_index, window_info) in redraw_windows.enumerate() {
|
||||||
for window_info in redraw_windows {
|
|
||||||
let window_dimensions = if window_info.fullscreen {
|
let window_dimensions = if window_info.fullscreen {
|
||||||
[self.dimensions[0], self.dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT]
|
[self.dimensions[0], self.dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT]
|
||||||
} else {
|
} else {
|
||||||
@@ -689,18 +706,21 @@ 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::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::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::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();
|
}).collect();
|
||||||
//draw window background
|
//draw window background
|
||||||
instructions.push_front(DrawInstructions::Rect([0, 0], window_dimensions, theme_info.background));
|
instructions.push_front(DrawInstructions::Rect([0, 0], window_dimensions, theme_info.background));
|
||||||
//draw window top decorations and what not
|
//draw window top decorations and what not
|
||||||
|
let title = trunc_words(&["nimbus-roman".to_string()], window_info.window_like.title(), None, window_dimensions[0]);
|
||||||
instructions.extend(vec![
|
instructions.extend(vec![
|
||||||
//left top border
|
//left top border
|
||||||
DrawInstructions::Rect([0, 0], [window_dimensions[0], 1], theme_info.border_left_top),
|
DrawInstructions::Rect([0, 0], [window_dimensions[0], 1], theme_info.border_left_top),
|
||||||
DrawInstructions::Rect([0, 0], [1, window_dimensions[1]], theme_info.border_left_top),
|
DrawInstructions::Rect([0, 0], [1, window_dimensions[1]], theme_info.border_left_top),
|
||||||
//top
|
//top
|
||||||
DrawInstructions::Rect([1, 1], [window_dimensions[0] - 2, WINDOW_TOP_HEIGHT - 3], theme_info.top),
|
DrawInstructions::Rect([1, 1], [window_dimensions[0] - 2, WINDOW_TOP_HEIGHT - 3], theme_info.top),
|
||||||
DrawInstructions::Text([4, 4], vec!["nimbus-roman".to_string()], window_info.window_like.title().to_string(), theme_info.top_text, theme_info.top, None, None),
|
//window title
|
||||||
|
DrawInstructions::Text([4, 4], vec!["nimbus-roman".to_string()], title, theme_info.top_text, theme_info.top, None, None),
|
||||||
//top bottom border
|
//top bottom border
|
||||||
DrawInstructions::Rect([1, WINDOW_TOP_HEIGHT - 2], [window_dimensions[0] - 2, 2], theme_info.border_left_top),
|
DrawInstructions::Rect([1, WINDOW_TOP_HEIGHT - 2], [window_dimensions[0] - 2, 2], theme_info.border_left_top),
|
||||||
//right bottom border
|
//right bottom border
|
||||||
@@ -724,10 +744,7 @@ impl WindowManager {
|
|||||||
match instruction {
|
match instruction {
|
||||||
DrawInstructions::Rect(top_left, dimensions, color) => {
|
DrawInstructions::Rect(top_left, dimensions, color) => {
|
||||||
//try and prevent overflows out of the window
|
//try and prevent overflows out of the window
|
||||||
let true_dimensions = [
|
let true_dimensions = WindowManager::prevent_overflow_dimensions(dimensions, window_dimensions, top_left);
|
||||||
min(dimensions[0], window_dimensions[0] - top_left[0]),
|
|
||||||
min(dimensions[1], window_dimensions[1] - top_left[1]),
|
|
||||||
];
|
|
||||||
window_writer.draw_rect(top_left, true_dimensions, color);
|
window_writer.draw_rect(top_left, true_dimensions, color);
|
||||||
},
|
},
|
||||||
DrawInstructions::Circle(centre, radius, color) => {
|
DrawInstructions::Circle(centre, radius, color) => {
|
||||||
@@ -741,12 +758,15 @@ impl WindowManager {
|
|||||||
window_writer.draw_bmp(top_left, path, reverse);
|
window_writer.draw_bmp(top_left, path, reverse);
|
||||||
},
|
},
|
||||||
DrawInstructions::Gradient(top_left, dimensions, start_color, end_color, steps) => {
|
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);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.writer.borrow_mut().draw_buffer(window_info.top_left, window_dimensions[1], window_dimensions[0] * bytes_per_pixel, &window_writer.get_buffer());
|
self.writer.borrow_mut().draw_buffer(window_info.top_left, window_dimensions[1], window_dimensions[0] * bytes_per_pixel, &window_writer.get_buffer());
|
||||||
w_index += 1;
|
|
||||||
}
|
}
|
||||||
//could probably figure out a way to do borrow() when self.rotate is false but does it matter?
|
//could probably figure out a way to do borrow() when self.rotate is false but does it matter?
|
||||||
let mut writer_borrow = self.writer.borrow_mut();
|
let mut writer_borrow = self.writer.borrow_mut();
|
||||||