WIP rocket webserver for ipc

This commit is contained in:
Michael 2022-12-10 00:31:53 +00:00
parent 5ac5326614
commit 5a8d2f335d
Signed by: michael
GPG Key ID: 523BD9EF68BDD44C
13 changed files with 2534 additions and 29 deletions

6
.gitignore vendored
View File

@ -1,2 +1,8 @@
/target
/static/fonts
/static/stylesheets
/static/javascript/bootstrap*
/static/javascript/jquery*
scss/bootstrap
.vscode

2224
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,11 @@
name = "sway-ipc"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
repository = "https://git.0cd.xyz/michael/sway-ipc"
license = "MIT"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_json = "1.0"
rocket = "0.5.0-rc.2"
rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["tera"] }

View File

@ -1,3 +1,9 @@
APP=sway-ipc
BOOTSTRAP=v5.2.3
JQUERY=3.6.0
INTER=3.19
PREFIX=/usr/local
run:
cargo run
@ -11,4 +17,44 @@ doc:
cargo doc
clean:
rm -rf target/
rm -rf target/
clean-assets:
rm -rf ./assets/stylesheets
rm -rf $(shell find ./assets/javascript -maxdepth 1 -type f -name "*" ! -name "application.js")
rm -rf ./assets/fonts
rm -rf ./scss/bootstrap
clone-bootstrap:
ifeq ($(shell test ! -e ./scss/bootstrap && echo -n yes),yes)
git clone --quiet --depth=1 https://github.com/twbs/bootstrap.git -b $(BOOTSTRAP) ./scss/bootstrap
endif
css: clone-bootstrap
install -dm755 -- ./static/stylesheets
sassc --style compressed scss/application.scss > static/stylesheets/application.css
bootstrap: clone-bootstrap
install -dm755 -- ./static/stylesheets
install -dm755 -- ./static/javascript
install -m664 -- ./scss/bootstrap/dist/css/bootstrap.min.css ./static/stylesheets/
install -m664 -- ./scss/bootstrap/dist/css/bootstrap.min.css.map ./static/stylesheets/
install -m664 -- ./scss/bootstrap/dist/js/bootstrap.bundle.min.js ./static/javascript/
rm -f ./static/javascript/jquery-$(JQUERY).slim.min.js
wget -4 -P ./static/javascript/ https://code.jquery.com/jquery-$(JQUERY).slim.min.js
fonts: clean-fonts
wget https://github.com/rsms/inter/releases/download/v$(INTER)/Inter-$(INTER).zip
unzip Inter-$(INTER).zip -d Inter-$(INTER)
install -dm755 -- ./static/fonts
install -dm755 -- ./static/stylesheets
install -m664 -- Inter-$(INTER)/Inter\ Web/*.woff ./static/fonts/
install -m664 -- Inter-$(INTER)/Inter\ Web/*.woff2 ./static/fonts/
install -m664 -- Inter-$(INTER)/Inter\ Web/inter.css ./static/stylesheets/
sed -i 's/url(\"/url(\"\/public\/fonts\//g' ./static/stylesheets/inter.css
rm -rf ./Inter*
clean-fonts:
rm -rf ./static/fonts
rm -rf ./Inter*
rm -rf ./static/stylesheets/inter.css

3
Rocket.toml Normal file
View File

@ -0,0 +1,3 @@
[default]
address = "10.0.1.5"
port = 8000

26
scss/application.scss Normal file
View File

@ -0,0 +1,26 @@
$body-bg: #212529;
$body-color: #f1f3f5;
$link-color: #1098ad;
$headings-color: #1098ad;
$code-color: #ffffff;
$pre-color: #ffffff;
$input-color: #343a40;
$theme-colors: (
"primary": #1098ad,
"secondary": #343a40,
"body": #2a2a2a,
"danger": #e45735,
"warning": #f7941d,
"success": #1ca551,
"love": #ff3366,
"gray": #454545,
"dark-gray": #252525,
"brown": #4b413a,
);
$accordion-btn-color: $headings-color;
$accordion-button-active-color: $headings-color;
@import "./bootstrap/scss/bootstrap";
@import "fonts";

12
scss/fonts.scss Normal file
View File

@ -0,0 +1,12 @@
.inter { font-family: 'Inter var', sans-serif; }
a {
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
.navbar a:hover {
text-decoration: none;
}

View File

@ -1,31 +1,74 @@
use std::env;
#[macro_use] extern crate rocket;
fn main() -> std::io::Result<()> {
let mut i3 = sway_ipc::I3msg::new(env!("SWAYSOCK"));
match i3.get_workspaces() {
Ok(workspaces) => {
for ws in workspaces {
for float in ws.floating_nodes {
match float.app_id {
Some(app_id) => println!("{}", app_id),
None => println!("none"),
}
}
}
},
Err(e) => {
eprintln!("error: {e:?}");
use rocket::fs::{FileServer};
use rocket::response::status;
use rocket::response::status::Custom;
use rocket::http::Status;
use rocket::form::{self, Form, Error};
use rocket_dyn_templates::{Template, context};
use serde::{Serialize};
use sway_ipc::Sway;
#[derive(Serialize)]
struct App<'a> {
name: &'a str,
description: &'a str,
}
impl<'a> App<'a> {
fn new() -> App<'a> {
return App {
name: "sway-ipc",
description: "some description",
}
}
match i3.get_outputs() {
Ok(outputs) => {
for output in outputs {
println!("output: {}", output.name);
}
#[get("/")]
async fn index() -> Result<Template, Custom<String>> {
let mut ipc = match Sway::new(env!("SWAYSOCK")) {
Ok(ipc) => ipc,
Err(e) => return Err(status::Custom(Status::InternalServerError, e.to_string())),
};
let outputs = match ipc.get_outputs() {
Ok(outputs) => outputs,
Err(e) => return Err(status::Custom(Status::InternalServerError, e.to_string())),
};
Ok(Template::render("index", context!{
title: "sway-ipc",
app: &App::new(),
outputs: outputs,
}))
}
#[derive(FromForm)]
struct Input {
resolution: String
}
#[post("/outputs", data = "<input>")]
async fn output(input: Form<Input>) {
let mut ipc = match Sway::new(env!("SWAYSOCK")) {
Ok(ipc) => ipc,
Err(e) => return eprintln!("{}", e.to_string()),
};
let test = format!("output DP-1 resolution {}", input.resolution);
match ipc.run_command(&test) {
Ok(success) => {
for v in success {
println!("{}", v.success);
}
},
Err(e) => {
eprintln!("error: {e:?}");
}
}
Ok(())
Err(e) => eprintln!("{}", e.to_string()),
};
println!("{:?}", input.resolution);
}
#[launch]
async fn rocket() -> _ {
rocket::build()
.mount("/", routes![index, output])
.mount("/public", FileServer::from("static/"))
.attach(Template::fairing())
}

View File

@ -0,0 +1,59 @@
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
async function sendMessage() {
const [form, label, valid] = await validate("form");
if (!valid) return
const response = await request("/outputs", "POST");
if (response.ok) {
form.reset();
label.then(resp => {
resp.textContent = "resolution set";
resp.classList.add("text-success");
document.getElementById("form").appendChild(resp);
})
window.location.reload(true);
return
}
label.then(resp => {
resp.textContent = "unable to send";
resp.classList.add("text-danger");
document.getElementById("form").appendChild(resp);
window.location.reload(true);
})
}
async function request(url, method) {
let response = await fetch(url, {
method: method,
body: new FormData(form)
});
return response
}
async function validate(id) {
var label = message();
var form = document.getElementById(id);
if (form.checkValidity() == false) {
form.reportValidity();
return [form, label, false]
}
return [form, label, true]
}
async function message() {
let label = document.createElement("span");
label.id = "label";
label.classList.add("p-2");
remove("label");
return label
}
async function remove(id) {
elem = document.getElementById(id);
if (elem != null) {
elem.parentNode.removeChild(elem);
}
}

12
templates/base.html.tera Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html class="h-100" lang="en">
{% include "header" %}
<body class="d-flex flex-column h-100">
<header class="m-5">
</header>
<div class="container-md mt-3 mb-3 m-5">
{% block content %}{% endblock content %}
</div>
<!--{% include "footer" %}-->
</body>
</html>

View File

@ -0,0 +1,15 @@
<footer class="footer mt-auto p-5">
{% block footer %}
<ul class="nav justify-content-center">
<li class="nav-item">
<a class="nav-link inter" href="/privacy">Privacy</a>
</li>
<li class="nav-item">
<a class="nav-link inter" href="/terms">Terms</a>
</li>
<li class="nav-item">
<a class="nav-link inter" href="/contact">Contact</a>
</li>
</ul>
{% endblock footer %}
</footer>

View File

@ -0,0 +1,24 @@
<head>
{% block head %}
{% if title == app.name %}
<title>{{app.name}}</title>
{% else %}
<title>{{app.name}} - {{title}}</title>
{% endif %}
<meta charset="utf-8">
<meta property="og:title" content="{{title}}">
<meta property="og:site_name" content="{{app.name}}">
<meta property="og:image" content="public/images/icon.png">
<meta property="og:image:width" content="128">
<meta property="og:image:height" content="128">
<meta property="og:description" content="{{app.description}}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--<link rel="icon" type="image/svg+xml" href="/static/images/icon.svg">-->
<link rel="stylesheet" href="public/stylesheets/bootstrap.min.css" type="text/css">
<link rel="stylesheet" href="public/stylesheets/inter.css" type="text/css">
<link rel="stylesheet" href="public/stylesheets/application.css" type="text/css">
<script src="public/javascript/jquery-3.6.0.slim.min.js"></script>
<script src="public/javascript/bootstrap.bundle.min.js"></script>
<script src="public/javascript/application.js"></script>
{% endblock head %}
</head>

33
templates/index.html.tera Normal file
View File

@ -0,0 +1,33 @@
{% extends "base" %}
{% block content %}
<div class="d-grid gap-3 mb-3">
<h1 class="inter">{{app.name}}</h1>
<p class="px-1 fs-5 inter">{{app.description}}</p>
<h2 class="inter">Outputs</h2>
{% for output in outputs -%}
<div class="card rounded bg-secondary">
<div class="card-body">
<h4>
<a href="">{{output.model}} ({{output.name}})</a>
<span class="badge bg-primary">{{output.current_mode.width}}x{{output.current_mode.height}}</span>
</h4>
<a class="font-weight-bold text-light" href=""></a>
<form id="form">
<div class="form-group">
<select class="form-select bg-light form-select-sm mb-2" id="resolution" name="resolution" aria-label="Default select example">
<option selected>Open this select menu</option>
{% for mode in output.modes -%}
<option value="{{mode.width}}x{{mode.height}}">{{mode.width}}x{{mode.height}} @{{mode.refresh/1000}} Hz</option>
{% endfor -%}
</select>
<button type="button" class="btn btn-primary" onclick="sendMessage();">Send</button>
</div>
</form>
</div>
<div class="card-footer bg-secondary border-0">
<small>{{output.make}}</small>
</div>
</div>
{% endfor -%}
</div>
{% endblock content %}