Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
f8f248d
add call API
r10s Mar 6, 2025
8fc1ff4
do not notify the call with a normal notification
r10s Mar 10, 2025
de37757
do not emit IncomingCall in case the call is stale
r10s Mar 12, 2025
ac9aa46
clearify scope of events
r10s Mar 12, 2025
e601ddd
check for remaining ringing seconds
r10s Mar 12, 2025
d04786f
emit end-call after timeout
r10s Mar 12, 2025
25a3629
factor out mark_call_as_accepted
r10s Mar 12, 2025
63e0ba3
end outgoing calls after one minute
r10s Mar 12, 2025
2b7a18f
test call acceptance
r10s Mar 12, 2025
477121f
make clippy happy
r10s Mar 12, 2025
4c8f65e
update docs
r10s Mar 12, 2025
430d6a6
clarify what should happen on multiple calls
r10s Mar 12, 2025
9b0969a
notify about missed calls
r10s Mar 13, 2025
b927977
update call info message for missed calls
r10s Mar 13, 2025
ffb6e4c
show all call state in the same info-message
r10s Mar 14, 2025
a6251b7
adapt to new info_contact_id api
r10s Apr 8, 2025
16e8640
document msg_id for all call events
r10s Jun 16, 2025
9470566
set WebrtcRoom for calls
r10s Jun 16, 2025
ab5141d
relay place_call_info and accept_call_info
r10s Jun 18, 2025
36553f9
fix ffi
r10s Jun 18, 2025
e7d72ad
make clippy happy
r10s Jul 7, 2025
fde80d1
clarify docs
r10s Aug 21, 2025
d104ee3
Update src/calls.rs
r10s Aug 28, 2025
d3c4eee
clarify docs
r10s Aug 28, 2025
4832fbf
Update deltachat-ffi/deltachat.h
r10s Aug 28, 2025
57e0472
Update deltachat-ffi/deltachat.h
r10s Aug 28, 2025
d5d1cb4
Update deltachat-ffi/deltachat.h
r10s Aug 28, 2025
bffa8da
Update src/calls.rs
r10s Aug 28, 2025
8b828e7
Update deltachat-ffi/deltachat.h
r10s Aug 28, 2025
2acf719
Update deltachat-ffi/deltachat.h
r10s Aug 28, 2025
f20a513
clarify docs
r10s Aug 28, 2025
7e6fa74
clarify docs
r10s Aug 28, 2025
0c9e60e
Update src/calls.rs
r10s Aug 28, 2025
a4fd2a8
log call errors
r10s Aug 28, 2025
336c115
avoid clone()
r10s Aug 28, 2025
8da8498
simplify parameter name
r10s Aug 28, 2025
5e99fcc
simplify event sending
r10s Aug 28, 2025
6c24adc
move call tests to separate file
r10s Aug 28, 2025
0256fe2
do not notify CallAccepted/CallEnded info messages, this is handled b…
r10s Aug 28, 2025
7829cc3
Update src/calls/calls_tests.rs
r10s Aug 29, 2025
0a50811
change load_call_by_root_id() to load_call_by_id()
r10s Aug 29, 2025
f5f5b37
refine docs
r10s Aug 29, 2025
d1c6610
clearer booleans
r10s Aug 29, 2025
3763ce8
improve tests
r10s Aug 29, 2025
9dd4828
improve tests
r10s Aug 29, 2025
535ea7e
improve tests
r10s Aug 29, 2025
902261f
improve tests
r10s Aug 29, 2025
4953345
improve tests
r10s Aug 29, 2025
6677011
calls test: let setup_call() return a struct
r10s Aug 30, 2025
c3979e7
calls test: let accept_call() return a struct
r10s Aug 30, 2025
756cd21
remove broken test creating a deprecated videochat-invitation without…
r10s Aug 30, 2025
4148bf4
remove comment describing only a possible future
r10s Aug 30, 2025
6f456c5
avoid likely flaky test
r10s Aug 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions deltachat-ffi/deltachat.h
Original file line number Diff line number Diff line change
Expand Up @@ -1215,6 +1215,103 @@ void dc_set_webxdc_integration (dc_context_t* context, const char* f
uint32_t dc_init_webxdc_integration (dc_context_t* context, uint32_t chat_id);


/**
* Start an outgoing call.
* This sends a message with all relevant information to the callee,
* who will get informed by an #DC_EVENT_INCOMING_CALL event and rings.
*
* Possible actions during ringing:
*
* - caller cancels the call using dc_end_call():
* callee receives #DC_EVENT_CALL_ENDED
*
* - callee accepts using dc_accept_incoming_call():
* caller receives #DC_EVENT_OUTGOING_CALL_ACCEPTED.
* callee's devices receive #DC_EVENT_INCOMING_CALL_ACCEPTED, call starts
*
* - callee rejects using dc_end_call():
* caller receives #DC_EVENT_CALL_ENDED after 1 minute timeout.
* callee's other devices receive #DC_EVENT_CALL_ENDED
*
* - callee is already in a call:
* in this case, UI may decide to show a notification instead of ringing.
* otherwise, this is same as timeout
*
* - timeout:
* after 1 minute without action,
* caller and callee receive #DC_EVENT_CALL_ENDED
* to prevent endless ringing of callee
* in case caller got offline without being able to send cancellation message
*
* Actions during the call:
*
* - caller ends the call using dc_end_call():
* callee receives #DC_EVENT_CALL_ENDED
*
* - callee ends the call using dc_end_call():
* caller receives #DC_EVENT_CALL_ENDED
*
* Note, that the events are for updating the call screen,
* possible status messages are added and updated as usual, including the known events.
* In the UI, the sorted chatlist is used as an overview about calls as well as messages.
* To place a call with a contact that has no chat yet, use dc_create_chat_by_contact_id() first.
*
* UI will usually allow only one call at the same time,
* this has to be tracked by UI across profile, the core does not track this.
*
* @memberof dc_context_t
* @param context The context object.
* @param chat_id The chat to place a call for.
* This needs to be a one-to-one chat.
* @param place_call_info any data that other devices receive
* in #DC_EVENT_INCOMING_CALL.
* @return ID of the system message announcing the call.
*/
uint32_t dc_place_outgoing_call (dc_context_t* context, uint32_t chat_id, const char* place_call_info);


/**
* Accept incoming call.
*
* This implicitly accepts the contact request, if not yet done.
* All affected devices will receive
* either #DC_EVENT_OUTGOING_CALL_ACCEPTED or #DC_EVENT_INCOMING_CALL_ACCEPTED.
*
* @memberof dc_context_t
* @param context The context object.
* @param msg_id The ID of the call to accept.
* This is the ID reported by #DC_EVENT_INCOMING_CALL
* and equals to the ID of the corresponding info message.
* @param accept_call_info any data that other devices receive
* in #DC_EVENT_OUTGOING_CALL_ACCEPTED or #DC_EVENT_INCOMING_CALL_ACCEPTED.
* @return 1=success, 0=error
*/
int dc_accept_incoming_call (dc_context_t* context, uint32_t msg_id, const char* accept_call_info);


/**
* End incoming or outgoing call.
*
* From the view of the caller, a "cancellation",
* from the view of callee, a "rejection".
* If the call was accepted, this is a "hangup".
*
* For accepted calls,
* all participant devices get informed about the ended call via #DC_EVENT_CALL_ENDED.
* For not accepted calls, only the caller will inform the callee.
*
* If the callee rejects, the caller will get a timeout or give up at some point -
* same as for all other reasons the call cannot be established: Device not in reach, device muted, connectivity etc.
* This is to protect privacy of the callee, avoiding to check if callee is online.
*
* @memberof dc_context_t
* @param context The context object.
* @param msg_id the ID of the call.
* @return 1=success, 0=error
*/
int dc_end_call (dc_context_t* context, uint32_t msg_id);


/**
* Save a draft for a chat in the database.
*
Expand Down Expand Up @@ -4546,6 +4643,8 @@ int dc_msg_is_info (const dc_msg_t* msg);
* and also offer a way to fix the encryption, eg. by a button offering a QR scan
* - DC_INFO_WEBXDC_INFO_MESSAGE (32) - Info-message created by webxdc app sending `update.info`
* - DC_INFO_CHAT_E2EE (50) - Info-message for "Chat is end-to-end-encrypted"
* - DC_INFO_OUTGOING_CALL (60) - Info-message refers to an outgoing call
* - DC_INFO_INCOMING_CALL (65) - Info-message refers to an incoming call
*
* For the messages that refer to a CONTACT,
* dc_msg_get_info_contact_id() returns the contact ID.
Expand Down Expand Up @@ -4602,6 +4701,8 @@ uint32_t dc_msg_get_info_contact_id (const dc_msg_t* msg);
#define DC_INFO_INVALID_UNENCRYPTED_MAIL 13
#define DC_INFO_WEBXDC_INFO_MESSAGE 32
#define DC_INFO_CHAT_E2EE 50
#define DC_INFO_OUTGOING_CALL 60
#define DC_INFO_INCOMING_CALL 65


/**
Expand Down Expand Up @@ -6636,6 +6737,63 @@ void dc_event_unref(dc_event_t* event);
*/
#define DC_EVENT_CHANNEL_OVERFLOW 2400



/**
* Incoming call.
* UI will usually start ringing,
* or show a notification if there is already a call in some profile.
*
* Together with this event,
* an info-message is added to the corresponding chat.
* The info-message, however, is _not_ additionally notified using #DC_EVENT_INCOMING_MSG,
* if needed, this has to be done by the UI explicitly.
*
* If user takes action, dc_accept_incoming_call() or dc_end_call() should be called.
*
* Otherwise, ringing should end on #DC_EVENT_CALL_ENDED
* or #DC_EVENT_INCOMING_CALL_ACCEPTED
*
* @param data1 (int) msg_id ID of the info-message referring to the call.
* @param data2 (char*) place_call_info, text passed to dc_place_outgoing_call()
*/
#define DC_EVENT_INCOMING_CALL 2550

/**
* The callee accepted an incoming call on another device using dc_accept_incoming_call().
* The caller gets the event #DC_EVENT_OUTGOING_CALL_ACCEPTED at the same time.
*
* The event is sent unconditionally when the corresponding message is received.
* UI should only take action in case call UI was opened before, otherwise the event should be ignored.
*
* @param data1 (int) msg_id ID of the info-message referring to the call
* @param data2 (char*) accept_call_info, text passed to dc_place_outgoing_call()
*/
#define DC_EVENT_INCOMING_CALL_ACCEPTED 2560

/**
* A call placed using dc_place_outgoing_call() was accepted by the callee using dc_accept_incoming_call().
*
* The event is sent unconditionally when the corresponding message is received.
* UI should only take action in case call UI was opened before, otherwise the event should be ignored.
*
* @param data1 (int) msg_id ID of the info-message referring to the call
* @param data2 (char*) accept_call_info, text passed to dc_accept_incoming_call()
*/
#define DC_EVENT_OUTGOING_CALL_ACCEPTED 2570

/**
* An incoming or outgoing call was ended using dc_end_call().
* Moreover, the event is sent when the call was not accepted within 1 minute timeout.
*
* The event is sent unconditionally when the corresponding message is received.
* UI should only take action in case call UI was opened before, otherwise the event should be ignored.
*
* @param data1 (int) msg_id ID of the info-message referring to the call
*/
#define DC_EVENT_CALL_ENDED 2580


/**
* @}
*/
Expand Down
88 changes: 85 additions & 3 deletions deltachat-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,10 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
EventType::AccountsChanged => 2302,
EventType::AccountsItemChanged => 2303,
EventType::EventChannelOverflow { .. } => 2400,
EventType::IncomingCall { .. } => 2550,
EventType::IncomingCallAccepted { .. } => 2560,
EventType::OutgoingCallAccepted { .. } => 2570,
EventType::CallEnded { .. } => 2580,
#[allow(unreachable_patterns)]
#[cfg(test)]
_ => unreachable!("This is just to silence a rust_analyzer false-positive"),
Expand Down Expand Up @@ -619,7 +623,11 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
EventType::WebxdcRealtimeData { msg_id, .. }
| EventType::WebxdcStatusUpdate { msg_id, .. }
| EventType::WebxdcRealtimeAdvertisementReceived { msg_id }
| EventType::WebxdcInstanceDeleted { msg_id, .. } => msg_id.to_u32() as libc::c_int,
| EventType::WebxdcInstanceDeleted { msg_id, .. }
| EventType::IncomingCall { msg_id, .. }
| EventType::IncomingCallAccepted { msg_id, .. }
| EventType::OutgoingCallAccepted { msg_id, .. }
| EventType::CallEnded { msg_id, .. } => msg_id.to_u32() as libc::c_int,
EventType::ChatlistItemChanged { chat_id } => {
chat_id.unwrap_or_default().to_u32() as libc::c_int
}
Expand Down Expand Up @@ -671,6 +679,10 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
| EventType::ChatModified(_)
| EventType::ChatDeleted { .. }
| EventType::WebxdcRealtimeAdvertisementReceived { .. }
| EventType::IncomingCall { .. }
| EventType::IncomingCallAccepted { .. }
| EventType::OutgoingCallAccepted { .. }
| EventType::CallEnded { .. }
| EventType::EventChannelOverflow { .. } => 0,
EventType::MsgsChanged { msg_id, .. }
| EventType::ReactionsChanged { msg_id, .. }
Expand Down Expand Up @@ -767,8 +779,23 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
| EventType::ChatlistChanged
| EventType::AccountsChanged
| EventType::AccountsItemChanged
| EventType::WebxdcRealtimeAdvertisementReceived { .. }
| EventType::EventChannelOverflow { .. } => ptr::null_mut(),
| EventType::WebxdcRealtimeAdvertisementReceived { .. } => ptr::null_mut(),
EventType::IncomingCall {
place_call_info, ..
} => {
let data2 = place_call_info.to_c_string().unwrap_or_default();
data2.into_raw()
}
EventType::IncomingCallAccepted {
accept_call_info, ..
}
| EventType::OutgoingCallAccepted {
accept_call_info, ..
} => {
let data2 = accept_call_info.to_c_string().unwrap_or_default();
data2.into_raw()
}
EventType::CallEnded { .. } | EventType::EventChannelOverflow { .. } => ptr::null_mut(),
EventType::ConfigureProgress { comment, .. } => {
if let Some(comment) = comment {
comment.to_c_string().unwrap_or_default().into_raw()
Expand Down Expand Up @@ -1167,6 +1194,61 @@ pub unsafe extern "C" fn dc_init_webxdc_integration(
.unwrap_or(0)
}

#[no_mangle]
pub unsafe extern "C" fn dc_place_outgoing_call(
context: *mut dc_context_t,
chat_id: u32,
place_call_info: *const libc::c_char,
) -> u32 {
if context.is_null() || chat_id == 0 {
eprintln!("ignoring careless call to dc_place_outgoing_call()");
return 0;
}
let ctx = &*context;
let chat_id = ChatId::new(chat_id);
let place_call_info = to_string_lossy(place_call_info);

block_on(ctx.place_outgoing_call(chat_id, place_call_info))
.context("Failed to place call")
.log_err(ctx)
.map(|msg_id| msg_id.to_u32())
.unwrap_or_log_default(ctx, "Failed to place call")
}

#[no_mangle]
pub unsafe extern "C" fn dc_accept_incoming_call(
context: *mut dc_context_t,
msg_id: u32,
accept_call_info: *const libc::c_char,
) -> libc::c_int {
if context.is_null() || msg_id == 0 {
eprintln!("ignoring careless call to dc_accept_incoming_call()");
return 0;
}
let ctx = &*context;
let msg_id = MsgId::new(msg_id);
let accept_call_info = to_string_lossy(accept_call_info);

block_on(ctx.accept_incoming_call(msg_id, accept_call_info))
.context("Failed to accept call")
.is_ok() as libc::c_int
}

#[no_mangle]
pub unsafe extern "C" fn dc_end_call(context: *mut dc_context_t, msg_id: u32) -> libc::c_int {
if context.is_null() || msg_id == 0 {
eprintln!("ignoring careless call to dc_end_call()");
return 0;
}
let ctx = &*context;
let msg_id = MsgId::new(msg_id);

block_on(ctx.end_call(msg_id))
.context("Failed to end call")
.log_err(ctx)
.is_ok() as libc::c_int
}

#[no_mangle]
pub unsafe extern "C" fn dc_set_draft(
context: *mut dc_context_t,
Expand Down
55 changes: 55 additions & 0 deletions deltachat-jsonrpc/src/api/types/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,37 @@ pub enum EventType {
/// Number of events skipped.
n: u64,
},

/// Incoming call.
IncomingCall {
/// ID of the info message referring to the call.
msg_id: u32,
/// User-defined info as passed to place_outgoing_call()
place_call_info: String,
},

/// Incoming call accepted.
/// This is esp. interesting to stop ringing on other devices.
IncomingCallAccepted {
/// ID of the info message referring to the call.
msg_id: u32,
/// User-defined info passed to dc_accept_incoming_call()
accept_call_info: String,
},

/// Outgoing call accepted.
OutgoingCallAccepted {
/// ID of the info message referring to the call.
msg_id: u32,
/// User-defined info passed to dc_accept_incoming_call(
accept_call_info: String,
},

/// Call ended.
CallEnded {
/// ID of the info message referring to the call.
msg_id: u32,
},
}

impl From<CoreEventType> for EventType {
Expand Down Expand Up @@ -566,6 +597,30 @@ impl From<CoreEventType> for EventType {
CoreEventType::EventChannelOverflow { n } => EventChannelOverflow { n },
CoreEventType::AccountsChanged => AccountsChanged,
CoreEventType::AccountsItemChanged => AccountsItemChanged,
CoreEventType::IncomingCall {
msg_id,
place_call_info,
} => IncomingCall {
msg_id: msg_id.to_u32(),
place_call_info,
},
CoreEventType::IncomingCallAccepted {
msg_id,
accept_call_info,
} => IncomingCallAccepted {
msg_id: msg_id.to_u32(),
accept_call_info,
},
CoreEventType::OutgoingCallAccepted {
msg_id,
accept_call_info,
} => OutgoingCallAccepted {
msg_id: msg_id.to_u32(),
accept_call_info,
},
CoreEventType::CallEnded { msg_id } => CallEnded {
msg_id: msg_id.to_u32(),
},
#[allow(unreachable_patterns)]
#[cfg(test)]
_ => unreachable!("This is just to silence a rust_analyzer false-positive"),
Expand Down
Loading
Loading