@@ -0,0 +1,2 @@ | |||
[target.armv7-unknown-linux-gnueabihf] | |||
linker = "arm-linux-gnueabihf-gcc" |
@@ -0,0 +1,11 @@ | |||
# Rust | |||
/target | |||
**/*.rs.bk | |||
Cargo.lock | |||
# Clickable | |||
.clickable/ | |||
build/ | |||
# JetBrains IDEs | |||
.idea/ |
@@ -0,0 +1,19 @@ | |||
[package] | |||
name = "radiate" | |||
version = "0.0.0" | |||
authors = ["Colin Reeder <colin@vpzom.click"] | |||
edition = "2018" | |||
build = "src/build.rs" | |||
[dependencies] | |||
qmetaobject = "0.0.5" | |||
cstr = "0.1.0" | |||
cpp = "0.4" | |||
tokio = "0.1.15" | |||
huey = { git = "https://git.vpzom.click/vpzom/huey", rev = "88d3550c" } | |||
# huey = { path = "../hueclient" } | |||
futures = "0.1.25" | |||
try_future = "0.1.3" | |||
[build-dependencies] | |||
cpp_build = "0.4" |
@@ -0,0 +1,3 @@ | |||
# Radiate | |||
A client app for Philips Hue lights |
@@ -0,0 +1,24 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg style="enable-background:new" xmlns="http://www.w3.org/2000/svg" height="512" viewBox="0 0 512.40002 512" width="512.4" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink"> | |||
<defs> | |||
<linearGradient id="a" y2="494" gradientUnits="userSpaceOnUse" x2="256" gradientTransform="matrix(1 0 0 1.076 0 -19.36)" y1="18" x1="256"> | |||
<stop style="stop-color:#eaeaea" offset="0"/> | |||
<stop style="stop-color:#f4f4f4" offset="1"/> | |||
</linearGradient> | |||
</defs> | |||
<rect style="color:#000000;enable-background:accumulate;fill:url(#a)" height="512" width="512" y="0" x="0"/> | |||
<path style="opacity:.1" d="m256 112c-79.5 0-144 64.5-144 144s64.5 144 144 144 144-64.5 144-144-64.5-144-144-144zm49.47 39.98c3.098 0.0797 6.222 0.9104 9.096 2.568 9.197 5.312 12.34 17.07 7.029 26.26-5.305 9.196-17.06 12.34-26.25 7.029-9.19-5.305-12.34-17.06-7.039-26.25 3.653-6.322 10.35-9.787 17.17-9.611zm-49.48 20.5c7.731 0 15.21 1.074 22.31 3.035 1.253 7.726 5.837 14.84 13.15 19.06 7.298 4.207 15.74 4.629 23.05 1.867 14.22 13.98 23.47 32.99 24.81 54.17l-27.39 0.4043c-2.5-28.7-26.6-51.2-55.9-51.2-8.451 0-16.47 1.88-23.65 5.225l-13.4-23.9c11.16-5.522 23.72-8.645 37.01-8.645zm-46.33 14.03 14.06 23.54c-14.44 10.16-23.88 26.95-23.88 45.95s9.437 35.78 23.88 45.94l-14.06 23.55c-16.83-11.24-29.34-28.43-34.54-48.55 6.069-4.952 9.953-12.49 9.953-20.94 0-8.451-3.884-15.99-9.953-20.94 5.201-20.12 17.72-37.3 34.54-48.55zm-51.58 50.26c10.62 0 19.22 8.608 19.22 19.23s-8.605 19.22-19.22 19.22-19.23-8.605-19.23-19.22 8.607-19.23 19.23-19.23zm153.8 24.21 27.4 0.4023c-1.348 21.18-10.6 40.19-24.81 54.17-7.31-2.762-15.75-2.347-23.05 1.867-7.309 4.227-11.89 11.34-13.15 19.06-7.104 1.967-14.58 3.041-22.31 3.041-13.29 0-25.86-3.124-37.01-8.652l13.36-23.94c7.186 3.344 15.2 5.225 23.65 5.225 29.34 0 53.41-22.49 55.93-51.18zm-7.482 60.6c6.816-0.1758 13.51 3.289 17.16 9.607 5.311 9.197 2.16 20.95-7.035 26.25-9.196 5.311-20.95 2.159-26.25-7.037-5.312-9.191-2.159-20.94 7.037-26.25 2.872-1.658 5.994-2.49 9.092-2.57z"/> | |||
<g transform="translate(0,18)"> | |||
<path style="opacity:.3;color:#000000;enable-background:accumulate;fill:#fff" d="m256 256l-147.8 256h403.8v-256h-122.7-133.3z" transform="translate(0,-18)"/> | |||
<path style="opacity:.15;color:#000000;enable-background:accumulate;fill:#fff" d="m0 0v512h108.2l76.4-132.3 71.4-123.7-68.9-120-0.2 0.3-78.7-136.3h-108.2z" transform="translate(0,-18)"/> | |||
</g> | |||
<path d="m-208.3 605.6c-7.842 0-14.55 5.029-17.32 12.19-0.0612-0.00067-0.1208 0-0.1823 0-7.088 0-12.83 6.002-12.83 13.41 0 7.404 5.746 13.41 12.83 13.41h17.5 16.33c5.155 0 9.333-4.365 9.333-9.75 0-4.543-2.974-8.363-7-9.445 0.001-0.1029 0-0.2015 0-0.3047 0-10.77-8.357-19.5-18.67-19.5z" style="stroke-width:4;color:#000000;stroke:#ccc;enable-background:accumulate;display:none;fill:#fff"/> | |||
<path style="stroke-width:4;color:#000000;stroke:#ccc;enable-background:accumulate;display:none;fill:none" d="m288.9 828.1c-6.349 0-11.78 4.072-14.02 9.867-0.0496-0.00055-0.0978 0-0.1476 0-5.738 0-10.39 4.86-10.39 10.85 0 5.995 4.652 10.85 10.39 10.85h14.17 13.22c4.173 0 7.557-3.534 7.557-7.894 0-3.678-2.408-6.771-5.667-7.647 0.001-0.0833 0-0.1632 0-0.2467 0-8.719-6.766-15.79-15.11-15.79z"/> | |||
<g style="display:none" transform="translate(0 -558.4)"> | |||
<path d="m166.1 576.4h179.9c145.3 0 166.1 20.74 166.1 165.9v144.2c0 145.2-20.76 165.9-166.1 165.9h-179.9c-145.3 0-166.1-20-166.1-165.5v-144.2c0-145.2 20.76-165.9 166.1-165.9z" style="stroke:#00f;fill:#00f;display:inline;fill-opacity:.08118"/> | |||
<circle style="stroke-width:.8333;fill-opacity:.08118;color:#000000;stroke:#00f;enable-background:accumulate;display:inline;fill:#00f" cx="256" transform="matrix(1.2 0 0 1.2 -51.2 528.8)" cy="238" r="120"/> | |||
<rect style="stroke-linejoin:round;fill-opacity:.08118;color:#000000;stroke:#00f;stroke-linecap:round;enable-background:accumulate;display:inline;fill:#00f" height="272" width="272" y="678.4" x="120"/> | |||
<rect style="stroke-linejoin:round;fill-opacity:.08118;color:#000000;stroke:#00f;stroke-linecap:round;enable-background:accumulate;display:inline;fill:#00f" height="240" width="304" y="694.4" x="104"/> | |||
<rect style="stroke-linejoin:round;fill-opacity:.08118;color:#000000;stroke:#00f;stroke-linecap:round;enable-background:accumulate;display:inline;fill:#00f" height="304" width="240" y="662.4" x="136"/> | |||
</g> | |||
</svg> |
@@ -0,0 +1,15 @@ | |||
{ | |||
"name": "radiate.vpzom", | |||
"description": "A client app for Philips Hue lights", | |||
"architecture": "armhf", | |||
"title": "Radiate", | |||
"hooks": { | |||
"radiate": { | |||
"apparmor": "radiate.apparmor", | |||
"desktop": "radiate.desktop" | |||
} | |||
}, | |||
"version": "0.0.0", | |||
"maintainer": "Colin Reeder <colin@vpzom.click>", | |||
"framework" : "ubuntu-sdk-16.04" | |||
} |
@@ -0,0 +1,4 @@ | |||
{ | |||
"policy_groups": ["networking"], | |||
"policy_version": 16.04 | |||
} |
@@ -0,0 +1,7 @@ | |||
[Desktop Entry] | |||
Name=Radiate | |||
Exec=radiate | |||
Icon=assets/logo.svg | |||
Terminal=false | |||
Type=Application | |||
X-Ubuntu-Touch=true |
@@ -0,0 +1,5 @@ | |||
{ | |||
"template": "rust", | |||
"kill": "radiate" | |||
} | |||
@@ -0,0 +1,69 @@ | |||
import QtQuick 2.0 | |||
import QtQuick.Layouts 1.0 | |||
import QtQuick.Controls 2.0 | |||
import RadiateApp 0.0 | |||
ApplicationWindow { | |||
id: window | |||
width: 640 | |||
height: 480 | |||
title: "Radiate" | |||
StackView { | |||
id: stackView | |||
anchors.fill: parent | |||
initialItem: Page { | |||
Component.onCompleted: { | |||
stackView.replace(connectPage) | |||
} | |||
} | |||
} | |||
Component.onCompleted: { | |||
show(); | |||
} | |||
Component { | |||
id: connectPage | |||
Page { | |||
header: ToolBar { | |||
RowLayout { | |||
anchors.fill: parent | |||
Label { | |||
text: "Connect to Bridge" | |||
} | |||
Item { Layout.fillWidth: true } | |||
} | |||
} | |||
ColumnLayout { | |||
Label { | |||
id: statusText | |||
text: "Searching for Hue bridges..." | |||
} | |||
} | |||
BridgeConnector { | |||
id: connector | |||
onDiscovered_bridges: { | |||
statusText.text = "Found %1 bridge(s). Press the button to pair.".arg(count) | |||
} | |||
onErrored: { | |||
statusText.text = "Error: %1".arg(err) | |||
} | |||
onAuthenticated: { | |||
statusText.text = "Connected." | |||
} | |||
} | |||
Component.onCompleted: { | |||
connector.start_searching() | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,54 @@ | |||
/* Copyright (C) 2018 Olivier Goffart <ogoffart@woboq.com> | |||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | |||
associated documentation files (the "Software"), to deal in the Software without restriction, | |||
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, | |||
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, | |||
subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all copies or substantial | |||
portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | |||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES | |||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||
*/ | |||
extern crate cpp_build; | |||
use std::process::Command; | |||
fn qmake_query(var: &str) -> String { | |||
String::from_utf8( | |||
Command::new("qmake") | |||
.args(&["-query", var]) | |||
.output() | |||
.expect("Failed to execute qmake. Make sure 'qmake' is in your path") | |||
.stdout, | |||
).expect("UTF-8 conversion failed") | |||
} | |||
fn main() { | |||
let qt_include_path = qmake_query("QT_INSTALL_HEADERS"); | |||
let qt_library_path = qmake_query("QT_INSTALL_LIBS"); | |||
cpp_build::Config::new() | |||
.include(qt_include_path.trim()) | |||
.build("src/main.rs"); | |||
let macos_lib_search = if cfg!(target_os = "macos") { | |||
"=framework" | |||
} else { | |||
"" | |||
}; | |||
let macos_lib_framework = if cfg!(target_os = "macos") { "" } else { "5" }; | |||
println!( | |||
"cargo:rustc-link-search{}={}", | |||
macos_lib_search, | |||
qt_library_path.trim() | |||
); | |||
println!("cargo:rustc-link-lib{}=Qt{}Widgets", macos_lib_search, macos_lib_framework); | |||
println!("cargo:rustc-link-lib{}=Qt{}Gui", macos_lib_search, macos_lib_framework); | |||
println!("cargo:rustc-link-lib{}=Qt{}Core", macos_lib_search, macos_lib_framework); | |||
println!("cargo:rustc-link-lib{}=Qt{}Quick", macos_lib_search, macos_lib_framework); | |||
println!("cargo:rustc-link-lib{}=Qt{}Qml", macos_lib_search, macos_lib_framework); | |||
println!("cargo:rustc-link-lib{}=Qt{}QuickControls2", macos_lib_search, macos_lib_framework); | |||
} |
@@ -0,0 +1,121 @@ | |||
#[macro_use] | |||
extern crate cstr; | |||
#[macro_use] | |||
extern crate cpp; | |||
#[macro_use] | |||
extern crate qmetaobject; | |||
use futures::{Future}; | |||
use qmetaobject::*; | |||
mod qrc; | |||
#[derive(QObject, Default)] | |||
pub struct BridgeConnector { | |||
base: qt_base_class!(trait QObject), | |||
discovered_bridges: qt_signal!(count: usize), | |||
errored: qt_signal!(err: String), | |||
authenticated: qt_signal!(username: String), | |||
start_searching: qt_method!(fn(&self)), | |||
} | |||
impl BridgeConnector { | |||
fn start_searching(&self) { | |||
let callback_discovered = { | |||
let ptr = QPointer::from(&*self); | |||
queued_callback(move |count| { | |||
ptr.as_ref().map(|x| x.discovered_bridges(count)); | |||
}) | |||
}; | |||
let callback_errored_1 = { | |||
let ptr = QPointer::from(&*self); | |||
queued_callback(move |err| { | |||
ptr.as_ref().map(|x| x.errored(err)); | |||
}) | |||
}; | |||
let callback_errored_2 = callback_errored_1.clone(); | |||
let callback_authenticated = { | |||
let ptr = QPointer::from(&*self); | |||
queued_callback(move |username| { | |||
ptr.as_ref().map(|x| x.authenticated(username)); | |||
}) | |||
}; | |||
tokio::spawn(huey::bridge_discovery::discover() | |||
.then(move |result| { | |||
println!("{:?}", result); | |||
match result { | |||
Ok(bridges) => { | |||
if bridges.len() == 0 { | |||
callback_errored_1("Couldn't find a bridge.".to_owned()); | |||
Err(()) | |||
} | |||
else { | |||
callback_discovered(bridges.len()); | |||
Ok(bridges) | |||
} | |||
}, | |||
Err(err) => { | |||
eprintln!("{:?}", err); | |||
callback_errored_1(format!("{}", err)); | |||
Err(()) | |||
} | |||
} | |||
}) | |||
.and_then(move |bridges| { | |||
fn run(bridges: Vec<huey::bridge_discovery::DiscoveredBridge>, i: usize) -> Box<Future<Item=String, Error=huey::HueError> + Send> { | |||
let bridge: &huey::bridge_discovery::DiscoveredBridge = &bridges[i]; | |||
Box::new(huey::registration::register(&bridge.internalipaddress, "radiate") | |||
.or_else(move |err| { | |||
match err { | |||
huey::HueError::LinkButtonNotPressed => { | |||
let i = if i + 1 < bridges.len() { | |||
i + 1 | |||
} else { | |||
0 | |||
}; | |||
run(bridges, i) | |||
}, | |||
other_err => { | |||
Box::new(futures::future::err(other_err)) | |||
} | |||
} | |||
})) | |||
} | |||
run(bridges, 0) | |||
.then(move |res| { | |||
match res { | |||
Ok(username) => { | |||
callback_authenticated(username); | |||
}, | |||
Err(err) => { | |||
eprintln!("{:?}", err); | |||
callback_errored_2(format!("{}", err)); | |||
}, | |||
} | |||
Ok(()) | |||
}) | |||
})); | |||
} | |||
} | |||
fn main() { | |||
tokio::run(futures::lazy(move || { | |||
unsafe { | |||
cpp! { { | |||
#include <QtCore/QCoreApplication> | |||
}} | |||
cpp!{[]{ | |||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); | |||
}} | |||
} | |||
// QQuickStyle::set_style("Suru"); | |||
qrc::load(); | |||
let plugin_name = cstr!("RadiateApp"); | |||
qml_register_type::<BridgeConnector>(plugin_name, 0, 0, cstr!("BridgeConnector")); | |||
let mut engine = QmlEngine::new(); | |||
engine.load_file("qrc:/qml/Main.qml".into()); | |||
engine.exec(); | |||
Ok(()) | |||
})) | |||
} |
@@ -0,0 +1,9 @@ | |||
qrc!(qml_resources, | |||
"/" { | |||
"qml/Main.qml" | |||
}, | |||
); | |||
pub fn load() { | |||
qml_resources(); | |||
} |