secure chat addon

This commit is contained in:
ariska committed 2026-07-07 16:47:11 +07:00
1 parent c156efc882
commit f7961b8e70
1 file changed
+245 -76
+245 -76
View File
@@ -6,7 +6,7 @@ mod webhub;
use std::ffi::{c_void, CString};
use std::mem::zeroed;
use std::os::raw::c_int;
use std::os::raw::{c_char, c_int, c_uint, c_ulong};
use std::ptr::null_mut;
use std::sync::mpsc::{Receiver, Sender};
@@ -49,6 +49,11 @@ struct SecureChatCtx {
const CONTACT_REFRESH_TICKS: u32 = 20;
const CHAT_POLL_TICKS: u32 = 4;
const UI_TIMER_MS: c_int = 500;
const GDK_KEY_RETURN: c_uint = 0xff0d;
const GDK_KEY_KP_ENTER: c_uint = 0xff8d;
const GDK_KEY_TAB: c_uint = 0xff09;
const GDK_KEY_ISO_LEFT_TAB: c_uint = 0xfe20;
const GDK_SHIFT_MASK: c_uint = 1 << 0;
#[allow(non_upper_case_globals)]
static mut CTX: *mut SecureChatCtx = null_mut();
@@ -84,7 +89,7 @@ static mut thread_header_label: *mut libui::uiLabel = null_mut();
static mut thread_view: *mut libui::uiMultilineEntry = null_mut();
#[allow(non_upper_case_globals)]
static mut compose_entry: *mut libui::uiEntry = null_mut();
static mut compose_entry: *mut libui::uiMultilineEntry = null_mut();
#[allow(non_upper_case_globals)]
static mut send_button: *mut libui::uiButton = null_mut();
@@ -134,6 +139,38 @@ static mut my_qr_name_entry: *mut libui::uiEntry = null_mut();
#[allow(non_upper_case_globals)]
static mut my_qr_path_label: *mut libui::uiLabel = null_mut();
#[cfg(target_os = "linux")]
#[repr(C)]
struct GdkEventKey {
type_: c_int,
window: *mut c_void,
send_event: i8,
time: u32,
state: c_uint,
keyval: c_uint,
length: c_int,
string: *mut c_char,
hardware_keycode: u16,
group: u8,
is_modifier: u32,
}
#[cfg(target_os = "linux")]
type GCallback = Option<unsafe extern "C" fn()>;
#[cfg(target_os = "linux")]
extern "C" {
fn gtk_widget_grab_focus(widget: *mut c_void);
fn g_signal_connect_data(
instance: *mut c_void,
detailed_signal: *const c_char,
c_handler: GCallback,
data: *mut c_void,
destroy_data: Option<unsafe extern "C" fn(data: *mut c_void, closure: *mut c_void)>,
connect_flags: c_uint,
) -> c_ulong;
}
unsafe fn ctx() -> &'static mut SecureChatCtx {
&mut *CTX
}
@@ -164,6 +201,207 @@ unsafe fn multiline_text(entry: *mut libui::uiMultilineEntry) -> String {
.into_owned()
}
unsafe fn selected_contact_is_online() -> bool {
selected_contact().map(|cv| cv.online).unwrap_or(false)
}
unsafe fn send_current_message() -> bool {
if compose_entry.is_null() || CTX.is_null() {
return false;
}
let body = multiline_text(compose_entry).trim().to_string();
if body.is_empty() {
return false;
}
let network_id = match selected_network_id() {
Some(id) => id,
None => return false,
};
let cv = match selected_contact() {
Some(c) => c,
None => return false,
};
let c = ctx();
let sender_name = self_display_name(&c.client);
let sender_id = c.client.self_address();
let target_host = preferred_chat_host(&c.client, &cv.contact.peer_id, &cv.contact.ip);
let now = crate::serviceclient::ms_since_epoch();
let mut ports = vec![cv.contact.port];
if !ports.contains(&DEFAULT_WEBHUB_PORT) {
ports.push(DEFAULT_WEBHUB_PORT);
}
if !ports.contains(&DEFAULT_BACKONE_PORT) {
ports.push(DEFAULT_BACKONE_PORT);
}
if c.realtime_chat_available {
let _ = c.hub_cmd.send(HubCommand::SendChatRemote {
host: target_host.clone(),
ports,
network_id: network_id.to_lowercase(),
sender_id: sender_id.to_lowercase(),
target_id: cv.contact.peer_id.to_lowercase(),
sender_name: sender_name.clone(),
body: body.clone(),
});
}
c.session.thread_for(&cv.contact.peer_id).push(ChatLine {
sender: String::from("me"),
sender_name: sender_name.clone(),
body: body.clone(),
at: now,
});
let empty = CString::new("").unwrap();
libui::uiMultilineEntrySetText(compose_entry, empty.as_ptr());
if !c.realtime_chat_available {
let relayed = relay::relay_chat_to_peer_paths(
&c.client,
&network_id,
&sender_id,
&sender_name,
&cv.contact.peer_id,
&target_host,
&body,
);
if !relayed {
c.session
.thread_for(&cv.contact.peer_id)
.push(ChatLine {
sender: String::from("system"),
sender_name: String::from("system"),
body: String::from(
"Message not delivered — check contact IP:port, webhub bind, and install-secure-chat-deps.sh on both machines",
),
at: crate::serviceclient::ms_since_epoch(),
});
}
}
render_thread();
true
}
#[cfg(target_os = "linux")]
unsafe fn control_widget(control: *mut libui::uiControl) -> *mut c_void {
if control.is_null() {
return null_mut();
}
libui::uiControlHandle(control) as *mut c_void
}
#[cfg(not(target_os = "linux"))]
unsafe fn control_widget(_: *mut libui::uiControl) -> *mut c_void {
null_mut()
}
unsafe fn focus_compose_entry() {
let widget = control_widget(compose_entry.cast());
if !widget.is_null() {
#[cfg(target_os = "linux")]
gtk_widget_grab_focus(widget);
}
}
unsafe fn focus_contact_combo() {
let widget = control_widget(contact_combo.cast());
if !widget.is_null() {
#[cfg(target_os = "linux")]
gtk_widget_grab_focus(widget);
}
}
unsafe fn focus_message_input_if_ready() {
if compose_entry.is_null()
|| libui::uiControlEnabled(compose_entry.cast()) == 0
|| !selected_contact_is_online()
{
return;
}
focus_compose_entry();
}
#[cfg(target_os = "linux")]
unsafe extern "C" fn on_compose_key_press(
_: *mut c_void,
event: *mut GdkEventKey,
_: *mut c_void,
) -> c_int {
if event.is_null() {
return 0;
}
let key = (*event).keyval;
let state = (*event).state;
if key == GDK_KEY_TAB && state & GDK_SHIFT_MASK == 0 {
focus_contact_combo();
return 1;
}
if key == GDK_KEY_ISO_LEFT_TAB || (key == GDK_KEY_TAB && state & GDK_SHIFT_MASK != 0) {
focus_contact_combo();
return 1;
}
if (key == GDK_KEY_RETURN || key == GDK_KEY_KP_ENTER) && state & GDK_SHIFT_MASK == 0 {
return if send_current_message() { 1 } else { 0 };
}
0
}
#[cfg(target_os = "linux")]
unsafe extern "C" fn on_contact_combo_key_press(
_: *mut c_void,
event: *mut GdkEventKey,
_: *mut c_void,
) -> c_int {
if event.is_null() {
return 0;
}
let key = (*event).keyval;
let state = (*event).state;
if key == GDK_KEY_TAB && state & GDK_SHIFT_MASK == 0 {
focus_compose_entry();
return 1;
}
if key == GDK_KEY_ISO_LEFT_TAB || (key == GDK_KEY_TAB && state & GDK_SHIFT_MASK != 0) {
focus_compose_entry();
return 1;
}
0
}
unsafe fn install_keyboard_shortcuts() {
#[cfg(target_os = "linux")]
{
let signal = CString::new("key-press-event").unwrap();
let compose_widget = control_widget(compose_entry.cast());
if !compose_widget.is_null() {
g_signal_connect_data(
compose_widget,
signal.as_ptr(),
Some(std::mem::transmute::<
unsafe extern "C" fn(*mut c_void, *mut GdkEventKey, *mut c_void) -> c_int,
unsafe extern "C" fn(),
>(on_compose_key_press)),
null_mut(),
None,
0,
);
}
let contact_widget = control_widget(contact_combo.cast());
if !contact_widget.is_null() {
g_signal_connect_data(
contact_widget,
signal.as_ptr(),
Some(std::mem::transmute::<
unsafe extern "C" fn(*mut c_void, *mut GdkEventKey, *mut c_void) -> c_int,
unsafe extern "C" fn(),
>(on_contact_combo_key_press)),
null_mut(),
None,
0,
);
}
}
}
unsafe fn set_entry_text(entry: *mut libui::uiEntry, text: &str) {
if entry.is_null() {
return;
@@ -425,6 +663,7 @@ unsafe fn refill_contact_combo_filtered() {
render_contact_list();
render_thread();
update_send_enabled();
focus_message_input_if_ready();
}
unsafe fn refresh_contact_status_only() {
@@ -753,6 +992,7 @@ unsafe extern "C" fn on_contact_changed(_: *mut libui::uiCombobox, _: *mut c_voi
update_thread_header();
render_thread();
update_send_enabled();
focus_message_input_if_ready();
}
unsafe extern "C" fn on_search_changed(_: *mut libui::uiEntry, _: *mut c_void) {
@@ -764,79 +1004,7 @@ unsafe extern "C" fn on_search_changed(_: *mut libui::uiEntry, _: *mut c_void) {
}
unsafe extern "C" fn on_send_clicked(_: *mut libui::uiButton, _: *mut c_void) {
if compose_entry.is_null() || CTX.is_null() {
return;
}
let body = entry_text(compose_entry).trim().to_string();
if body.is_empty() {
return;
}
let network_id = match selected_network_id() {
Some(id) => id,
None => return,
};
let cv = match selected_contact() {
Some(c) => c,
None => return,
};
let c = ctx();
let name = self_display_name(&c.client);
let sender_id = c.client.self_address();
let target_host = preferred_chat_host(&c.client, &cv.contact.peer_id, &cv.contact.ip);
let now = crate::serviceclient::ms_since_epoch();
let mut ports = vec![cv.contact.port];
if !ports.contains(&DEFAULT_WEBHUB_PORT) {
ports.push(DEFAULT_WEBHUB_PORT);
}
if !ports.contains(&DEFAULT_BACKONE_PORT) {
ports.push(DEFAULT_BACKONE_PORT);
}
if c.realtime_chat_available {
let _ = c.hub_cmd.send(HubCommand::SendChatRemote {
host: target_host.clone(),
ports,
network_id: network_id.to_lowercase(),
sender_id: sender_id.to_lowercase(),
target_id: cv.contact.peer_id.to_lowercase(),
sender_name: name.clone(),
body: body.clone(),
});
}
c.session
.thread_for(&cv.contact.peer_id)
.push(ChatLine {
sender: String::from("me"),
sender_name: name,
body: body.clone(),
at: now,
});
set_entry_text(compose_entry, "");
if !c.realtime_chat_available {
let relayed = relay::relay_chat_to_peer_paths(
&c.client,
&network_id,
&sender_id,
&self_display_name(&c.client),
&cv.contact.peer_id,
&target_host,
&body,
);
if !relayed {
c.session
.thread_for(&cv.contact.peer_id)
.push(ChatLine {
sender: String::from("system"),
sender_name: String::from("system"),
body: String::from(
"Message not delivered — check contact IP:port, webhub bind, and install-secure-chat-deps.sh on both machines",
),
at: crate::serviceclient::ms_since_epoch(),
});
}
}
render_thread();
let _ = send_current_message();
}
unsafe fn ingest_chat_message(msg: webhub::ChatIncoming) {
@@ -1255,7 +1423,7 @@ pub fn secure_chat_main() {
let compose_row = libui::uiNewHorizontalBox();
libui::uiBoxSetPadded(compose_row, 1);
compose_entry = libui::uiNewEntry();
compose_entry = libui::uiNewMultilineEntry();
libui::uiBoxAppend(compose_row, compose_entry.cast(), 1);
let send_lbl = CString::new("Send").unwrap();
@@ -1268,6 +1436,7 @@ pub fn secure_chat_main() {
libui::uiBoxAppend(body, right.cast(), 1);
libui::uiBoxAppend(root, body.cast(), 1);
install_keyboard_shortcuts();
libui::uiWindowSetChild(main_window, root.cast());