Browse Source
Use a web view instead of opening a browser
Use a web view instead of opening a browser
This also means we don't need to have the 'exit' button on the panel page any more, since we can just exit the application when the web view is closed.main
9 changed files with 1198 additions and 186 deletions
-
1014Cargo.lock
-
2Cargo.toml
-
6src/injector.rs
-
139src/main.rs
-
36src/open_url.rs
-
107src/panel.rs
-
18src/panel/index.html
-
33src/panel/panel.js
-
29src/web_view.rs
1014
Cargo.lock
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,133 +1,30 @@ |
|||
// Incantation to not allocate a Windows console when compiled in release mode
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|||
|
|||
use std::{collections::HashMap, net::SocketAddr, time::Duration};
|
|||
|
|||
use include_dir::{include_dir, Dir};
|
|||
use once_cell::sync::{Lazy, OnceCell};
|
|||
use warp::{
|
|||
http::HeaderValue,
|
|||
hyper::HeaderMap,
|
|||
reply::{json, Response},
|
|||
Filter, Rejection, Reply,
|
|||
};
|
|||
|
|||
mod injector;
|
|||
mod open_url;
|
|||
mod panel;
|
|||
mod themes;
|
|||
mod web_view;
|
|||
|
|||
use crate::themes::{read_theme_from_dir, Theme};
|
|||
|
|||
static CURRENT_ADDRESS: OnceCell<SocketAddr> = OnceCell::new();
|
|||
static THEMES: Lazy<HashMap<String, Theme>> = Lazy::new(|| {
|
|||
let mut themes = HashMap::new();
|
|||
|
|||
for entry in std::fs::read_dir("styles").unwrap() {
|
|||
if entry.is_err() {
|
|||
continue;
|
|||
}
|
|||
use std::{net::SocketAddr, sync::mpsc, thread};
|
|||
|
|||
let path = entry.unwrap().path();
|
|||
if path.is_dir() {
|
|||
if let Some(theme) = read_theme_from_dir(path) {
|
|||
themes.insert(theme.slug.clone(), theme);
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
themes
|
|||
});
|
|||
|
|||
fn injector(theme_name: String) -> String {
|
|||
if let Some(theme) = THEMES.get(&theme_name) {
|
|||
let current_addr = CURRENT_ADDRESS.get().expect("Couldn't get current address");
|
|||
injector::render_injector(current_addr, theme)
|
|||
} else {
|
|||
"// Unknown theme, sorry!".to_string()
|
|||
}
|
|||
async fn serve_panel(addr_listener: mpsc::Sender<SocketAddr>) {
|
|||
let (panel_addr, panel_listener) = crate::panel::serve_panel();
|
|||
addr_listener.send(panel_addr).unwrap();
|
|||
panel_listener.await;
|
|||
}
|
|||
|
|||
fn themes() -> impl Reply {
|
|||
let themes: &HashMap<_, _> = &THEMES;
|
|||
json(themes)
|
|||
}
|
|||
|
|||
fn shutdown() -> String {
|
|||
let _ = std::thread::spawn(|| {
|
|||
std::thread::sleep(Duration::from_millis(500));
|
|||
std::process::exit(0)
|
|||
fn main() {
|
|||
let (tx, rx) = mpsc::channel();
|
|||
thread::spawn(move || {
|
|||
let runtime = tokio::runtime::Builder::new_current_thread()
|
|||
.enable_all()
|
|||
.build()
|
|||
.expect("Couldn't create async runtime");
|
|||
runtime.block_on(serve_panel(tx));
|
|||
});
|
|||
|
|||
"Goodbye!".to_string()
|
|||
}
|
|||
|
|||
const PANEL_DIR: Dir = include_dir!("./src/panel");
|
|||
|
|||
async fn serve_panel_file(path: warp::path::Tail) -> Result<Response, Rejection> {
|
|||
let mut file_name = path.as_str().to_string();
|
|||
if ("/".to_string() + &file_name).ends_with("/") {
|
|||
file_name.push_str("index.html");
|
|||
}
|
|||
|
|||
if let Some(file) = PANEL_DIR.get_file(&file_name) {
|
|||
let (mut head, body) = Response::new(file.contents().into()).into_parts();
|
|||
|
|||
let extension = file_name.rfind('.').map(|idx| &file_name[idx..]);
|
|||
let content_type = match extension {
|
|||
Some(".html") => "text/html; charset=utf-8",
|
|||
Some(".css") => "text/css; charset=utf-8",
|
|||
Some(".js") => "application/javascript; charset=utf-8",
|
|||
Some(".txt") => "text/plain; charset=utf-8",
|
|||
_ => "application/octet-stream",
|
|||
};
|
|||
|
|||
head.headers
|
|||
.insert("Content-Type", HeaderValue::from_str(content_type).unwrap());
|
|||
|
|||
return Ok(Response::from_parts(head, body));
|
|||
}
|
|||
|
|||
Err(warp::reject::not_found())
|
|||
}
|
|||
|
|||
#[tokio::main]
|
|||
async fn main() {
|
|||
let styles_route = {
|
|||
let mut static_headers = HeaderMap::new();
|
|||
for (h, v) in [
|
|||
("Access-Control-Allow-Origin", "*"),
|
|||
("Pragma", "no-cache"),
|
|||
("Cache-Control", "no-cache"),
|
|||
] {
|
|||
static_headers.insert(h, HeaderValue::from_static(v));
|
|||
}
|
|||
|
|||
warp::path("styles")
|
|||
.and(warp::fs::dir("styles"))
|
|||
.with(warp::reply::with::headers(static_headers))
|
|||
};
|
|||
|
|||
let themes_list_route = warp::path("themes.json").map(themes);
|
|||
let injector_route = warp::path!("theme" / String / "injector.js").map(injector);
|
|||
let panel_route = warp::get()
|
|||
.and(warp::path::tail())
|
|||
.and_then(serve_panel_file);
|
|||
let shutdown_route = warp::post().and(warp::path("shutdown")).map(shutdown);
|
|||
|
|||
let routes = styles_route
|
|||
.or(injector_route)
|
|||
.or(themes_list_route)
|
|||
.or(panel_route)
|
|||
.or(shutdown_route);
|
|||
|
|||
let (addr, listener) = warp::serve(routes).bind_ephemeral(([127, 0, 0, 1], 0));
|
|||
println!("Listening on: http://{}/", &addr);
|
|||
|
|||
open_url::open(&format!("http://{}/", addr));
|
|||
|
|||
CURRENT_ADDRESS
|
|||
.set(addr)
|
|||
.expect("Could not set current address");
|
|||
|
|||
listener.await;
|
|||
let panel_addr = rx.recv().unwrap();
|
|||
crate::web_view::open_webview(&format!("http://{}/", panel_addr))
|
|||
.expect("Failed to open web view");
|
|||
}
|
@ -1,36 +0,0 @@ |
|||
#[cfg(target_os = "windows")]
|
|||
pub fn open(url: &str) {
|
|||
extern crate winapi;
|
|||
|
|||
use std::ffi::CString;
|
|||
use std::ptr;
|
|||
use winapi::um::shellapi::ShellExecuteA;
|
|||
|
|||
unsafe {
|
|||
let open_str = CString::new("open").unwrap();
|
|||
let url_str = CString::new(url.to_string().replace("\n", "%0A")).unwrap();
|
|||
|
|||
ShellExecuteA(
|
|||
ptr::null_mut(),
|
|||
open_str.as_ptr(),
|
|||
url_str.as_ptr(),
|
|||
ptr::null(),
|
|||
ptr::null(),
|
|||
winapi::um::winuser::SW_SHOWNORMAL,
|
|||
);
|
|||
}
|
|||
}
|
|||
|
|||
#[cfg(target_os = "macos")]
|
|||
pub fn open(url: &str) {
|
|||
let _ = std::process::Command::new("open")
|
|||
.arg(url.to_string())
|
|||
.output();
|
|||
}
|
|||
|
|||
#[cfg(target_os = "linux")]
|
|||
pub fn open(url: &str) {
|
|||
let _ = std::process::Command::new("xdg-open")
|
|||
.arg(url.to_string())
|
|||
.output();
|
|||
}
|
@ -0,0 +1,107 @@ |
|||
use std::{collections::HashMap, net::SocketAddr};
|
|||
|
|||
use futures::Future;
|
|||
use include_dir::{include_dir, Dir};
|
|||
use once_cell::sync::Lazy;
|
|||
use warp::{
|
|||
http::HeaderValue,
|
|||
hyper::HeaderMap,
|
|||
reply::{json, Response},
|
|||
Filter, Rejection, Reply,
|
|||
};
|
|||
|
|||
use crate::themes::{read_theme_from_dir, Theme};
|
|||
|
|||
static THEMES: Lazy<HashMap<String, Theme>> = Lazy::new(|| {
|
|||
let mut themes = HashMap::new();
|
|||
|
|||
for entry in std::fs::read_dir("styles").unwrap() {
|
|||
if entry.is_err() {
|
|||
continue;
|
|||
}
|
|||
|
|||
let path = entry.unwrap().path();
|
|||
if path.is_dir() {
|
|||
if let Some(theme) = read_theme_from_dir(path) {
|
|||
themes.insert(theme.slug.clone(), theme);
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
themes
|
|||
});
|
|||
|
|||
fn injector(theme_name: String, host: String) -> String {
|
|||
if let Some(theme) = THEMES.get(&theme_name) {
|
|||
// let current_addr = CURRENT_ADDRESS.get().expect("Couldn't get current address");
|
|||
crate::injector::render_injector(host, theme)
|
|||
} else {
|
|||
"// Unknown theme, sorry!".to_string()
|
|||
}
|
|||
}
|
|||
|
|||
fn themes() -> impl Reply {
|
|||
let themes: &HashMap<_, _> = &THEMES;
|
|||
json(themes)
|
|||
}
|
|||
|
|||
const PANEL_DIR: Dir = include_dir!("./src/panel");
|
|||
|
|||
async fn serve_panel_file(path: warp::path::Tail) -> Result<Response, Rejection> {
|
|||
let mut file_name = path.as_str().to_string();
|
|||
if ("/".to_string() + &file_name).ends_with("/") {
|
|||
file_name.push_str("index.html");
|
|||
}
|
|||
|
|||
if let Some(file) = PANEL_DIR.get_file(&file_name) {
|
|||
let (mut head, body) = Response::new(file.contents().into()).into_parts();
|
|||
|
|||
let extension = file_name.rfind('.').map(|idx| &file_name[idx..]);
|
|||
let content_type = match extension {
|
|||
Some(".html") => "text/html; charset=utf-8",
|
|||
Some(".css") => "text/css; charset=utf-8",
|
|||
Some(".js") => "application/javascript; charset=utf-8",
|
|||
Some(".txt") => "text/plain; charset=utf-8",
|
|||
_ => "application/octet-stream",
|
|||
};
|
|||
|
|||
head.headers
|
|||
.insert("Content-Type", HeaderValue::from_str(content_type).unwrap());
|
|||
|
|||
return Ok(Response::from_parts(head, body));
|
|||
}
|
|||
|
|||
Err(warp::reject::not_found())
|
|||
}
|
|||
|
|||
pub fn serve_panel() -> (SocketAddr, impl Future<Output = ()>) {
|
|||
let styles_route = {
|
|||
let mut static_headers = HeaderMap::new();
|
|||
for (h, v) in [
|
|||
("Access-Control-Allow-Origin", "*"),
|
|||
("Pragma", "no-cache"),
|
|||
("Cache-Control", "no-cache"),
|
|||
] {
|
|||
static_headers.insert(h, HeaderValue::from_static(v));
|
|||
}
|
|||
|
|||
warp::path("styles")
|
|||
.and(warp::fs::dir("styles"))
|
|||
.with(warp::reply::with::headers(static_headers))
|
|||
};
|
|||
|
|||
let themes_list_route = warp::path("themes.json").map(themes);
|
|||
let injector_route = warp::path!("theme" / String / "injector.js")
|
|||
.and(warp::header("Host"))
|
|||
.map(injector);
|
|||
let panel_route = warp::get()
|
|||
.and(warp::path::tail())
|
|||
.and_then(serve_panel_file);
|
|||
|
|||
let routes = styles_route
|
|||
.or(injector_route)
|
|||
.or(themes_list_route)
|
|||
.or(panel_route);
|
|||
|
|||
warp::serve(routes).bind_ephemeral(([127, 0, 0, 1], 0))
|
|||
}
|
@ -0,0 +1,29 @@ |
|||
use wry::{
|
|||
application::{
|
|||
event::{Event, WindowEvent},
|
|||
event_loop::{ControlFlow, EventLoop},
|
|||
window::WindowBuilder,
|
|||
},
|
|||
webview::WebViewBuilder,
|
|||
Result,
|
|||
};
|
|||
|
|||
pub fn open_webview(target_url: &str) -> Result<()> {
|
|||
let event_loop = EventLoop::new();
|
|||
let window = WindowBuilder::new()
|
|||
.with_title("Discord Theme Injector")
|
|||
.build(&event_loop)?;
|
|||
let _webview = WebViewBuilder::new(window)?.with_url(target_url)?.build()?;
|
|||
|
|||
event_loop.run(move |event, _, control_flow| {
|
|||
*control_flow = ControlFlow::Wait;
|
|||
|
|||
if let Event::WindowEvent {
|
|||
event: WindowEvent::CloseRequested,
|
|||
..
|
|||
} = event
|
|||
{
|
|||
*control_flow = ControlFlow::Exit
|
|||
}
|
|||
});
|
|||
}
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue