Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec5cba13c8 | ||
|
|
4c4c9a1b35 | ||
|
|
6cc8e06e89 | ||
|
|
dfe065d26a | ||
|
|
c1afd3f33e | ||
|
|
724ffbd494 | ||
|
|
7c6a7d6b6d | ||
|
|
667b4cd2d9 | ||
|
|
c5a41244b4 | ||
|
|
d32b82a2bb |
22
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ming-wm"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
repository = "https://github.com/stjet/ming-wm"
|
||||
license = "GPL-3.0-or-later"
|
||||
edition = "2021"
|
||||
@@ -9,29 +9,25 @@ default-run = "ming"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[workspace]
|
||||
members = ["linux"]
|
||||
members = [ "wm", "linux" ]
|
||||
|
||||
[build-dependencies]
|
||||
bmp-rust = "0.5.0"
|
||||
blake2 = { version = "0.10.6", default-features = false }
|
||||
bitcoin_hashes = { version = "0.16.0", default-features = false }
|
||||
|
||||
[dependencies]
|
||||
ming-wm-lib = { path = "ming-wm-lib" }
|
||||
blake2 = { version = "0.10.6", default-features = false }
|
||||
linux = { path = "linux" }
|
||||
termion = { version = "4.0.3", optional = true }
|
||||
wm = { path = "wm", optional = true }
|
||||
linux = { path = "linux", optional = true }
|
||||
rodio = { version = "0.19.0", default-features = false, features = [ "flac", "mp3", "symphonia-vorbis", "wav" ], optional = true }
|
||||
rand = { version = "0.9.0", default-features = false, features = [ "small_rng" ], optional = true }
|
||||
id3 = { version = "1.10.0", optional = true }
|
||||
mp4ameta = { version = "0.11.0", optional = true }
|
||||
metaflac = { version = "0.2.5", optional = true }
|
||||
bmp-rust = "0.5.0"
|
||||
pty-process = { version = "0.5.1", optional = true }
|
||||
|
||||
[features]
|
||||
default = [ "main", "terminal" ]
|
||||
main = [ "termion" ]
|
||||
terminal = [ "pty-process" ]
|
||||
default = [ "wm", "terminal" ]
|
||||
terminal = [ "linux" ]
|
||||
audio_player = [ "id3", "mp4ameta", "metaflac", "rand", "rodio" ]
|
||||
|
||||
[profile.release]
|
||||
@@ -39,8 +35,8 @@ lto = true
|
||||
|
||||
[[bin]]
|
||||
name = "ming"
|
||||
path = "src/bin/main.rs"
|
||||
required-features = [ "main" ]
|
||||
path = "src/bin/wm.rs"
|
||||
required-features = [ "wm" ]
|
||||
|
||||
[[bin]]
|
||||
name = "mingFiles_Audio_Player"
|
||||
|
||||
14
README.md
@@ -1,4 +1,4 @@
|
||||
Ming-wm is a keyboard-based, retro-themed window manager for Linux. It is neither for Wayland or the X Window System - it writes directly to the framebuffer. Inspirations include i3, Haiku, SerenityOS, and Windows98, and it is a conceptual successor to the previous [mingde](https://github.com/stjet/mingde) and [ming-os](https://github.com/stjet/ming-os).
|
||||
Ming-wm is a keyboard-based, retro-themed window manager for Linux. It is neither for Wayland or the X Window System - it writes directly to the framebuffer. Inspirations include i3, Haiku, SerenityOS, and Windows98, and it is a conceptual successor to my previous projects [ming-de](https://github.com/stjet/mingde) and [ming-os](https://github.com/stjet/ming-os).
|
||||
|
||||

|
||||

|
||||
@@ -49,9 +49,9 @@ Usage for most of the included windows and window-likes are included in `docs/wi
|
||||
|
||||
## Running on Mobile Linux
|
||||
|
||||
Running with an onscreen keyboard. The framebuffer may not be redrawn to the screen without a (real) key press. The volume down button seems to work. If someone knows why this is the case, and/or how to fix this, please let me know.
|
||||
More or the less the same, but includes with an onscreen keyboard for touchscreens.
|
||||
|
||||
`evtest` needs to be installed. Currently, the input device is assumed to be at `/dev/first-touchscreen`.
|
||||
Currently, the touchscreen input device is assumed to be at `/dev/input/by-path/first-touchscreen`, but this is easily editable (see `src/bin/wm.rs`). For touchscreen support, the user running `ming` needs to have read permissions for that `/dev/input/` file.
|
||||
|
||||
```
|
||||
ming touch
|
||||
@@ -73,12 +73,18 @@ See [/docs/philosophy.md](/docs/philosophy.md) for some hopefully interesting ra
|
||||
|
||||
Windows (may be called apps in other window managers) can be developed in any language, though it is easiest to do so in Rust because the `ming-wm-lib` crate can be used.
|
||||
|
||||
See [koxinga](https://github.com/stjet/koxinga) or `src/bin` for examples. The `docs` directory includes a [brief introduction to writing windows](docs/system/writing_windows.md), and (incomplete) documentation on the workings of ming-wm.
|
||||
The `docs` directory includes a [brief introduction to writing windows](docs/system/writing_windows.md), and (incomplete) documentation on the workings of ming-wm.
|
||||
|
||||
See [koxinga](https://github.com/stjet/koxinga) or `src/bin` for examples.
|
||||
|
||||
A (very poorly written, and WIP) window is being written in Lisp Scheme: [ming-flashcards](https://github.com/stjet/ming-flashcards).
|
||||
|
||||
## Security
|
||||
|
||||
Make sure the permissions of `password.env` are so other users cannot read or write to it. If there is no plan to recompile, just delete it.
|
||||
|
||||
Understand the implications of adding the user to the `video` group. And if the permissions of a `/dev/input/` file was changed for touchscreen support, understand those implications too.
|
||||
|
||||
Obviously, don't run the executable with `sudo` or `doas`, or as the root user!
|
||||
|
||||
## License
|
||||
|
||||
|
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 |
13
build.rs
@@ -4,7 +4,7 @@ use std::env;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
use blake2::{ Blake2b512, Digest };
|
||||
use bitcoin_hashes::Sha512;
|
||||
use bmp_rust::bmp::BMP;
|
||||
|
||||
fn font_chars_to_alphas(dir: &str) {
|
||||
@@ -15,8 +15,7 @@ fn font_chars_to_alphas(dir: &str) {
|
||||
if file_name.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
if file_name[1] == "bmp" {
|
||||
if !path.is_dir() {
|
||||
if file_name[1] == "bmp" && !path.is_dir() {
|
||||
let mut ch: Vec<Vec<String>> = Vec::new();
|
||||
let b = BMP::new_from_file(&path.clone().into_os_string().into_string().unwrap()).unwrap();
|
||||
let dib_header = b.get_dib_header().unwrap();
|
||||
@@ -44,17 +43,15 @@ fn font_chars_to_alphas(dir: &str) {
|
||||
).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
//hash + "salt" password and add to build
|
||||
let password = read_to_string("password.env").unwrap_or("password".to_string()).replace("\n", "") + "salt?sorrycryptographers";
|
||||
let mut hasher = Blake2b512::new();
|
||||
hasher.update(password.as_bytes());
|
||||
let password = read_to_string("password.env").unwrap_or("password".to_string()).replace("\n", "") + "salt?sorrycryptographers!1!";
|
||||
let hash = Sha512::hash(password.as_bytes()).to_byte_array();
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let dest_path = Path::new(&out_dir).join("password.rs");
|
||||
write(&dest_path, format!("pub const PASSWORD_HASH: [u8; 64] = {:?};", hasher.finalize())).unwrap();
|
||||
write(&dest_path, format!("pub const PASSWORD_HASH: [u8; 64] = {:?};", hash)).unwrap();
|
||||
//process bmps
|
||||
for entry in read_dir("./bmps").unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
|
||||
@@ -69,5 +69,10 @@ More often than not, not relying on dependencies removes unnecessary bloat and c
|
||||
Expect to see more dependencies in Cargo.toml eliminated soon.
|
||||
|
||||
PS:
|
||||
1. `rodio` is unlikely to ever be eliminated (simply because audio is *complex*), and it's optional (if the audio player is not wanted)
|
||||
2.`bmp-rust` is written by me and so isn't technically an external dependency
|
||||
1. `rodio` is unlikely to ever be eliminated (simply because audio is *complex*), and it's optional (if the audio player is not wanted). The dependency which provides hashing will also not be removed as I am not a cryptographer and not (quite) stupid enough to pretend being one
|
||||
2. `bmp-rust` is written by me and so isn't technically an external dependency
|
||||
|
||||
> ### Update
|
||||
> Ignoring the audio player dependencies and `bmp-rust`, the only dependencies not written by me are now `libc` for libc Rust bindings and `bitcoin-hashes` for SHA-512 password hashing (yes, I know SHA-512 is not the greatest password hash function, but it is lightweight, relatively).
|
||||
>
|
||||
> Prior dependencies like `termion`, `linux_framebuffer`, `pty-process`, `evtest` have been removed and replaced by a me-written version in the `linux` crate. As of v1.1, the dependency removing goal has been achieved. Huzzah!
|
||||
|
||||
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+K: Move window to top 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+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.
|
||||
|
||||
> In the case of `WindowMessage::Request(WindowManagerRequest::ClipboardCopy(<copy_string>))`, windows should convert any `\n` into `𐘂` when copying to clipboard and vice versa when pasting, in order to allow for multi-line clipboard contents.
|
||||
|
||||
## Hello, World!
|
||||
|
||||
A minimal example using `ming-wm-lib`.
|
||||
|
||||
@@ -14,6 +14,8 @@ Type to write commands, backspace to delete last character, and enter to run com
|
||||
|
||||
Tab completion is supported for the `<dir>` and `<dir / playlist file>` arguments.
|
||||
|
||||
The copy shortcut will copy the currently playing song's file name, if there is a currently playing song.
|
||||
|
||||
## Playlists
|
||||
|
||||
Example playlist file:
|
||||
|
||||
@@ -12,6 +12,7 @@ It is probably best to read a Vim tutorial for the basics. All supportd keystrok
|
||||
- `t[abe] <file>`, `[tab]n`, `[tab]p`
|
||||
- `q[uit]`
|
||||
- `w[rite]`
|
||||
- `/<query>`
|
||||
|
||||
Tab completion is supported for the `<file>` argument.
|
||||
|
||||
@@ -23,6 +24,7 @@ Tab completion is supported for the `<file>` argument.
|
||||
- `r`
|
||||
- `dd`
|
||||
- `dw`
|
||||
- `d$`
|
||||
- `G`
|
||||
- `gg`
|
||||
- `<number>gg`
|
||||
@@ -32,6 +34,7 @@ Tab completion is supported for the `<file>` argument.
|
||||
- `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`
|
||||
- `0`, `^`, `$`
|
||||
- `%`
|
||||
|
||||
### 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
|
||||
|
||||
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
|
||||
|
||||
|
||||
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 n.is_ok() {
|
||||
//Alt+<char> sends Esc+<char>
|
||||
Key::Alt(char::from(n.unwrap()))
|
||||
} else {
|
||||
Key::Esc
|
||||
}
|
||||
},
|
||||
127 => {
|
||||
Key::Backspace
|
||||
},
|
||||
_ => {
|
||||
Key::Char(char::from(first))
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,5 @@
|
||||
pub mod fb;
|
||||
|
||||
pub mod raw;
|
||||
pub mod keys;
|
||||
pub mod pty;
|
||||
pub mod input;
|
||||
|
||||
55
linux/src/pty.rs
Normal file
@@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ming-wm-lib"
|
||||
version = "0.1.5"
|
||||
version = "0.2.0"
|
||||
repository = "https://github.com/stjet/ming-wm"
|
||||
description = "library for building windows for ming-wm in rust"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -2,10 +2,10 @@ use std::vec;
|
||||
use std::vec::Vec;
|
||||
|
||||
use crate::components::Component;
|
||||
use ming_wm_lib::framebuffer_types::{ Dimensions, Point };
|
||||
use ming_wm_lib::themes::ThemeInfo;
|
||||
use ming_wm_lib::messages::WindowMessage;
|
||||
use ming_wm_lib::window_manager_types::DrawInstructions;
|
||||
use crate::framebuffer_types::{ Dimensions, Point };
|
||||
use crate::themes::ThemeInfo;
|
||||
use crate::messages::WindowMessage;
|
||||
use crate::window_manager_types::DrawInstructions;
|
||||
|
||||
pub struct HighlightButton<T> {
|
||||
name_: String,
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::vec::Vec;
|
||||
|
||||
use ming_wm_lib::themes::ThemeInfo;
|
||||
use ming_wm_lib::messages::WindowMessage;
|
||||
use ming_wm_lib::window_manager_types::DrawInstructions;
|
||||
use crate::themes::ThemeInfo;
|
||||
use crate::messages::WindowMessage;
|
||||
use crate::window_manager_types::DrawInstructions;
|
||||
|
||||
pub mod toggle_button;
|
||||
pub mod highlight_button;
|
||||
@@ -1,12 +1,11 @@
|
||||
use std::vec;
|
||||
use std::vec::Vec;
|
||||
|
||||
|
||||
use ming_wm_lib::framebuffer_types::{ Dimensions, Point };
|
||||
use ming_wm_lib::themes::ThemeInfo;
|
||||
use ming_wm_lib::messages::WindowMessage;
|
||||
use ming_wm_lib::window_manager_types::DrawInstructions;
|
||||
use ming_wm_lib::utils::calc_actual_lines;
|
||||
use crate::framebuffer_types::{ Dimensions, Point };
|
||||
use crate::themes::ThemeInfo;
|
||||
use crate::messages::WindowMessage;
|
||||
use crate::window_manager_types::DrawInstructions;
|
||||
use crate::utils::calc_actual_lines;
|
||||
use crate::components::Component;
|
||||
|
||||
const MONO_WIDTH: u8 = 10;
|
||||
@@ -2,10 +2,10 @@ use std::vec;
|
||||
use std::vec::Vec;
|
||||
|
||||
use crate::components::Component;
|
||||
use ming_wm_lib::framebuffer_types::{ Dimensions, Point };
|
||||
use ming_wm_lib::themes::ThemeInfo;
|
||||
use ming_wm_lib::messages::WindowMessage;
|
||||
use ming_wm_lib::window_manager_types::DrawInstructions;
|
||||
use crate::framebuffer_types::{ Dimensions, Point };
|
||||
use crate::themes::ThemeInfo;
|
||||
use crate::messages::WindowMessage;
|
||||
use crate::window_manager_types::DrawInstructions;
|
||||
|
||||
const MONO_WIDTH: u8 = 10;
|
||||
|
||||
@@ -2,10 +2,10 @@ use std::vec;
|
||||
use std::vec::Vec;
|
||||
|
||||
use crate::components::Component;
|
||||
use ming_wm_lib::framebuffer_types::{ Dimensions, Point };
|
||||
use ming_wm_lib::themes::ThemeInfo;
|
||||
use ming_wm_lib::messages::WindowMessage;
|
||||
use ming_wm_lib::window_manager_types::DrawInstructions;
|
||||
use crate::framebuffer_types::{ Dimensions, Point };
|
||||
use crate::themes::ThemeInfo;
|
||||
use crate::messages::WindowMessage;
|
||||
use crate::window_manager_types::DrawInstructions;
|
||||
|
||||
pub struct ToggleButton<T> {
|
||||
name_: String,
|
||||
@@ -58,22 +58,24 @@ pub fn listen(mut window_like: impl WindowLike) {
|
||||
let arg = &parts.collect::<Vec<&str>>().join(" ");
|
||||
let output = match method {
|
||||
"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" => {
|
||||
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" => {
|
||||
format!("{}", window_like.title())
|
||||
window_like.title().to_string()
|
||||
},
|
||||
"resizable" => {
|
||||
format!("{}", window_like.resizable())
|
||||
window_like.resizable().to_string()
|
||||
},
|
||||
"subtype" => {
|
||||
format!("{}", &window_like.subtype().serialize())
|
||||
window_like.subtype().serialize().to_string()
|
||||
},
|
||||
"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(),
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ pub mod themes;
|
||||
pub mod serialize;
|
||||
pub mod messages;
|
||||
pub mod ipc;
|
||||
pub mod components;
|
||||
pub mod dirs;
|
||||
pub mod utils;
|
||||
pub mod logging;
|
||||
|
||||
@@ -39,10 +39,10 @@ fn option_to_string<T: Display>(option: &Option<T>) -> String {
|
||||
fn get_color(serialized: &str) -> Result<[u8; 3], ()> {
|
||||
let rgb = serialized.split("\x1F");
|
||||
let mut color = [0; 3];
|
||||
let mut c_i = 0;
|
||||
//won't return error if rgb is 0, 1, or 2 elements.
|
||||
//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 {
|
||||
return Err(());
|
||||
}
|
||||
@@ -51,7 +51,6 @@ fn get_color(serialized: &str) -> Result<[u8; 3], ()> {
|
||||
} else {
|
||||
return Err(());
|
||||
}
|
||||
c_i += 1;
|
||||
}
|
||||
Ok(color)
|
||||
}
|
||||
@@ -68,7 +67,7 @@ fn get_two_array(serialized: &str) -> Result<[usize; 2], ()> {
|
||||
}
|
||||
return Err(());
|
||||
}
|
||||
return Ok(a);
|
||||
Ok(a)
|
||||
}
|
||||
|
||||
pub trait Serializable {
|
||||
@@ -87,9 +86,8 @@ impl Serializable for ThemeInfo {
|
||||
let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized };
|
||||
let mut theme_info: ThemeInfo = Default::default();
|
||||
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
|
||||
for a in arrays {
|
||||
for (a_i, a) in arrays.enumerate() {
|
||||
if a_i == 9 {
|
||||
return Err(());
|
||||
}
|
||||
@@ -127,7 +125,6 @@ impl Serializable for ThemeInfo {
|
||||
if a_i == 8 {
|
||||
return Ok(theme_info);
|
||||
}
|
||||
a_i += 1;
|
||||
}
|
||||
Err(())
|
||||
}
|
||||
@@ -149,7 +146,7 @@ impl Serializable for WindowMessageResponse {
|
||||
WindowMessageResponse::Request(req) => {
|
||||
let req = match req {
|
||||
WindowManagerRequest::OpenWindow(name) => format!("OpenWindow/{}", name),
|
||||
WindowManagerRequest::ClipboardCopy(name) => format!("ClipboardCopy/{}", name),
|
||||
WindowManagerRequest::ClipboardCopy(copy_string) => format!("ClipboardCopy/{}", copy_string.replace("\n", "𐘂")), //serialised output must be 1 line
|
||||
WindowManagerRequest::CloseStartMenu => "CloseStartMenu".to_string(),
|
||||
WindowManagerRequest::Unlock => "Unlock".to_string(),
|
||||
WindowManagerRequest::Lock => "Lock".to_string(),
|
||||
@@ -220,7 +217,7 @@ impl Serializable for DrawInstructions {
|
||||
match self {
|
||||
//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::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::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)),
|
||||
@@ -649,7 +646,7 @@ impl Serializable for WindowMessage {
|
||||
"FullscreenWindow" => Some(ShortcutType::FullscreenWindow),
|
||||
"HalfWidthWindow" => Some(ShortcutType::HalfWidthWindow),
|
||||
"ClipboardCopy" => Some(ShortcutType::ClipboardCopy),
|
||||
"ClipboardPaste" => Some(ShortcutType::ClipboardPaste(get_rest_of_split(&mut parts, Some("/")))),
|
||||
"ClipboardPaste" => Some(ShortcutType::ClipboardPaste(get_rest_of_split(&mut parts, Some("/")).replace("𐘂", "\n"))),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(shortcut) = shortcut {
|
||||
@@ -674,8 +671,7 @@ impl Serializable for WindowMessage {
|
||||
}
|
||||
let mut w_tuple: (usize, String) = Default::default();
|
||||
let mut w_vec = Vec::new();
|
||||
let mut i = 0;
|
||||
for a in arg2.unwrap().split("\x1F") {
|
||||
for (i, a) in arg2.unwrap().split("\x1F").enumerate() {
|
||||
if i % 2 == 0 {
|
||||
if let Ok(n) = a.parse() {
|
||||
w_tuple.0 = n;
|
||||
@@ -684,7 +680,6 @@ impl Serializable for WindowMessage {
|
||||
w_tuple.1 = a.to_string();
|
||||
w_vec.push(w_tuple.clone());
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
let arg2 = parts2.next();
|
||||
if arg2.is_none() {
|
||||
|
||||
@@ -104,6 +104,6 @@ pub fn get_theme_info(theme: &Themes) -> Option<ThemeInfo> {
|
||||
return Some(pair.1);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ pub trait Substring {
|
||||
fn substring(&self, start: usize, end: usize) -> &str;
|
||||
fn remove(&self, index: usize, len: usize) -> String;
|
||||
fn remove_last(&self) -> String;
|
||||
fn find_substring(&self, substr: &str) -> Option<usize>;
|
||||
}
|
||||
|
||||
impl Substring for String {
|
||||
@@ -29,6 +30,14 @@ impl Substring for String {
|
||||
byte_end += char_length;
|
||||
}
|
||||
&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 {
|
||||
@@ -38,13 +47,26 @@ impl Substring for String {
|
||||
fn remove_last(&self) -> 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
|
||||
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 line_num = 0;
|
||||
for real_line in lines {
|
||||
for (line_num, real_line) in lines.enumerate() {
|
||||
let mut line = real_line.to_string() + if one_extra { " " } else { "" };
|
||||
let mut first = true;
|
||||
loop {
|
||||
@@ -62,7 +84,6 @@ pub fn calc_actual_lines<'a>(lines: impl Iterator<Item = &'a String>, max_chars_
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
line_num += 1;
|
||||
}
|
||||
actual_lines
|
||||
}
|
||||
@@ -132,7 +153,7 @@ pub fn hex_to_u8(c1: char, c2: char) -> u8 {
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -194,3 +215,15 @@ 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,13 +14,12 @@ use mp4ameta;
|
||||
use metaflac;
|
||||
|
||||
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
|
||||
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
|
||||
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType };
|
||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||
use ming_wm_lib::themes::ThemeInfo;
|
||||
use ming_wm_lib::utils::{ concat_paths, path_autocomplete, format_seconds, Substring };
|
||||
use ming_wm_lib::utils::{ concat_paths, get_all_files, path_autocomplete, format_seconds, Substring };
|
||||
use ming_wm_lib::dirs::home;
|
||||
use ming_wm_lib::ipc::listen;
|
||||
use ming_wm::fs::get_all_files;
|
||||
|
||||
fn get_artist(path: &PathBuf) -> Option<String> {
|
||||
let ext = path.extension().unwrap();
|
||||
@@ -80,7 +79,6 @@ pub struct AudioPlayer {
|
||||
|
||||
impl WindowLike for AudioPlayer {
|
||||
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
|
||||
//
|
||||
match message {
|
||||
WindowMessage::Init(dimensions) => {
|
||||
self.dimensions = dimensions;
|
||||
@@ -110,11 +108,34 @@ impl WindowLike for AudioPlayer {
|
||||
} else {
|
||||
return WindowMessageResponse::DoNothing;
|
||||
}
|
||||
} else {
|
||||
} else if key_press.is_regular() {
|
||||
self.command += &key_press.key.to_string();
|
||||
} else {
|
||||
return WindowMessageResponse::DoNothing
|
||||
}
|
||||
WindowMessageResponse::JustRedraw
|
||||
},
|
||||
WindowMessage::Shortcut(shortcut) => {
|
||||
match shortcut {
|
||||
ShortcutType::ClipboardPaste(paste_string) => {
|
||||
self.command += &paste_string.replace("\n", "");
|
||||
WindowMessageResponse::JustRedraw
|
||||
},
|
||||
ShortcutType::ClipboardCopy => {
|
||||
let internal_locked = self.internal.lock().unwrap();
|
||||
let sink_len = internal_locked.sink.len();
|
||||
if sink_len > 0 {
|
||||
let queue = &internal_locked.queue;
|
||||
let current = &queue[queue.len() - sink_len];
|
||||
let current_name = current.0.file_name().unwrap().to_string_lossy().into_owned();
|
||||
WindowMessageResponse::Request(WindowManagerRequest::ClipboardCopy(current_name))
|
||||
} else {
|
||||
WindowMessageResponse::DoNothing
|
||||
}
|
||||
},
|
||||
_ => WindowMessageResponse::DoNothing,
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
WindowMessageResponse::DoNothing
|
||||
},
|
||||
|
||||
@@ -61,8 +61,7 @@ impl WindowLike for FileExplorer {
|
||||
WindowMessageResponse::JustRedraw
|
||||
},
|
||||
WindowMessage::KeyPress(key_press) => {
|
||||
self.state = State::List;
|
||||
if key_press.is_enter() {
|
||||
if key_press.is_enter() && self.state == State::List {
|
||||
if self.current_dir_contents.len() > 0 {
|
||||
let selected_entry = &self.current_dir_contents[self.position];
|
||||
if !selected_entry.is_file {
|
||||
@@ -75,6 +74,7 @@ impl WindowLike for FileExplorer {
|
||||
}
|
||||
WindowMessageResponse::DoNothing
|
||||
} 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() {
|
||||
//down
|
||||
if self.position == self.current_dir_contents.len() - 1 {
|
||||
@@ -103,13 +103,22 @@ impl WindowLike for FileExplorer {
|
||||
};
|
||||
WindowMessageResponse::JustRedraw
|
||||
} else if key_press.key == 'i' {
|
||||
if self.state == State::Info {
|
||||
self.state = State::List;
|
||||
} else {
|
||||
self.state = State::Info;
|
||||
let selected_entry = &self.current_dir_contents[self.position];
|
||||
self.metadata = Some(metadata(&selected_entry.path).unwrap());
|
||||
}
|
||||
WindowMessageResponse::JustRedraw
|
||||
} else {
|
||||
if self.state == State::Info {
|
||||
self.state = State::List;
|
||||
WindowMessageResponse::JustRedraw
|
||||
} else {
|
||||
WindowMessageResponse::DoNothing
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => WindowMessageResponse::DoNothing,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::vec::Vec;
|
||||
use std::vec;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{ read_to_string, write };
|
||||
|
||||
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType };
|
||||
@@ -10,7 +11,7 @@ use ming_wm_lib::framebuffer_types::Dimensions;
|
||||
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
|
||||
use ming_wm_lib::utils::{ calc_actual_lines, calc_new_cursor_pos, Substring };
|
||||
use ming_wm_lib::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;
|
||||
|
||||
const MONO_WIDTH: u8 = 10;
|
||||
@@ -175,18 +176,23 @@ impl WindowLike for Malvim {
|
||||
}
|
||||
let new_length = current_file.content[current_file.line_pos].chars().count();
|
||||
current_file.cursor_pos = calc_new_cursor_pos(current_file.cursor_pos, new_length);
|
||||
} else if key_press.key == 'w' {
|
||||
} else if key_press.key == 'w' || key_press.key == '$' {
|
||||
let line = ¤t_file.content[current_file.line_pos];
|
||||
let line_len = line.chars().count();
|
||||
if line_len > 0 && current_file.cursor_pos < line_len {
|
||||
//offset until space or eol
|
||||
let mut line_chars = line.chars().skip(current_file.cursor_pos).peekable();
|
||||
let current_char = line_chars.peek().unwrap().clone();
|
||||
let offset = line_chars.position(|c| if current_char == ' ' {
|
||||
//deref is Copy
|
||||
let current_char = *line_chars.peek().unwrap();
|
||||
let offset = if key_press.key == 'w' {
|
||||
line_chars.position(|c| if current_char == ' ' {
|
||||
c != ' '
|
||||
} else {
|
||||
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);
|
||||
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);
|
||||
@@ -240,6 +246,8 @@ impl WindowLike for Malvim {
|
||||
if current_length == 1 {
|
||||
current_file.cursor_pos = 0;
|
||||
}
|
||||
} else {
|
||||
changed = false;
|
||||
}
|
||||
} 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);
|
||||
@@ -295,7 +303,88 @@ impl WindowLike for Malvim {
|
||||
} else if key_press.key == 'F' {
|
||||
self.state = State::BackFind;
|
||||
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);
|
||||
numbered = true;
|
||||
changed = false;
|
||||
@@ -368,11 +457,21 @@ impl WindowLike for Malvim {
|
||||
ShortcutType::ClipboardPaste(copy_string) => {
|
||||
if self.mode == Mode::Insert {
|
||||
let current_file = &mut self.files[self.current_file_index];
|
||||
for (i, cs) in copy_string.split("\n").enumerate() {
|
||||
if i == 0 {
|
||||
//modify current line
|
||||
let line = ¤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();
|
||||
} else {
|
||||
//insert a new line
|
||||
current_file.content.insert(current_file.line_pos + 1, cs.to_string());
|
||||
current_file.line_pos += 1;
|
||||
current_file.cursor_pos = cs.chars().count();
|
||||
}
|
||||
}
|
||||
self.calc_top_line_pos();
|
||||
self.calc_current(); //too over zealous but whatever
|
||||
self.calc_current();
|
||||
self.files[self.current_file_index].changed = true;
|
||||
WindowMessageResponse::JustRedraw
|
||||
} else {
|
||||
@@ -401,7 +500,7 @@ impl WindowLike for Malvim {
|
||||
let mut used_width = 0;
|
||||
for file_index in 0..self.files.len() {
|
||||
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
|
||||
if future_used_width > self.dimensions[0] {
|
||||
break;
|
||||
@@ -455,12 +554,11 @@ impl WindowLike for Malvim {
|
||||
//bottom blue band stuff
|
||||
//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)));
|
||||
let file_status;
|
||||
if self.files.len() > 0 {
|
||||
file_status = self.files[self.current_file_index].name.clone();
|
||||
let file_status = if self.files.len() > 0 {
|
||||
self.files[self.current_file_index].name.clone()
|
||||
} 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)));
|
||||
//write command or bottom message
|
||||
if self.mode == Mode::Command {
|
||||
@@ -593,12 +691,40 @@ impl Malvim {
|
||||
} else if first.starts_with("/") {
|
||||
let current_file = &mut self.files[self.current_file_index];
|
||||
if current_file.content.len() > 0 {
|
||||
let found_line_no = current_file.content.iter().skip(current_file.line_pos + 1).position(|line| {
|
||||
line.contains(&first[1..])
|
||||
});
|
||||
if let Some(found_line_no) = found_line_no {
|
||||
current_file.line_pos = found_line_no + current_file.line_pos + 1;
|
||||
current_file.cursor_pos = 0;
|
||||
let p1 = if arg == "" {
|
||||
String::new()
|
||||
} else {
|
||||
" ".to_string() + arg
|
||||
};
|
||||
let rest = get_rest_of_split(&mut parts, Some(" "));
|
||||
let rest = if rest == "" {
|
||||
String::new()
|
||||
} else {
|
||||
" ".to_string() + &rest
|
||||
};
|
||||
let query = first[1..].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" {
|
||||
|
||||
@@ -65,15 +65,15 @@ impl WindowLike for Minesweeper {
|
||||
self.first_char = '\0';
|
||||
WindowMessageResponse::DoNothing
|
||||
} 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;
|
||||
}
|
||||
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 y = 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 {
|
||||
loop {
|
||||
self.new_tiles();
|
||||
|
||||
@@ -3,15 +3,16 @@ use std::vec;
|
||||
use std::sync::mpsc::{ channel, Receiver, Sender };
|
||||
use std::thread;
|
||||
use std::process::{ Child, Stdio };
|
||||
use std::process::Command;
|
||||
use std::io::{ Read, Write };
|
||||
use std::time::Duration;
|
||||
use std::path::PathBuf;
|
||||
use std::fmt;
|
||||
|
||||
use pty_process::blocking;
|
||||
use linux::pty::open_pty;
|
||||
|
||||
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
|
||||
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, ShortcutType };
|
||||
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType };
|
||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||
use ming_wm_lib::themes::ThemeInfo;
|
||||
use ming_wm_lib::utils::{ concat_paths, path_autocomplete, Substring };
|
||||
@@ -46,6 +47,11 @@ fn strip_ansi_escape_codes(line: String) -> String {
|
||||
new_line
|
||||
}
|
||||
|
||||
fn bytes_to_string(bytes: Vec<u8>) -> String {
|
||||
let bytes_len = bytes.len();
|
||||
String::from_utf8(bytes).unwrap_or("?".repeat(bytes_len))
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq)]
|
||||
enum Mode {
|
||||
#[default]
|
||||
@@ -78,6 +84,7 @@ pub struct Terminal {
|
||||
current_path: String,
|
||||
running_process: Option<Child>,
|
||||
process_current_line: Vec<u8>, //bytes of line
|
||||
output: String, //current or previous running output of command
|
||||
pty_outerr_rx: Option<Receiver<u8>>,
|
||||
pty_in_tx: Option<Sender<String>>,
|
||||
history: Vec<String>,
|
||||
@@ -116,6 +123,7 @@ impl WindowLike for Terminal {
|
||||
self.history_index = None;
|
||||
self.mode = self.process_command();
|
||||
self.current_input = String::new();
|
||||
self.output = String::new();
|
||||
} else if key_press.key == '\t' { //tab
|
||||
//autocomplete assuming it's a file system path
|
||||
//...mostly working
|
||||
@@ -131,7 +139,7 @@ impl WindowLike for Terminal {
|
||||
self.prev();
|
||||
} else if key_press.is_down_arrow() {
|
||||
self.next();
|
||||
} else {
|
||||
} else if key_press.is_regular() {
|
||||
self.current_input += &key_press.key.to_string();
|
||||
}
|
||||
self.calc_actual_lines();
|
||||
@@ -144,12 +152,17 @@ impl WindowLike for Terminal {
|
||||
loop {
|
||||
if let Ok(ci) = self.pty_outerr_rx.as_mut().unwrap().recv_timeout(Duration::from_millis(5)) {
|
||||
if char::from(ci) == '\n' {
|
||||
let pcl_len = self.process_current_line.len();
|
||||
self.lines.push(strip_ansi_escape_codes(String::from_utf8(self.process_current_line.clone()).unwrap_or("?".repeat(pcl_len))));
|
||||
let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()));
|
||||
self.output += &append_line;
|
||||
self.output += "\n";
|
||||
self.lines.push(append_line);
|
||||
self.process_current_line = Vec::new();
|
||||
} else if char::from(ci) == '\r' {
|
||||
//for now, ignore
|
||||
//
|
||||
} else if char::from(ci) == '\t' {
|
||||
//for now, interpret as space
|
||||
self.process_current_line.push(b' ');
|
||||
} else {
|
||||
self.process_current_line.push(ci);
|
||||
}
|
||||
@@ -163,7 +176,14 @@ impl WindowLike for Terminal {
|
||||
//process exited
|
||||
self.pty_outerr_rx = None;
|
||||
self.mode = Mode::Input;
|
||||
if self.process_current_line.len() > 0 {
|
||||
//add to lines
|
||||
let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()));
|
||||
self.output += &append_line;
|
||||
self.lines.push(append_line);
|
||||
//only need to reset if not empty
|
||||
self.process_current_line = Vec::new();
|
||||
}
|
||||
changed = true;
|
||||
} else {
|
||||
if key_press.key == 'i' {
|
||||
@@ -184,8 +204,9 @@ impl WindowLike for Terminal {
|
||||
} else if key_press.is_enter() {
|
||||
let _ = self.pty_in_tx.as_mut().unwrap().send(self.current_stdin_input.clone());
|
||||
self.mode = Mode::Running;
|
||||
let pcl_len = self.process_current_line.len();
|
||||
self.lines.push(strip_ansi_escape_codes(String::from_utf8(self.process_current_line.clone()).unwrap_or("?".repeat(pcl_len))) + &self.current_stdin_input);
|
||||
let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()) + &self.current_stdin_input);
|
||||
self.output += &append_line;
|
||||
self.lines.push(append_line);
|
||||
self.current_stdin_input = String::new();
|
||||
self.process_current_line = Vec::new();
|
||||
} else if key_press.is_backspace() {
|
||||
@@ -207,6 +228,12 @@ impl WindowLike for Terminal {
|
||||
//kills and running_process is now None
|
||||
let _ = self.running_process.take().unwrap().kill();
|
||||
self.mode = Mode::Input;
|
||||
if self.process_current_line.len() > 0 {
|
||||
let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()));
|
||||
self.output += &append_line;
|
||||
self.lines.push(append_line);
|
||||
self.process_current_line = Vec::new();
|
||||
}
|
||||
WindowMessageResponse::JustRedraw
|
||||
} else if self.mode == Mode::Input && (key_press.key == 'p' || key_press.key == 'n') {
|
||||
//only the last command is saved unlike other terminals. good enough for me
|
||||
@@ -228,6 +255,7 @@ impl WindowLike for Terminal {
|
||||
},
|
||||
WindowMessage::Shortcut(shortcut) => {
|
||||
match shortcut {
|
||||
ShortcutType::ClipboardCopy => WindowMessageResponse::Request(WindowManagerRequest::ClipboardCopy(self.output.clone())),
|
||||
ShortcutType::ClipboardPaste(copy_string) => {
|
||||
if self.mode == Mode::Input || self.mode == Mode::Stdin {
|
||||
if self.mode == Mode::Input {
|
||||
@@ -331,11 +359,13 @@ impl Terminal {
|
||||
}
|
||||
Mode::Input
|
||||
} else {
|
||||
let (pty, pts) = blocking::open().unwrap();
|
||||
self.running_process = Some(blocking::Command::new("sh").arg("-c").arg(&self.current_input).current_dir(&self.current_path).stdin(Stdio::piped()).spawn(pts).unwrap());
|
||||
let (pty, pts) = open_pty().unwrap();
|
||||
let mut cmd = Command::new("sh");
|
||||
let cmd = cmd.arg("-c").arg(&self.current_input).current_dir(&self.current_path).stdin(Stdio::piped());
|
||||
self.running_process = Some(pts.attach_and_spawn(cmd).unwrap());
|
||||
let (tx1, rx1) = channel();
|
||||
thread::spawn(move || {
|
||||
for ci in pty.bytes() {
|
||||
for ci in pty.file.bytes() {
|
||||
if let Ok(ci) = ci {
|
||||
tx1.send(ci).unwrap();
|
||||
} else {
|
||||
@@ -378,8 +408,7 @@ impl Terminal {
|
||||
//must_add_current_line will be false
|
||||
"$ ".to_string() + &self.current_input + "█"
|
||||
} else {
|
||||
let pcl_len = self.process_current_line.len();
|
||||
strip_ansi_escape_codes(String::from_utf8(self.process_current_line.clone()).unwrap_or("?".repeat(pcl_len))) + &self.current_stdin_input.clone() + "█"
|
||||
strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()) + &self.current_stdin_input.clone() + "█")
|
||||
}
|
||||
} else {
|
||||
self.lines[line_num].clone()
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
use std::process::{ Command, Stdio };
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::io::{ stdin, stdout, BufReader, BufRead, Write };
|
||||
use std::io::{ stdin, stdout, Write };
|
||||
use std::process::exit;
|
||||
use std::env;
|
||||
|
||||
use linux::fb::Framebuffer;
|
||||
use termion::input::TermRead;
|
||||
use termion::raw::IntoRawMode;
|
||||
use termion::event::Key;
|
||||
use linux::raw::RawStdout;
|
||||
use linux::keys::{ RawStdin, Key };
|
||||
use linux::input::{ Input, EventType };
|
||||
use wm::framebuffer::{ FramebufferWriter, FramebufferInfo };
|
||||
use wm::window_manager::WindowManager;
|
||||
|
||||
use ming_wm_lib::window_manager_types::KeyChar;
|
||||
use ming_wm_lib::messages::*;
|
||||
use ming_wm::framebuffer::{ FramebufferWriter, FramebufferInfo };
|
||||
use ming_wm::window_manager::WindowManager;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/password.rs"));
|
||||
|
||||
const CLEAR_ALL: &'static str = "\x1b[2J";
|
||||
const HIDE_CURSOR: &'static str = "\x1b[?25l";
|
||||
@@ -30,10 +31,10 @@ fn key_to_char(key: Key) -> Option<KeyChar> {
|
||||
Key::Ctrl(c) => Some(KeyChar::Ctrl(c)),
|
||||
Key::Backspace => Some(KeyChar::Press('𐘁')),
|
||||
Key::Esc => Some(KeyChar::Press('𐘃')),
|
||||
Key::Up => Some(KeyChar::Press('𐙘')),
|
||||
Key::Down => Some(KeyChar::Press('𐘞')),
|
||||
Key::Left => Some(KeyChar::Press('𐙣')),
|
||||
Key::Right => Some(KeyChar::Press('𐙥')),
|
||||
Key::ArrowUp => Some(KeyChar::Press('𐙘')),
|
||||
Key::ArrowDown => Some(KeyChar::Press('𐘞')),
|
||||
Key::ArrowLeft => Some(KeyChar::Press('𐙣')),
|
||||
Key::ArrowRight => Some(KeyChar::Press('𐙥')),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -71,15 +72,16 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
||||
|
||||
writer.init(framebuffer_info.clone());
|
||||
|
||||
let mut wm: WindowManager = WindowManager::new(writer, framebuffer, dimensions, rotate, grayscale);
|
||||
let mut wm: WindowManager = WindowManager::new(writer, framebuffer, dimensions, rotate, grayscale, PASSWORD_HASH);
|
||||
|
||||
let mut stdout = stdout().into_raw_mode().unwrap();
|
||||
let mut stdout = RawStdout::new(stdout());
|
||||
stdout.enter_raw_mode().unwrap();
|
||||
|
||||
write!(stdout, "{}", CLEAR_ALL).unwrap();
|
||||
write!(stdout.stdout, "{}", CLEAR_ALL).unwrap();
|
||||
|
||||
write!(stdout, "{}", HIDE_CURSOR).unwrap();
|
||||
write!(stdout.stdout, "{}", HIDE_CURSOR).unwrap();
|
||||
|
||||
stdout.flush().unwrap();
|
||||
stdout.stdout.flush().unwrap();
|
||||
|
||||
wm.draw(None, false);
|
||||
|
||||
@@ -89,9 +91,9 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
||||
|
||||
//read key presses
|
||||
thread::spawn(move || {
|
||||
let stdin = stdin().lock();
|
||||
for c in stdin.keys() {
|
||||
if let Some(kc) = key_to_char(c.unwrap()) {
|
||||
let stdin = RawStdin::new(stdin());
|
||||
for c in stdin {
|
||||
if let Some(kc) = key_to_char(c) {
|
||||
//do not allow exit when locked unless debugging
|
||||
//if kc == KeyChar::Alt('E') {
|
||||
if kc == KeyChar::Alt('E') {
|
||||
@@ -110,19 +112,18 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
||||
thread::spawn(move || {
|
||||
//spawn evtest, parse it for touch coords
|
||||
if touch {
|
||||
let mut evtest = Command::new("evtest").arg("/dev/input/by-path/first-touchscreen").stdout(Stdio::piped()).spawn().unwrap();
|
||||
let reader = BufReader::new(evtest.stdout.as_mut().unwrap());
|
||||
let mut events = Input::new("/dev/input/by-path/first-touchscreen").unwrap(); //panics in threads don't matter in this case
|
||||
let mut x: Option<usize> = None;
|
||||
let mut y: Option<usize> = None;
|
||||
for line in reader.lines() {
|
||||
let line = line.unwrap();
|
||||
if line.contains(&"ABS_X), value ") || line.contains(&"ABS_Y), value ") {
|
||||
let value: Vec<_> = line.split("), value ").collect();
|
||||
let value = value[value.len() - 1].parse::<usize>().unwrap();
|
||||
if line.contains(&"ABS_X") {
|
||||
x = Some(value);
|
||||
loop {
|
||||
let event = events.next();
|
||||
if let Some(event) = event {
|
||||
//ABS_X = 0, ABS_Y = 1
|
||||
if event.type_ == EventType::EV_ABS && (event.code == 0 || event.code == 1) {
|
||||
if event.code == 0 {
|
||||
x = Some(event.value as usize); //event.value is u16 so this should be fine. unless usize is u8, lmao
|
||||
} else {
|
||||
y = Some(value);
|
||||
y = Some(event.value as usize);
|
||||
}
|
||||
if x.is_some() && y.is_some() {
|
||||
let (x2, y2) = if rotate {
|
||||
@@ -140,6 +141,7 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
||||
y = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
@@ -152,15 +154,18 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
||||
for message in rx {
|
||||
match message {
|
||||
ThreadMessage::KeyChar(kc) => wm.handle_message(WindowManagerMessage::KeyChar(kc.clone())),
|
||||
ThreadMessage::Touch(x, y) => wm.handle_message(WindowManagerMessage::Touch(x, y)),
|
||||
ThreadMessage::Touch(x, y) => {
|
||||
wm.handle_message(WindowManagerMessage::Touch(x, y));
|
||||
println!(" "); //without any stdout, on my phone, for some reason the framebuffer doesn't get redrawn to the screen
|
||||
},
|
||||
ThreadMessage::Clear => {
|
||||
write!(stdout, "{}", CLEAR_ALL).unwrap();
|
||||
stdout.flush().unwrap();
|
||||
write!(stdout.stdout, "{}", CLEAR_ALL).unwrap();
|
||||
stdout.stdout.flush().unwrap();
|
||||
},
|
||||
ThreadMessage::Exit => {
|
||||
if !wm.locked {
|
||||
write!(stdout, "{}", SHOW_CURSOR).unwrap();
|
||||
stdout.suspend_raw_mode().unwrap();
|
||||
write!(stdout.stdout, "{}", SHOW_CURSOR).unwrap();
|
||||
stdout.exit_raw_mode().unwrap();
|
||||
exit(0);
|
||||
}
|
||||
},
|
||||
12
wm/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "wm"
|
||||
version = "1.0.3"
|
||||
repository = "https://github.com/stjet/ming-wm"
|
||||
license = "GPL-3.0-or-later"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ming-wm-lib = { path = "../ming-wm-lib" }
|
||||
linux = { path = "../linux" }
|
||||
bitcoin_hashes = { version = "0.16.0", default-features = false }
|
||||
bmp-rust = "0.5.0"
|
||||
@@ -7,8 +7,8 @@ use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
|
||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||
use ming_wm_lib::themes::ThemeInfo;
|
||||
use ming_wm_lib::dirs::exe_dir;
|
||||
use crate::components::Component;
|
||||
use crate::components::paragraph::Paragraph;
|
||||
use ming_wm_lib::components::Component;
|
||||
use ming_wm_lib::components::paragraph::Paragraph;
|
||||
|
||||
pub struct About {
|
||||
dimensions: Dimensions,
|
||||
@@ -8,8 +8,8 @@ use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
|
||||
use ming_wm_lib::dirs::exe_dir;
|
||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||
use ming_wm_lib::themes::ThemeInfo;
|
||||
use crate::components::paragraph::Paragraph;
|
||||
use crate::components::Component;
|
||||
use ming_wm_lib::components::paragraph::Paragraph;
|
||||
use ming_wm_lib::components::Component;
|
||||
|
||||
pub struct Help {
|
||||
dimensions: Dimensions,
|
||||
@@ -5,15 +5,14 @@ use ming_wm_lib::framebuffer_types::Dimensions;
|
||||
use ming_wm_lib::themes::ThemeInfo;
|
||||
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest };
|
||||
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
|
||||
use blake2::{ Blake2b512, Digest };
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/password.rs"));
|
||||
use bitcoin_hashes::Sha512;
|
||||
|
||||
//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 {
|
||||
dimensions: Dimensions,
|
||||
input_password: String,
|
||||
password_hash: [u8; 64],
|
||||
}
|
||||
|
||||
impl WindowLike for LockScreen {
|
||||
@@ -26,9 +25,7 @@ impl WindowLike for LockScreen {
|
||||
WindowMessage::KeyPress(key_press) => {
|
||||
if key_press.is_enter() {
|
||||
//check password
|
||||
let mut hasher = Blake2b512::new();
|
||||
hasher.update((self.input_password.clone() + "salt?sorrycryptographers").as_bytes());
|
||||
if hasher.finalize() == PASSWORD_HASH.into() {
|
||||
if Sha512::hash((self.input_password.clone() + "salt?sorrycryptographers!1!").as_bytes()).to_byte_array() == self.password_hash {
|
||||
WindowMessageResponse::Request(WindowManagerRequest::Unlock)
|
||||
} else {
|
||||
self.input_password = String::new();
|
||||
@@ -73,10 +70,11 @@ impl WindowLike for LockScreen {
|
||||
}
|
||||
|
||||
impl LockScreen {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(password_hash: [u8; 64]) -> Self {
|
||||
Self {
|
||||
dimensions: [0, 0],
|
||||
input_password: String::new(),
|
||||
password_hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManager
|
||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||
use ming_wm_lib::themes::ThemeInfo;
|
||||
use ming_wm_lib::utils::point_inside;
|
||||
use crate::components::Component;
|
||||
use crate::components::press_button::PressButton;
|
||||
use ming_wm_lib::components::Component;
|
||||
use ming_wm_lib::components::press_button::PressButton;
|
||||
|
||||
//seems like framebuffer only updates if (real) key is pressed...
|
||||
//on mobile, volume down button seems to work but is annoying
|
||||
@@ -7,9 +7,9 @@ use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManager
|
||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||
use ming_wm_lib::themes::ThemeInfo;
|
||||
use ming_wm_lib::dirs::exe_dir;
|
||||
use ming_wm_lib::components::Component;
|
||||
use ming_wm_lib::components::highlight_button::HighlightButton;
|
||||
use crate::fs::{ ExeWindowInfos, get_all_executable_windows };
|
||||
use crate::components::Component;
|
||||
use crate::components::highlight_button::HighlightButton;
|
||||
|
||||
static CATEGORIES: [&'static str; 9] = ["About", "Utils", "Games", "Editing", "Files", "Internet", "Misc", "Help", "Lock"];
|
||||
|
||||
@@ -115,13 +115,11 @@ impl WindowLike for StartMenu {
|
||||
];
|
||||
let max_per_page = CATEGORIES.len();
|
||||
let current_focus = self.get_focus_index().unwrap();
|
||||
let mut index = 0;
|
||||
for component in &self.components {
|
||||
for (index, component) in self.components.iter().enumerate() {
|
||||
//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) {
|
||||
instructions.extend(component.draw(theme_info));
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
instructions
|
||||
}
|
||||
@@ -194,8 +192,7 @@ impl StartMenu {
|
||||
pub fn add_category_components(&mut self) {
|
||||
self.current_focus = "About".to_string();
|
||||
self.components = Vec::new();
|
||||
for c in 0..CATEGORIES.len() {
|
||||
let name = CATEGORIES[c];
|
||||
for (c, name) in CATEGORIES.iter().enumerate() {
|
||||
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
|
||||
)));
|
||||
@@ -6,8 +6,8 @@ use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLik
|
||||
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType, InfoType, WindowsVec };
|
||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||
use ming_wm_lib::themes::ThemeInfo;
|
||||
use crate::components::Component;
|
||||
use crate::components::toggle_button::ToggleButton;
|
||||
use ming_wm_lib::components::Component;
|
||||
use ming_wm_lib::components::toggle_button::ToggleButton;
|
||||
|
||||
const PADDING: usize = 4;
|
||||
const META_WIDTH: usize = 175; //of the window button
|
||||
@@ -89,7 +89,7 @@ impl FramebufferWriter {
|
||||
}
|
||||
}
|
||||
self.rotate_buffer = Some(output_array);
|
||||
&self.rotate_buffer.as_ref().unwrap()
|
||||
self.rotate_buffer.as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn save_buffer(&mut self) {
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::fs::{ read_dir, File };
|
||||
use std::path::PathBuf;
|
||||
use std::io::Read;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -34,19 +33,6 @@ pub fn get_font_char_from_fonts(fonts: &[String], c: char) -> (char, Vec<Vec<u8>
|
||||
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)>>;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub use linux;
|
||||
|
||||
pub mod framebuffer;
|
||||
pub mod window_manager;
|
||||
pub mod components;
|
||||
pub mod fs;
|
||||
mod proxy_window_like;
|
||||
mod essential;
|
||||
@@ -24,7 +24,6 @@ use crate::essential::start_menu::StartMenu;
|
||||
use crate::essential::about::About;
|
||||
use crate::essential::help::Help;
|
||||
use crate::essential::onscreen_keyboard::OnscreenKeyboard;
|
||||
//use crate::logging::log;
|
||||
|
||||
//todo: a lot of the usize should be changed to u16
|
||||
|
||||
@@ -40,6 +39,7 @@ struct WindowLikeInfo {
|
||||
id: usize,
|
||||
window_like: WindowBox,
|
||||
top_left: Point,
|
||||
old_top_left: Point,
|
||||
dimensions: Dimensions,
|
||||
workspace: Workspace,
|
||||
fullscreen: bool,
|
||||
@@ -65,12 +65,13 @@ pub struct WindowManager {
|
||||
current_workspace: u8,
|
||||
framebuffer: Framebuffer,
|
||||
clipboard: Option<String>,
|
||||
password_hash: [u8; 64],
|
||||
}
|
||||
|
||||
//1 is up, 2 is down
|
||||
|
||||
impl WindowManager {
|
||||
pub fn new(writer: FramebufferWriter, framebuffer: Framebuffer, dimensions: Dimensions, rotate: bool, grayscale: bool) -> Self {
|
||||
pub fn new(writer: FramebufferWriter, framebuffer: Framebuffer, dimensions: Dimensions, rotate: bool, grayscale: bool, password_hash: [u8; 64]) -> Self {
|
||||
//println!("bg: {}x{}", dimensions[0], dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT);
|
||||
let mut wm = WindowManager {
|
||||
writer: RefCell::new(writer),
|
||||
@@ -86,6 +87,7 @@ impl WindowManager {
|
||||
current_workspace: 0,
|
||||
framebuffer,
|
||||
clipboard: None,
|
||||
password_hash,
|
||||
};
|
||||
wm.lock();
|
||||
wm.change_theme();
|
||||
@@ -95,7 +97,7 @@ impl WindowManager {
|
||||
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 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;
|
||||
window_like.handle_message(WindowMessage::Init(dimensions));
|
||||
let dimensions = if subtype == WindowLikeType::Window { [dimensions[0], dimensions[1] + WINDOW_TOP_HEIGHT] } else { dimensions };
|
||||
@@ -103,6 +105,7 @@ impl WindowManager {
|
||||
id,
|
||||
window_like,
|
||||
top_left,
|
||||
old_top_left: top_left,
|
||||
dimensions,
|
||||
workspace: if subtype == WindowLikeType::Window {
|
||||
Workspace::Workspace(self.current_workspace)
|
||||
@@ -136,7 +139,7 @@ impl WindowManager {
|
||||
fn lock(&mut self) {
|
||||
self.locked = true;
|
||||
self.window_infos = Vec::new();
|
||||
self.add_window_like(Box::new(LockScreen::new()), [0, 0], None);
|
||||
self.add_window_like(Box::new(LockScreen::new(self.password_hash)), [0, 0], None);
|
||||
}
|
||||
|
||||
fn unlock(&mut self) {
|
||||
@@ -154,7 +157,7 @@ impl WindowManager {
|
||||
file.read_to_string(&mut contents).unwrap();
|
||||
let lines: Vec<&str> = contents.split("\n").collect();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -456,17 +459,20 @@ impl WindowManager {
|
||||
let window_like = &self.window_infos[focused_index].window_like;
|
||||
if window_like.subtype() == WindowLikeType::Window && window_like.resizable() {
|
||||
//toggle fullscreen
|
||||
self.window_infos[focused_index].fullscreen ^= true;
|
||||
let window_info = &mut self.window_infos[focused_index];
|
||||
window_info.fullscreen ^= true;
|
||||
//todo: send message to window about resize
|
||||
let new_dimensions;
|
||||
if self.window_infos[focused_index].fullscreen {
|
||||
if window_info.fullscreen {
|
||||
new_dimensions = [self.dimensions[0], self.dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT];
|
||||
self.window_infos[focused_index].top_left = [0, INDICATOR_HEIGHT];
|
||||
redraw_ids = Some(vec![self.window_infos[focused_index].id]);
|
||||
window_info.old_top_left = window_info.top_left;
|
||||
window_info.top_left = [0, INDICATOR_HEIGHT];
|
||||
redraw_ids = Some(vec![window_info.id]);
|
||||
} else {
|
||||
new_dimensions = self.window_infos[focused_index].dimensions;
|
||||
window_info.top_left = window_info.old_top_left;
|
||||
new_dimensions = window_info.dimensions;
|
||||
}
|
||||
self.window_infos[focused_index].window_like.handle_message(WindowMessage::ChangeDimensions([new_dimensions[0], new_dimensions[1] - WINDOW_TOP_HEIGHT]));
|
||||
window_info.window_like.handle_message(WindowMessage::ChangeDimensions([new_dimensions[0], new_dimensions[1] - WINDOW_TOP_HEIGHT]));
|
||||
press_response = WindowMessageResponse::JustRedraw;
|
||||
}
|
||||
}
|
||||
@@ -667,8 +673,7 @@ impl WindowManager {
|
||||
});
|
||||
//these are needed to decide when to snapshot
|
||||
let max_index = if redraw_ids.len() > 0 { redraw_ids.len() } else { maybe_length } - 1;
|
||||
let mut w_index = 0;
|
||||
for window_info in redraw_windows {
|
||||
for (w_index, window_info) in redraw_windows.enumerate() {
|
||||
let window_dimensions = if window_info.fullscreen {
|
||||
[self.dimensions[0], self.dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT]
|
||||
} else {
|
||||
@@ -746,7 +751,6 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
self.writer.borrow_mut().draw_buffer(window_info.top_left, window_dimensions[1], window_dimensions[0] * bytes_per_pixel, &window_writer.get_buffer());
|
||||
w_index += 1;
|
||||
}
|
||||
//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();
|
||||