secure chat addon
This commit is contained in:
1 parent
c156efc882
commit
f7961b8e70
1 file changed
+245
-76
+245
-76
@@ -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());
|
||||
|
||||
|
||||
Reference in new issue
Block a user