/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2026 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
// Hides the cmd.exe terminal on Windows.
// Enable this when making release builds.
#![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
use clap::Parser;
use darkfi::{system::CondVar, tx::Transaction, util::parse::encode_base10};
use darkfi_money_contract::model::DARK_TOKEN_ID;
use darkfi_serial::{deserialize, Decodable, Encodable};
use smol::Task;
use std::sync::{Arc, OnceLock};
#[macro_use]
extern crate tracing;
#[cfg(target_os = "android")]
mod android;
mod app;
mod build_info;
mod clipboard;
mod error;
mod expr;
mod gfx;
mod logger;
mod mesh;
#[cfg(feature = "enable-netdebug")]
mod net;
mod plugin;
mod prop;
mod pubsub;
//mod py;
//mod ringbuf;
mod scene;
mod shape;
mod text;
mod ui;
mod util;
use crate::{
app::{App, AppPtr},
gfx::EpochIndex,
prop::{Property, PropertySubType, PropertyType},
scene::{CallArgType, SceneNode, SceneNodePtr, SceneNodeType},
util::AsyncRuntime,
};
#[cfg(feature = "enable-netdebug")]
use net::ZeroMQAdapter;
use {
// Local imports
app::schema::get_main_db_path,
gfx::Renderer,
prop::{PropertyBool, PropertyStr, Role},
scene::Slot,
// Global imports
sled_overlay::sled,
std::io::Cursor,
ui::chatview,
url::Url,
};
// This is historical, but ideally we can fix the entire project and remove this import.
pub use util::ExecutorPtr;
macro_rules! t { ($($arg:tt)*) => { trace!(target: "main", $($arg)*); } }
macro_rules! d { ($($arg:tt)*) => { trace!(target: "main", $($arg)*); } }
macro_rules! i { ($($arg:tt)*) => { trace!(target: "main", $($arg)*); } }
fn panic_hook(panic_info: &std::panic::PanicHookInfo) {
error!("panic occurred: {panic_info}");
error!("{}", std::backtrace::Backtrace::force_capture().to_string());
std::process::abort()
}
/// Contains values which persist between app restarts. For example on Android, we are
/// running a foreground service. Everytime the UI restarts main() is called again.
/// However the global state remains intact.
struct God {
_bg_runtime: AsyncRuntime,
_bg_ex: ExecutorPtr,
pub fg_runtime: AsyncRuntime,
_fg_ex: ExecutorPtr,
/// App must fully finish setup() before start() is allowed to begin.
cv_app_is_setup: Arc,
app: AppPtr,
/// This is the main rendering API used to send commands to the gfx subsystem.
/// We have a ref here so the gfx subsystem can increment the epoch counter.
renderer: gfx::Renderer,
/// This is how the gfx subsystem receives messages from the render API.
method_recv: async_channel::Receiver<(gfx::EpochIndex, gfx::GraphicsMethod)>,
/// Publisher to send input and window events to subscribers.
event_pub: gfx::GraphicsEventPublisherPtr,
/// A WorkerGuard for file logging used to ensure buffered logs are flushed
/// to their output in the case of abrupt terminations of a process.
_file_logging_guard: Option,
}
impl God {
fn new() -> Self {
// Abort the application on panic right away
std::panic::set_hook(Box::new(panic_hook));
let file_logging_guard = logger::setup_logging();
info!(target: "main", "Creating the app");
#[cfg(target_os = "android")]
{
use crate::android::get_appdata_path;
// Workaround for this bug
// https://gitlab.torproject.org/tpo/core/arti/-/issues/999
unsafe {
std::env::set_var("HOME", get_appdata_path().as_os_str());
}
}
let exe_path = std::env::current_exe().unwrap();
let basename = exe_path.parent().unwrap();
std::env::set_current_dir(basename).unwrap();
let db_path = get_main_db_path();
let db = sled::open(&db_path).expect("Sled DB failed to open");
let bg_ex = Arc::new(smol::Executor::new());
let fg_ex = Arc::new(smol::Executor::new());
let sg_root = SceneNode::root();
let bg_runtime = AsyncRuntime::new(bg_ex.clone(), "bg");
bg_runtime.start();
let fg_runtime = AsyncRuntime::new(fg_ex.clone(), "fg");
let (method_send, method_recv) = async_channel::unbounded();
// The UI actually needs to be running for this to reply back.
// Otherwise calls will just hang.
let renderer = gfx::Renderer::new(method_send);
let event_pub = gfx::GraphicsEventPublisher::new();
let app = App::new(sg_root.clone(), renderer.clone(), fg_ex.clone());
let app2 = app.clone();
let cv_app_is_setup = Arc::new(CondVar::new());
let cv = cv_app_is_setup.clone();
let db2 = db.clone();
let app_task = fg_ex.spawn(async move {
app2.setup(db2).await.unwrap();
cv.notify();
});
fg_runtime.push_task(app_task);
#[cfg(feature = "enable-netdebug")]
{
let sg_root = sg_root.clone();
let ex = bg_ex.clone();
let renderer = renderer.clone();
let zmq_task = bg_ex.spawn(async {
i!("Enabled net debugging backend in this build");
let zmq_rpc = ZeroMQAdapter::new(sg_root, renderer, ex).await;
zmq_rpc.run().await;
});
bg_runtime.push_task(zmq_task);
}
{
let ex = bg_ex.clone();
let cv = cv_app_is_setup.clone();
let renderer = renderer.clone();
let plug_task = bg_ex.spawn(async move {
load_plugins(ex, sg_root, renderer, cv, db).await;
});
bg_runtime.push_task(plug_task);
}
Self {
_bg_runtime: bg_runtime,
_bg_ex: bg_ex,
fg_runtime,
_fg_ex: fg_ex,
cv_app_is_setup,
app,
renderer,
method_recv,
event_pub,
_file_logging_guard: file_logging_guard,
}
}
/// Start the app. Can only happen once the window is ready.
pub fn start_app(&self, epoch: EpochIndex) {
info!(target: "main", "Starting the app");
#[cfg(target_os = "android")]
{
use crate::android::{get_appdata_path, get_external_storage_path};
info!("App internal data path: {:?}", get_appdata_path());
info!("App external storage path: {:?}", get_external_storage_path());
//let paths = std::fs::read_dir("/data/data/darkfi.darkfi/").unwrap();
//for path in paths {
// debug!("{}", path.unwrap().path().display())
//}
}
info!("Target OS: {}", build_info::TARGET_OS);
info!("Target arch: {}", build_info::TARGET_ARCH);
let cwd = std::env::current_dir().unwrap();
info!("Current dir: {}", cwd.display());
self.fg_runtime.start_with_count(2);
let app = self.app.clone();
let cv = self.cv_app_is_setup.clone();
let event_pub = self.event_pub.clone();
smol::block_on(async move {
cv.wait().await;
app.start(event_pub, epoch).await;
});
self.app.notify_start();
}
/// Put the app to sleep until the next restart.
pub fn stop_app(&self) {
self.app.notify_stop();
self.fg_runtime.stop();
self.app.stop();
info!(target: "main", "App stopped");
}
}
impl std::fmt::Debug for God {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "God")
}
}
static GOD: OnceLock = OnceLock::new();
async fn load_plugins(
ex: ExecutorPtr,
sg_root: SceneNodePtr,
renderer: Renderer,
cv: Arc,
db: sled::Db,
) {
let plugin = SceneNode::new("plugin", SceneNodeType::PluginRoot);
let plugin = plugin.setup_null();
sg_root.link(plugin.clone());
// DarkIrc needs /window to start
cv.wait().await;
let mut listeners: Vec> = vec![];
#[cfg(feature = "enable-plugin-darkirc")]
{
let darkirc = create_darkirc("darkirc");
let darkirc = darkirc
.setup(|me| async {
plugin::DarkIrc::new(me, sg_root.clone(), ex.clone(), db)
.await
.expect("DarkIrc pimpl setup")
})
.await;
let (slot, recvr) = Slot::new("recvmsg");
darkirc.register("recv", slot).unwrap();
let sg_root2 = sg_root.clone();
let darkirc_nick = PropertyStr::wrap(&darkirc, Role::App, "nick", 0).unwrap();
let renderer2 = renderer.clone();
let listen_recv = ex.spawn(async move {
while let Ok(data) = recvr.recv().await {
let atom = &mut renderer2.make_guard(gfxtag!("darkirc msg recv"));
let mut cur = Cursor::new(&data);
let channel = String::decode(&mut cur).unwrap();
let timestamp = chatview::Timestamp::decode(&mut cur).unwrap();
let id = chatview::MessageId::decode(&mut cur).unwrap();
let nick = String::decode(&mut cur).unwrap();
let msg = String::decode(&mut cur).unwrap();
let node_path = format!("/window/content/{channel}_chat_layer/content/chatty");
t!("Attempting to relay message to {node_path}");
let Some(chatview) = sg_root2.lookup_node(&node_path) else {
d!("Ignoring message since {node_path} doesn't exist");
continue
};
// I prefer to just re-encode because the code is clearer.
let mut data = vec![];
timestamp.encode(&mut data).unwrap();
id.encode(&mut data).unwrap();
nick.encode(&mut data).unwrap();
msg.encode(&mut data).unwrap();
if let Err(err) = chatview.call_method("insert_line", data).await {
error!(
target: "app",
"Call method {node_path}::insert_line({timestamp}, {id}, {nick}, '{msg}'): {err:?}"
);
}
// Apply coloring when you get a message
let chat_path = format!("/window/content/{channel}_chat_layer");
let chat_layer = sg_root2.lookup_node(chat_path).unwrap();
if chat_layer.get_property_bool("is_visible").unwrap() {
continue
}
let menu_node = sg_root2.lookup_node("/window/content/menu_layer/main_menu").unwrap();
let group_name = if msg.contains(&darkirc_nick.get()) { "role2_group" } else { "role1_group" };
let group = menu_node.get_property(group_name).unwrap();
if !group.get_str_vec().unwrap().contains(&channel) {
group.push_str(atom, Role::App, &channel).unwrap();
}
}
});
let (slot, recvr) = Slot::new("connect");
darkirc.register("connect", slot).unwrap();
let sg_root2 = sg_root.clone();
let renderer2 = renderer.clone();
let listen_connect = ex.spawn(async move {
let net0 = sg_root2.lookup_node("/window/content/netstatus_layer/net0").unwrap();
let net1 = sg_root2.lookup_node("/window/content/netstatus_layer/net1").unwrap();
let net2 = sg_root2.lookup_node("/window/content/netstatus_layer/net2").unwrap();
let net3 = sg_root2.lookup_node("/window/content/netstatus_layer/net3").unwrap();
let net0_is_visible = PropertyBool::wrap(&net0, Role::App, "is_visible", 0).unwrap();
let net1_is_visible = PropertyBool::wrap(&net1, Role::App, "is_visible", 0).unwrap();
let net2_is_visible = PropertyBool::wrap(&net2, Role::App, "is_visible", 0).unwrap();
let net3_is_visible = PropertyBool::wrap(&net3, Role::App, "is_visible", 0).unwrap();
while let Ok(data) = recvr.recv().await {
let (peers_count, is_dag_synced): (u32, bool) = deserialize(&data).unwrap();
let atom = &mut renderer2.make_guard(gfxtag!("netstatus change"));
if peers_count == 0 {
net0_is_visible.set(atom, true);
net1_is_visible.set(atom, false);
net2_is_visible.set(atom, false);
net3_is_visible.set(atom, false);
continue
}
assert!(peers_count > 0);
if !is_dag_synced {
net0_is_visible.set(atom, false);
net1_is_visible.set(atom, true);
net2_is_visible.set(atom, false);
net3_is_visible.set(atom, false);
continue
}
assert!(peers_count > 0 && is_dag_synced);
if peers_count == 1 {
net0_is_visible.set(atom, false);
net1_is_visible.set(atom, false);
net2_is_visible.set(atom, true);
net3_is_visible.set(atom, false);
continue
}
net0_is_visible.set(atom, false);
net1_is_visible.set(atom, false);
net2_is_visible.set(atom, false);
net3_is_visible.set(atom, true);
}
});
plugin.link(darkirc);
listeners.push(listen_recv);
listeners.push(listen_connect);
}
#[cfg(feature = "enable-plugin-fud")]
{
let fud = create_fud("fud");
let sg_root2 = sg_root.clone();
let fud = fud
.setup(|me| async {
plugin::FudPlugin::new(me, sg_root2, ex.clone()).await.expect("Fud pimpl setup")
})
.await;
let (slot, recv) = Slot::new("file_status_update");
let _ = fud.register("file_status_updated", slot);
let sg_root2 = sg_root.clone();
let listen_file_status = ex.spawn(async move {
while let Ok(data) = recv.recv().await {
let window = sg_root2.lookup_node("/window/content").unwrap();
let mut cur = Cursor::new(&data);
let url = Url::decode(&mut cur).unwrap();
let status = chatview::FileMessageStatus::decode(&mut cur).unwrap();
for child in window.get_children() {
if let Some(chatty) = child.lookup_node("/content/chatty") {
let mut data = vec![];
url.encode(&mut data).unwrap();
status.encode(&mut data).unwrap();
let _ = chatty.call_method("set_file_status", data).await;
}
}
}
});
plugin.link(fud);
listeners.push(listen_file_status);
}
#[cfg(feature = "enable-plugin-drk")]
{
let drk = create_drk("drk");
let drk = drk
.setup(|me| async {
plugin::DrkPlugin::new(me, sg_root.clone(), ex.clone())
.await
.expect("Drk pimpl setup")
})
.await;
let (slot, recvr) = Slot::new("connect");
drk.register("connect", slot).unwrap();
let sg_root2 = sg_root.clone();
let renderer2 = renderer.clone();
let listen_connect = ex.spawn(async move {
let net0 = sg_root2.lookup_node("/window/content/wallet/netstatus_layer/net0").unwrap();
let net1 = sg_root2.lookup_node("/window/content/wallet/netstatus_layer/net1").unwrap();
let net2 = sg_root2.lookup_node("/window/content/wallet/netstatus_layer/net2").unwrap();
let net3 = sg_root2.lookup_node("/window/content/wallet/netstatus_layer/net3").unwrap();
let net0_is_visible = PropertyBool::wrap(&net0, Role::App, "is_visible", 0).unwrap();
let net1_is_visible = PropertyBool::wrap(&net1, Role::App, "is_visible", 0).unwrap();
let net2_is_visible = PropertyBool::wrap(&net2, Role::App, "is_visible", 0).unwrap();
let net3_is_visible = PropertyBool::wrap(&net3, Role::App, "is_visible", 0).unwrap();
while let Ok(data) = recvr.recv().await {
let status: u8 = deserialize(&data).unwrap();
let atom = &mut renderer2.make_guard(gfxtag!("blockchain netstatus change"));
match status {
1 => {
net0_is_visible.set(atom, false);
net1_is_visible.set(atom, true);
net2_is_visible.set(atom, false);
net3_is_visible.set(atom, false);
}
2 => {
net0_is_visible.set(atom, false);
net1_is_visible.set(atom, false);
net2_is_visible.set(atom, true);
net3_is_visible.set(atom, false);
}
3 => {
net0_is_visible.set(atom, false);
net1_is_visible.set(atom, false);
net2_is_visible.set(atom, false);
net3_is_visible.set(atom, true);
}
_ => {
net0_is_visible.set(atom, true);
net1_is_visible.set(atom, false);
net2_is_visible.set(atom, false);
net3_is_visible.set(atom, false);
}
}
}
});
let (slot, recv) = Slot::new("balances_update");
let _ = drk.register("balances_updated", slot);
let sg_root2 = sg_root.clone();
let renderer2 = renderer.clone();
let drk_node2 = drk.clone();
let listen_balances = ex.spawn(async move {
use crate::ui::TokenRow;
use darkfi_money_contract::model::TokenId;
use darkfi_serial::Encodable;
let update = async || {
d!("drk balances_updated signal received");
// Fetch and update main wallet tokens table
if let Ok(Some(response_data)) = drk_node2.call_method("get_balances", vec![]).await
{
let atom = &mut renderer2.make_guard(gfxtag!("wallet - refresh tokens"));
let mut cur = std::io::Cursor::new(response_data);
if let Ok(balances) = Vec::<(String, TokenId, u64)>::decode(&mut cur) {
let token_rows: Vec = balances
.iter()
.map(|(symbol, token_id, balance)| TokenRow {
id: *token_id,
symbol: symbol.clone(),
balance: encode_base10(*balance, 8),
})
.collect();
let mut data: Vec = vec![];
for row in &token_rows {
let _ = TokenRow::encode(row, &mut data);
}
if let Some(tokens_table) =
sg_root2.lookup_node("/window/content/wallet/main_layer/tokens_table")
{
let _ = tokens_table.call_method("set_tokens", data.clone()).await;
}
if let Some(send_tokens_table) = sg_root2
.lookup_node("/window/content/wallet/send_step1_layer/tokens_table")
{
let _ = send_tokens_table.call_method("set_tokens", data).await;
}
// Update main wallet balance
if let Some(drk_row) =
token_rows.iter().find(|row| row.id == *DARK_TOKEN_ID)
{
if let Some(balance_node) = sg_root2
.lookup_node("/window/content/wallet/main_layer/wallet_balance")
{
balance_node
.set_property_str(
atom,
Role::App,
"text",
format!("DRK {}", drk_row.balance),
)
.unwrap();
}
}
if let Some(tx_status_layer) =
sg_root2.lookup_node("/window/content/wallet/tx_status_layer")
{
let tx_id = tx_status_layer.get_property_str("tx_id").unwrap();
if !tx_id.is_empty() {
let mut tx_id_data = vec![];
tx_id.encode(&mut tx_id_data).unwrap();
if let Ok(Some(data)) =
drk_node2.call_method("get_tx_status", tx_id_data).await
{
let mut cur = std::io::Cursor::new(data);
let status_text = String::decode(&mut cur).unwrap();
if let Some(status_node) =
tx_status_layer.lookup_node("/status")
{
status_node
.set_property_str(atom, Role::App, "text", status_text)
.unwrap();
}
}
}
}
}
}
};
update().await;
while let Ok(_) = recv.recv().await {
update().await;
}
});
let (slot, recv) = Slot::new("tx_updated");
let _ = drk.register("tx_updated", slot);
let sg_root2 = sg_root.clone();
let listen_tx = ex.spawn(async move {
while let Ok(data) = recv.recv().await {
if let Some(tx_status_layer) =
sg_root2.lookup_node("/window/content/wallet/tx_status_layer")
{
let _ = tx_status_layer.call_method("set_tx_status", data).await;
}
}
});
// Listen for tx_built signal - emitted when transaction is built (non-blocking)
let (slot, recv) = Slot::new("tx_built");
let _ = drk.register("tx_built", slot);
let sg_root2 = sg_root.clone();
let renderer2 = renderer.clone();
let listen_tx_built = ex.spawn(async move {
while let Ok(data) = recv.recv().await {
let mut cur = std::io::Cursor::new(data);
let amount = String::decode(&mut cur).unwrap();
let token_symbol = String::decode(&mut cur).unwrap();
let recipient_str = String::decode(&mut cur).unwrap();
// Decode transaction and pass to wallet schema
let tx = Transaction::decode(&mut cur).unwrap();
// Update tx_status_layer with built transaction
let atom = &mut renderer2.make_guard(gfxtag!("tx built"));
if let Some(tx_status) =
sg_root2.lookup_node("/window/content/wallet/tx_status_layer")
{
let mut tx_status_data = vec![];
None::.encode(&mut tx_status_data).unwrap();
Some("Broadcasting transaction...".to_string())
.encode(&mut tx_status_data)
.unwrap();
Some(amount).encode(&mut tx_status_data).unwrap();
Some(token_symbol).encode(&mut tx_status_data).unwrap();
Some(recipient_str).encode(&mut tx_status_data).unwrap();
let _ = tx_status.call_method("set_tx_status", tx_status_data).await;
// Call set_built_tx to store transaction for later broadcast
let mut set_built_tx_data = vec![];
tx.encode(&mut set_built_tx_data).unwrap();
let _ = tx_status.call_method("set_built_tx", set_built_tx_data).await;
}
// Hide step3 layer
if let Some(step4_layer) =
sg_root2.lookup_node("/window/content/wallet/send_step3_layer")
{
step4_layer.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
}
// Show step4 layer
if let Some(step4_layer) =
sg_root2.lookup_node("/window/content/wallet/send_step4_layer")
{
step4_layer.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
}
}
});
// Listen for tx_built_error signal - emitted when transaction building fails
let (slot, recv) = Slot::new("tx_built_error");
let _ = drk.register("tx_built_error", slot);
let sg_root2 = sg_root.clone();
let renderer2 = renderer.clone();
let listen_tx_built_error = ex.spawn(async move {
while let Ok(data) = recv.recv().await {
let mut cur = std::io::Cursor::new(data);
let error_message = String::decode(&mut cur).unwrap();
let atom = &mut renderer2.make_guard(gfxtag!("tx built error"));
// Display error message in step3
if let Some(error_node) =
sg_root2.lookup_node("/window/content/wallet/send_step3_layer/error")
{
error_node.set_property_str(atom, Role::App, "text", error_message).unwrap();
}
// Reset step4 send button to disabled state
if let Some(send_label_node) =
sg_root2.lookup_node("/window/content/wallet/send_step4_layer/send_btn_label")
{
send_label_node.set_property_str(atom, Role::App, "text", "send").unwrap();
let prop = send_label_node.get_property("text_color").unwrap();
prop.set_f32(atom, Role::App, 0, 0.5).unwrap();
prop.set_f32(atom, Role::App, 1, 0.5).unwrap();
prop.set_f32(atom, Role::App, 2, 0.5).unwrap();
prop.set_f32(atom, Role::App, 3, 1.).unwrap();
}
if let Some(send_bg_grey_node) =
sg_root2.lookup_node("/window/content/wallet/send_step4_layer/send_btn_bg_grey")
{
send_bg_grey_node
.set_property_bool(atom, Role::App, "is_visible", true)
.unwrap();
}
if let Some(send_bg_node) =
sg_root2.lookup_node("/window/content/wallet/send_step4_layer/send_btn_bg")
{
send_bg_node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
}
// Go back to step3
if let Some(step4) = sg_root2.lookup_node("/window/content/wallet/send_step4_layer")
{
step4.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
}
if let Some(step3) = sg_root2.lookup_node("/window/content/wallet/send_step3_layer")
{
step3.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
}
// Focus the amount input
if let Some(amount_input) = sg_root2.lookup_node(
"/window/content/wallet/send_step3_layer/send_amount_wrapper/send_amount_input",
) {
let _ = amount_input.call_method("focus", vec![]).await;
}
}
});
plugin.link(drk);
listeners.push(listen_connect);
listeners.push(listen_balances);
listeners.push(listen_tx);
listeners.push(listen_tx_built);
listeners.push(listen_tx_built_error);
}
i!("Plugins loaded");
futures::future::join_all(listeners).await;
}
pub fn create_darkirc(name: &str) -> SceneNode {
t!("create_darkirc({name})");
let mut node = SceneNode::new(name, SceneNodeType::Plugin);
let mut prop = Property::new("nick", PropertyType::Str, PropertySubType::Null);
prop.set_ui_text("Nick", "Nickname");
prop.set_defaults_str(vec!["anon".to_string()]).unwrap();
node.add_property(prop).unwrap();
let mut prop = Property::new("dm_public", PropertyType::Str, PropertySubType::Null);
prop.set_ui_text("DM Public Key", "Your DM public key (share with contacts)");
prop.allow_null_values();
prop.set_defaults_null().unwrap();
node.add_property(prop).unwrap();
let mut prop = Property::new("outbound_peers", PropertyType::Str, PropertySubType::Null);
prop.set_ui_text("Outbound Peers", "Connected outbound peers");
#[cfg(feature = "enable-plugin-darkirc")]
prop.set_array_len(plugin::darkirc::P2P_OUTBOUND_ACTIVE);
prop.allow_null_values();
prop.set_defaults_null().unwrap();
node.add_property(prop).unwrap();
node.add_signal(
"recv",
"Message received",
vec![
("channel", "Channel", CallArgType::Str),
("timestamp", "Timestamp", CallArgType::Uint64),
("id", "ID", CallArgType::Hash),
("nick", "Nick", CallArgType::Str),
("msg", "Message", CallArgType::Str),
],
)
.unwrap();
node.add_signal(
"connect",
"Connections and disconnects",
vec![
("peers_count", "Peers Count", CallArgType::Uint32),
("dag_synced", "Is DAG Synced", CallArgType::Bool),
],
)
.unwrap();
node.add_method(
"send",
vec![("channel", "Channel", CallArgType::Str), ("msg", "Message", CallArgType::Str)],
None,
)
.unwrap();
node.add_method("reconnect", vec![], None).unwrap();
node.add_method("rescan", vec![("channel", "Channel", CallArgType::Str)], None).unwrap();
node
}
pub fn create_fud(name: &str) -> SceneNode {
t!("create_fud({name})");
let mut node = SceneNode::new(name, SceneNodeType::Plugin);
let mut prop = Property::new("ready", PropertyType::Bool, PropertySubType::Null);
prop.set_defaults_bool(vec![false]).unwrap();
node.add_property(prop).unwrap();
node.add_signal(
"file_status_updated",
"File download status updated",
vec![("url", "File URL", CallArgType::Str), ("status", "File status", CallArgType::Str)],
)
.unwrap();
node.add_method("get", vec![("url", "Url", CallArgType::Str)], None).unwrap();
node.add_method("track_file", vec![("url", "Url", CallArgType::Str)], None).unwrap();
node
}
pub fn create_drk(name: &str) -> SceneNode {
t!("create_drk({name})");
let mut node = SceneNode::new(name, SceneNodeType::Plugin);
node.add_signal(
"connect",
"Connections and disconnects",
vec![("connected", "Is darkfid connected", CallArgType::Bool)],
)
.unwrap();
node.add_method(
"get_default_address",
vec![],
Some(vec![("address", "Default address", CallArgType::Str)]),
)
.unwrap();
node.add_method(
"get_balances",
vec![],
Some(vec![("balances", "Token balances", CallArgType::Hash)]),
)
.unwrap();
node.add_method(
"get_tx_status",
vec![("tx_id", "Transaction hash", CallArgType::Str)],
Some(vec![("status_text", "Status text", CallArgType::Str)]),
)
.unwrap();
node.add_method(
"build_tx",
vec![
("amount", "Amount", CallArgType::Str),
("token_id", "Token ID", CallArgType::Hash),
("recipient", "Recipient address", CallArgType::Str),
],
Some(vec![("tx", "Transaction", CallArgType::Hash)]),
)
.unwrap();
node.add_method(
"broadcast_tx",
vec![("tx", "Transaction", CallArgType::Hash)],
Some(vec![("status_text", "Status text", CallArgType::Str)]),
)
.unwrap();
node.add_signal("balances_updated", "Balances changed", vec![]).unwrap();
node.add_signal(
"tx_updated",
"Transaction status updated",
vec![
("tx_id", "Transaction ID", CallArgType::Str),
("status_text", "Transaction status text", CallArgType::Str),
],
)
.unwrap();
node.add_signal(
"tx_built",
"Transaction built - for wallet send flow",
vec![
("amount", "Amount", CallArgType::Str),
("token_symbol", "Token symbol", CallArgType::Str),
("recipient_str", "Recipient address", CallArgType::Str),
],
)
.unwrap();
node.add_signal(
"tx_built_error",
"Transaction build error",
vec![("error_message", "Error message", CallArgType::Str)],
)
.unwrap();
node
}
/// Simple program to greet a person
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// On Linux use the X11 backend
#[arg(long)]
linux_x11_backend: bool,
/// On Linux use the wayland backend
#[arg(long)]
linux_wayland_backend: bool,
}
fn main() {
let args = Args::parse();
GOD.get_or_init(God::new);
// Reuse renderer and event_pub
// No need for setup(), just wait for gfx start then call .start()
// ZMQ, darkirc stay running
let linux_backend = if args.linux_wayland_backend {
if args.linux_x11_backend {
miniquad::conf::LinuxBackend::WaylandWithX11Fallback
} else {
miniquad::conf::LinuxBackend::WaylandOnly
}
} else if args.linux_x11_backend {
miniquad::conf::LinuxBackend::X11Only
} else {
miniquad::conf::LinuxBackend::WaylandWithX11Fallback
};
gfx::run_gui(linux_backend);
debug!(target: "main", "Started GFX backend");
}
/*
use rustpython_vm::{self as pyvm, convert::ToPyObject};
fn main() {
let module = pyvm::Interpreter::without_stdlib(Default::default()).enter(|vm| {
let source = r#"
def foo():
open("hihi", "w")
return 110
#max(1 + lw/3, 4*10) + foo(2, True)
"#;
//let code_obj = vm
// .compile(source, pyvm::compiler::Mode::Exec, "".to_owned())
// .map_err(|err| vm.new_syntax_error(&err, Some(source))).unwrap();
//code_obj
pyvm::import::import_source(vm, "lain", source).unwrap()
});
fn foo(x: u32, y: bool) -> u32 {
if y {
2 * x
} else {
x
}
}
let res = pyvm::Interpreter::without_stdlib(Default::default()).enter(|vm| {
let globals = vm.ctx.new_dict();
globals.set_item("lw", vm.ctx.new_int(110).to_pyobject(vm), vm).unwrap();
globals.set_item("lh", vm.ctx.new_int(4).to_pyobject(vm), vm).unwrap();
globals.set_item("foo", vm.new_function("foo", foo).into(), vm).unwrap();
let scope = pyvm::scope::Scope::new(None, globals);
let foo_fn = module.get_attr("foo", vm).unwrap();
foo_fn.call((), vm).unwrap()
//vm.run_code_obj(code_obj, scope).unwrap()
});
println!("{:?}", res);
}
*/