added config file

This commit is contained in:
emilis 2023-01-16 15:23:44 +00:00
parent 0918ffea90
commit 78f5ba7ac1
8 changed files with 378 additions and 132 deletions

103
Cargo.lock generated
View File

@ -32,6 +32,37 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "dirs"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hermit-abi"
version = "0.2.6"
@ -46,8 +77,11 @@ name = "kk"
version = "0.1.0"
dependencies = [
"anyhow",
"serde",
"termion",
"tokio",
"toml",
"xdg",
]
[[package]]
@ -174,12 +208,43 @@ dependencies = [
"redox_syscall",
]
[[package]]
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall",
"thiserror",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
@ -228,6 +293,26 @@ dependencies = [
"redox_termios",
]
[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio"
version = "1.24.1"
@ -259,6 +344,15 @@ dependencies = [
"syn",
]
[[package]]
name = "toml"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
dependencies = [
"serde",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
@ -349,3 +443,12 @@ name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
[[package]]
name = "xdg"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6"
dependencies = [
"dirs",
]

View File

@ -5,10 +5,23 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.release]
lto = true
# [profile.release.build-override]
# opt-level = 3
# lto = true
[dependencies]
anyhow = "1.0.68"
# misskey = "0.2.0"
termion = "2.0.1"
xdg = "2.4.1"
toml = "0.5.10"
[dependencies.serde]
version = "1.0"
features = ["std", "derive"]
[dependencies.tokio]
version = "1.24.1"

35
src/cfg.rs Normal file
View File

@ -0,0 +1,35 @@
use std::fs::{self, File};
use crate::display::theme::Theme;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Config {
pub auth: Auth,
pub theme: Theme,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Auth {
hostname: Option<String>,
token: Option<String>,
}
impl Config {
pub fn load_or_create() -> Result<Self, anyhow::Error> {
const CONFIG_FILENAME: &str = "kk.toml";
let xdg_dirs = xdg::BaseDirectories::with_prefix("kk")?;
let cfg_filename =
xdg_dirs.place_config_file(CONFIG_FILENAME)?;
Ok(match fs::read(&cfg_filename) {
Ok(f) => toml::from_slice(&f)?,
Err(_) => {
// Save default file
let cfg = Self::default();
let fmt_cfg = toml::to_string_pretty(&cfg)?;
fs::write(cfg_filename, fmt_cfg)?;
cfg
}
})
}
}

View File

@ -45,7 +45,7 @@ impl Body {
"{hide}{clear}{fr}{cursor}",
clear = clear::All,
hide = cursor::Hide,
fr = env.frame.frame_str(env.theme.colors.primary),
fr = env.frame.frame_str(env.theme.windows.primary),
cursor = env.frame.goto(0, 0),
)?;
screen.flush()?;
@ -240,6 +240,9 @@ impl SigninPage {
#[inline]
fn char(&mut self, c: char) {
if ['\t', '\n', '\r'].contains(&c) {
return;
}
match self.cursor {
SigninCursorLocation::Hostname => {
self.hostname.push(c);
@ -264,13 +267,7 @@ impl Page for SigninPage {
termion::event::Key::Char(c) => self.char(c),
termion::event::Key::Up => self.previous(),
termion::event::Key::Down => self.next(),
termion::event::Key::Backspace => {
if let SigninCursorLocation::Hostname =
self.cursor
{
self.hostname.pop();
}
}
termion::event::Key::Backspace => self.del(),
termion::event::Key::Delete => {}
termion::event::Key::Left => {}
termion::event::Key::Right => {}
@ -284,10 +281,10 @@ impl Page for SigninPage {
screen,
"{}{}{}",
env.primary_frame(),
fr.frame_str(env.theme.colors.subwin),
fr.frame_str(env.theme.windows.subwin),
fr.make(self.draw(
env.theme.colors.highlight,
env.theme.colors.subwin
env.theme.windows.highlight,
env.theme.windows.subwin
))
)?;
screen.flush()?;
@ -301,24 +298,21 @@ impl Page for SigninPage {
screen: &mut RawTerminal<Stdout>,
) -> Result<()> {
self.frame = Some(env.frame.sub(
env.theme.colors.subframe,
super::frame::FrameSize::ByAbsolute(70, 15),
1,
env.theme.frames.inner,
super::frame::FrameSize::ByAbsolute(70, 10),
));
write!(
screen,
"{theme}{clear}{frame}{subframe}{content}{hide_cursor}",
theme = env.theme.frame(),
clear = clear::All,
frame = env.frame.frame_str(env.theme.colors.primary),
"{frame}{subframe}{content}{hide_cursor}",
frame = env.frame.frame_str(env.theme.windows.primary),
subframe = self
.frame
.unwrap()
.frame_str(env.theme.colors.subwin),
.frame_str(env.theme.windows.subwin),
hide_cursor = cursor::Hide,
content = self.frame.unwrap().make(self.draw(
env.theme.colors.highlight,
env.theme.colors.subwin
env.theme.windows.highlight,
env.theme.windows.subwin
))
)?;
screen.flush()?;

View File

@ -1,17 +1,10 @@
use termion::cursor;
const ESTIMATED_FRAME_BIT_SIZE: usize = 11;
use crate::display::compose::Text;
use super::{
compose::{Component, Components},
theme::ColorSet,
theme::{self, ColorSet},
};
const FRAME_CHAR_HORIZONTAL: char = ' ';
const FRAME_CHAR_VERTICAL: char = ' ';
pub enum FrameSize {
ByAbsolute(u16, u16),
ByPercent(u16, u16),
@ -22,36 +15,30 @@ pub struct Frame {
// Both are (w, h)
start: (u16, u16),
end: (u16, u16),
border: u16,
theme: ColorSet,
theme: theme::Frame,
}
impl Frame {
#[inline(always)]
pub fn sub(
&self,
theme: ColorSet,
size: FrameSize,
border: u16,
) -> Frame {
pub fn sub(&self, theme: theme::Frame, size: FrameSize) -> Frame {
let (p_width, p_height) = self.size();
let sub_size = size.abs_size((
p_width - self.border * 2,
p_height - self.border * 2,
p_width - self.theme.width * 2,
p_height - self.theme.width * 2,
));
self.child_centered(sub_size, border, theme)
self.child_centered(sub_size, theme)
}
#[inline(always)]
pub fn goto(&self, x: u16, y: u16) -> String {
cursor::Goto(
self.border.max(
(self.border + self.start.0 + x)
.min(self.end.0 - self.border),
self.theme.width.max(
(self.theme.width + self.start.0 + x)
.min(self.end.0 - self.theme.width),
),
self.border.max(
(self.border + self.start.1 + y)
.min(self.end.1 - self.border),
self.theme.width.max(
(self.theme.width + self.start.1 + y)
.min(self.end.1 - self.theme.width),
),
)
.into()
@ -86,14 +73,14 @@ impl Frame {
#[inline(always)]
fn write_centered_clear(&self, s: &str) -> Component {
let width = self.size().0;
let limit = width - self.border * 2;
let limit = width - self.theme.width * 2;
let s = if s.len() > limit as usize {
&s[..limit as usize]
} else {
s
};
let len = s.len() as u16;
let base_size = ((width - len) / 2) - self.border;
let base_size = ((width - len) / 2) - self.theme.width;
Component::String(super::compose::Text::PaddedBothSides(
base_size + ((width - len) % 2),
s.into(),
@ -102,12 +89,14 @@ impl Frame {
}
#[inline(always)]
fn write_frame_line(&self, y: u16, length: u16) -> Components {
vec![
self.goto_internal(0, y),
Component::Repeated(FRAME_CHAR_HORIZONTAL, length),
]
.into()
fn write_frame_line(
&self,
y: u16,
length: u16,
c: char,
) -> Components {
vec![self.goto_internal(0, y), Component::Repeated(c, length)]
.into()
}
pub fn make(&self, comp: Components) -> String {
@ -123,31 +112,31 @@ impl Frame {
#[inline(always)]
pub fn inner_size(&self) -> (u16, u16) {
(
self.end.0 - self.start.0 - self.border * 2,
self.end.1 - self.start.1 - self.border * 2,
self.end.0 - self.start.0 - self.theme.width * 2,
self.end.1 - self.start.1 - self.theme.width * 2,
)
}
#[inline(always)]
pub fn from_terminal_size(
theme: ColorSet,
width: u16,
theme: theme::Frame,
) -> Result<Self, anyhow::Error> {
let term = termion::terminal_size()?;
Ok(Self::from_bottom_right(term, term, width, theme))
Ok(Self::from_bottom_right(term, term, theme))
}
#[inline(always)]
fn child_centered(
&self,
(pos_w, pos_h): (u16, u16),
border: u16,
theme: ColorSet,
theme: theme::Frame,
) -> Frame {
let parent_offset = self.border / 2;
let parent_offset = self.theme.width / 2;
let (parent_w, parent_h) = self.size();
let (parent_w, parent_h) =
(parent_w - self.border, parent_h - self.border);
let (parent_w, parent_h) = (
parent_w - self.theme.width,
parent_h - self.theme.width,
);
let start = (
1.max(
(parent_w - pos_w) / 2
@ -163,7 +152,6 @@ impl Frame {
let frame = Self {
end: (start.0 + pos_w, start.1 + pos_h),
start,
border,
theme,
};
frame
@ -173,21 +161,19 @@ impl Frame {
fn from_bottom_right(
(pos_w, pos_h): (u16, u16),
(term_w, term_h): (u16, u16),
border: u16,
theme: ColorSet,
theme: theme::Frame,
) -> Self {
let start = (1.max(term_w - pos_w), 1.max(term_h - pos_h));
let frame = Self {
end: (start.0 + pos_w, start.1 + pos_h),
start,
border,
theme,
};
if frame.start.0 > frame.end.0 || frame.start.1 > frame.end.1
{
eprintln!(
"pos_w {}, pos_h {}, term_w {}, term_h {}, border {}",
pos_w, pos_h, term_w, term_h, border
pos_w, pos_h, term_w, term_h, theme.width,
);
panic!(
"start.0: {} end.0: {}, start.1: {}, end.1: {}",
@ -211,49 +197,66 @@ impl Frame {
pub fn compose(&self, body_theme: ColorSet) -> Components {
let body_theme = Component::Internal(body_theme.to_string());
let (w_len, h_len) = self.size();
let frame_theme = Component::Internal(self.theme.to_string());
let frame_theme =
Component::Internal(self.theme.color.to_string());
let body_clear =
Component::Internal(self.make(Into::<Components>::into(
vec![body_theme.clone(), Component::Padding(w_len)],
)));
let border =
Component::Repeated(FRAME_CHAR_VERTICAL, self.border);
let border_left = Component::Repeated(
self.theme.frame_chars.left,
self.theme.width,
);
let border_right = Component::Repeated(
self.theme.frame_chars.right,
self.theme.width,
);
// This seems cursed but, I assure you, it's fine
vec![vec![frame_theme.clone()]]
.into_iter()
// Top horizontal borders
.chain(
(0..self.border)
.into_iter()
.map(|y: u16| self.write_frame_line(y, w_len).0),
)
.chain((0..self.theme.width).into_iter().map(|y: u16| {
self.write_frame_line(
y,
w_len,
self.theme.frame_chars.top,
)
.0
}))
// Frame body + vertical frame borders
.chain(
(self.border..h_len - self.border).into_iter().map(
|y: u16| {
(self.theme.width..h_len - self.theme.width)
.into_iter()
.map(|y: u16| {
let left = self.goto_internal(0, y);
let right = self
.goto_internal(w_len - self.border, y);
let right = self.goto_internal(
w_len - self.theme.width,
y,
);
vec![
left.clone(),
body_clear.clone(),
frame_theme.clone(),
left,
border.clone(),
border_left.clone(),
right,
border.clone(),
border_right.clone(),
]
},
),
}),
)
// Bottom horizontal border
.chain(
(h_len - self.border..h_len)
.into_iter()
.map(|y: u16| self.write_frame_line(y, w_len).0),
)
.chain((h_len - self.theme.width..h_len).into_iter().map(
|y: u16| {
self.write_frame_line(
y,
w_len,
self.theme.frame_chars.bottom,
)
.0
},
))
// Theme reset + move to beginning of inside frame
.chain(
vec![vec![Component::Goto(0, 0), body_theme]]

View File

@ -89,25 +89,22 @@ pub struct Environment {
impl Into<String> for Environment {
fn into(self) -> String {
self.frame.frame_str(self.theme.colors.primary)
self.frame.frame_str(self.theme.windows.primary)
}
}
impl Environment {
fn initial_must(theme: Theme) -> Self {
let w: u16 = std::env::var("FRAME_WIDTH")
.map(|f| f.parse().unwrap_or(1))
.unwrap_or(1);
Self {
theme,
frame: Frame::from_terminal_size(theme.colors.frame, w)
frame: Frame::from_terminal_size(theme.frames.outer)
.expect("could not get terminal size"),
}
}
#[inline(always)]
pub fn primary_frame(&self) -> String {
self.frame.frame_str(self.theme.colors.primary)
self.frame.frame_str(self.theme.windows.primary)
}
}
@ -159,7 +156,9 @@ impl Screen {
write!(
scr,
"{color}{clear}{show_cursor}{move_to_start}",
color = Into::<Color>::into("#000000").bg_string(),
color = TryInto::<Color>::try_into("#000000")
.unwrap()
.bg_string(),
move_to_start = cursor::Goto(1, 1),
clear = clear::All,
show_cursor = cursor::Show,

View File

@ -1,23 +1,46 @@
use serde::{de::Visitor, Deserialize, Serialize};
use std::ops::Deref;
use termion::color;
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Theme {
pub colors: Colors,
pub windows: Colors,
pub frames: Frames,
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct ColorSet {
pub fg: Color,
pub bg: Color,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Frames {
pub outer: Frame,
pub inner: Frame,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct FrameChars {
pub top: char,
pub bottom: char,
pub left: char,
pub right: char,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Frame {
pub width: u16,
pub frame_chars: FrameChars,
pub color: ColorSet,
}
impl Default for ColorSet {
fn default() -> Self {
Self {
fg: "#ffffff".into(),
bg: "#000000".into(),
fg: "#ffffff".try_into().unwrap(),
bg: "#000000".try_into().unwrap(),
}
}
}
@ -29,18 +52,71 @@ impl ToString for ColorSet {
}
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Colors {
pub primary: ColorSet,
pub frame: ColorSet,
pub subframe: ColorSet,
pub subwin: ColorSet,
pub highlight: ColorSet,
}
impl Theme {}
#[derive(Clone, Copy, Debug)]
pub struct Color(color::Rgb);
impl Serialize for Color {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let rgb = self.0;
serializer.serialize_str(&format!(
"#{:02X}{:02X}{:02X}",
rgb.0, rgb.1, rgb.2,
))
}
}
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ColorCodeVisitor;
impl<'de> Visitor<'de> for ColorCodeVisitor {
fn expecting(
&self,
formatter: &mut std::fmt::Formatter,
) -> std::fmt::Result {
formatter.write_str("hex color code")
}
type Value = Color;
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match TryInto::<Color>::try_into(v) {
Ok(c) => Ok(c),
Err(e) => Err(E::custom(e.to_string())),
}
}
fn visit_string<E>(
self,
v: String,
) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match TryInto::<Color>::try_into(v) {
Ok(c) => Ok(c),
Err(e) => Err(E::custom(e.to_string())),
}
}
}
deserializer.deserialize_any(ColorCodeVisitor {})
}
}
impl Deref for Color {
type Target = color::Rgb;
@ -56,8 +132,10 @@ impl TryFrom<String> for Color {
}
}
impl From<&str> for Color {
fn from(value: &str) -> Self {
impl TryFrom<&str> for Color {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
if value.len() < 6 || value.len() > 7 {
panic!("hex code length invalid: {}", value.len());
}
@ -65,40 +143,60 @@ impl From<&str> for Color {
if value.starts_with('#') {
i = 1;
}
Self(color::Rgb(
Ok(Self(color::Rgb(
u8::from_str_radix(&value[i..i + 2], 16)
.expect("red hex"),
.map_err(|_| anyhow::anyhow!("red hex"))?,
u8::from_str_radix(&value[i + 2..i + 4], 16)
.expect("green hex"),
.map_err(|_| anyhow::anyhow!("green hex"))?,
u8::from_str_radix(&value[i + 4..i + 6], 16)
.expect("blue hex"),
))
.map_err(|_| anyhow::anyhow!("blue hex"))?,
)))
}
}
impl Default for Theme {
fn default() -> Self {
Self {
colors: Colors {
windows: Colors {
primary: ColorSet {
fg: "#330033".into(),
bg: "#a006d3".into(),
},
frame: ColorSet {
fg: "#ffe6ff".into(),
bg: "#9005c2".into(),
},
subframe: ColorSet {
fg: "#ffe6ff".into(),
bg: "#7003a0".into(),
fg: "#330033".try_into().unwrap(),
bg: "#a006d3".try_into().unwrap(),
},
highlight: ColorSet {
fg: "#ffffff".into(),
bg: "#0000ff".into(),
fg: "#ffffff".try_into().unwrap(),
bg: "#0000ff".try_into().unwrap(),
},
subwin: ColorSet {
fg: "#ffffff".into(),
bg: "#500170".into(),
fg: "#ffffff".try_into().unwrap(),
bg: "#500170".try_into().unwrap(),
},
},
frames: Frames {
outer: Frame {
color: ColorSet {
fg: "#ff36ff".try_into().unwrap(),
bg: "#9005c2".try_into().unwrap(),
},
frame_chars: FrameChars {
top: ' ',
bottom: ' ',
left: ' ',
right: ' ',
},
width: 1,
},
inner: Frame {
color: ColorSet {
fg: "#ffe6ff".try_into().unwrap(),
bg: "#7003a0".try_into().unwrap(),
},
frame_chars: FrameChars {
top: ' ',
bottom: ' ',
left: ' ',
right: ' ',
},
width: 1,
},
},
}
@ -108,11 +206,11 @@ impl Default for Theme {
impl Theme {
#[inline(always)]
pub fn primary(&self) -> String {
self.colors.primary.to_string()
self.windows.primary.to_string()
}
#[inline(always)]
pub fn frame(&self) -> String {
self.colors.frame.to_string()
self.frames.outer.color.to_string()
}
}

View File

@ -5,13 +5,14 @@ use display::theme::Theme;
use crate::display::Screen;
extern crate termion;
mod cfg;
mod display;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let config = cfg::Config::load_or_create()?;
{
let theme = Theme::default();
let x = Screen::new(theme).unwrap();
let x = Screen::new(config.theme).unwrap();
x.start().await?;
}
process::exit(0);