Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions deltachat-ffi/deltachat.h
Original file line number Diff line number Diff line change
Expand Up @@ -6661,6 +6661,7 @@ void dc_event_unref(dc_event_t* event);
* UI usually only takes action in case call UI was opened before, otherwise the event should be ignored.
*
* @param data1 (int) msg_id ID of the message referring to the call
* @param data2 (int) chat_id ID of the chat which the message belongs to
* @param data2 (char*) accept_call_info, text passed to dc_accept_incoming_call()
*/
#define DC_EVENT_OUTGOING_CALL_ACCEPTED 2570
Expand All @@ -6672,9 +6673,23 @@ void dc_event_unref(dc_event_t* event);
* UI usually only takes action in case call UI was opened before, otherwise the event should be ignored.
*
* @param data1 (int) msg_id ID of the message referring to the call
* @param data2 (int) chat_id ID of the chat which the message belongs to
*/
#define DC_EVENT_CALL_ENDED 2580

/**
* An incoming call was missed. Only emitted if the caller is allowed to call us. This happens when:
* - A call timed out (not accepted by us on time).
* - A call was canceled by the caller.
* - A stale call message was received, i.e. it is older than the timeout.
*
* This should trigger a UI notification.
*
* @param data1 (int) msg_id ID of the message referring to the call
* @param data2 (int) chat_id ID of the chat which the message belongs to
*/
#define DC_EVENT_CALL_MISSED 2590

/**
* Transport relay added/deleted or default has changed.
* UI should update the list.
Expand Down
11 changes: 8 additions & 3 deletions deltachat-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
EventType::IncomingCallAccepted { .. } => 2560,
EventType::OutgoingCallAccepted { .. } => 2570,
EventType::CallEnded { .. } => 2580,
EventType::CallMissed { .. } => 2590,
EventType::TransportsModified => 2600,
#[allow(unreachable_patterns)]
#[cfg(test)]
Expand Down Expand Up @@ -625,6 +626,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
| EventType::IncomingCall { msg_id, .. }
| EventType::IncomingCallAccepted { msg_id, .. }
| EventType::OutgoingCallAccepted { msg_id, .. }
| EventType::CallMissed { 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 @@ -677,10 +679,11 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
| EventType::ChatModified(_)
| EventType::ChatDeleted { .. }
| EventType::WebxdcRealtimeAdvertisementReceived { .. }
| EventType::OutgoingCallAccepted { .. }
| EventType::CallEnded { .. }
| EventType::EventChannelOverflow { .. }
| EventType::TransportsModified => 0,
EventType::OutgoingCallAccepted { chat_id, .. }
| EventType::CallEnded { chat_id, .. }
| EventType::CallMissed { chat_id, .. } => chat_id.to_u32() as libc::c_int,
EventType::MsgsChanged { msg_id, .. }
| EventType::ReactionsChanged { msg_id, .. }
| EventType::IncomingReaction { msg_id, .. }
Expand Down Expand Up @@ -796,7 +799,9 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
let data2 = accept_call_info.to_c_string().unwrap_or_default();
data2.into_raw()
}
EventType::CallEnded { .. } | EventType::EventChannelOverflow { .. } => ptr::null_mut(),
EventType::CallEnded { .. }
| EventType::CallMissed { .. }
| EventType::EventChannelOverflow { .. } => ptr::null_mut(),
EventType::ConfigureProgress { comment, .. } => {
if let Some(comment) = comment {
comment.to_c_string().unwrap_or_default().into_raw()
Expand Down
12 changes: 12 additions & 0 deletions deltachat-jsonrpc/src/api/types/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,14 @@ pub enum EventType {
chat_id: u32,
},

/// Call missed.
CallMissed {
/// ID of the info message referring to the call.
msg_id: u32,
/// ID of the chat which the message belongs to.
chat_id: u32,
},

/// One or more transports has changed.
///
/// UI should update the list.
Expand Down Expand Up @@ -658,6 +666,10 @@ impl From<CoreEventType> for EventType {
msg_id: msg_id.to_u32(),
chat_id: chat_id.to_u32(),
},
CoreEventType::CallMissed { msg_id, chat_id } => CallMissed {
msg_id: msg_id.to_u32(),
chat_id: chat_id.to_u32(),
},
CoreEventType::TransportsModified => TransportsModified,

#[allow(unreachable_patterns)]
Expand Down
112 changes: 62 additions & 50 deletions src/calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,11 @@ impl Context {

let wait = RINGING_SECONDS;
let context = self.get_weak_context();
task::spawn(Context::emit_end_call_if_unaccepted(
task::spawn(Context::finalize_call_if_unaccepted(
context,
wait.try_into()?,
call.id,
true, // Doesn't matter for outgoing calls
));

Ok(call.id)
Expand Down Expand Up @@ -314,39 +315,67 @@ impl Context {
Ok(())
}

async fn emit_end_call_if_unaccepted(
async fn finalize_call_if_unaccepted(
context: WeakContext,
wait: u64,
call_id: MsgId,
can_call_me: bool,
) -> Result<()> {
sleep(Duration::from_secs(wait)).await;
let context = context.upgrade()?;
let Some(mut call) = context.load_call_by_id(call_id).await? else {
warn!(
context,
"emit_end_call_if_unaccepted is called with {call_id} which does not refer to a call."
"finalize_call_if_unaccepted is called with {call_id} which does not refer to a call."
);
return Ok(());
};
if !call.is_accepted() && !call.is_ended() {
let (msg_id, chat_id) = (call_id, call.msg.chat_id);
if call.is_incoming() {
call.mark_as_canceled(&context).await?;
let missed_call_str = stock_str::missed_call(&context);
call.update_text(&context, &missed_call_str).await?;
if can_call_me {
context.emit_event(EventType::CallMissed { msg_id, chat_id });
}
} else {
call.mark_as_ended(&context).await?;
let canceled_call_str = stock_str::canceled_call(&context);
call.update_text(&context, &canceled_call_str).await?;
}
if can_call_me {
context.emit_event(EventType::CallEnded { msg_id, chat_id });
}
context.emit_msgs_changed(call.msg.chat_id, call_id);
context.emit_event(EventType::CallEnded {
msg_id: call.msg.id,
chat_id: call.msg.chat_id,
});
}
Ok(())
}

async fn can_call_me(&self, from_id: ContactId) -> Result<bool> {
Ok(match who_can_call_me(self).await? {
WhoCanCallMe::Contacts => ChatIdBlocked::lookup_by_contact(self, from_id)
.await?
.is_some_and(|chat_id_blocked| {
match chat_id_blocked.blocked {
Blocked::Not => true,
Blocked::Yes | Blocked::Request => {
// Do not notify about incoming calls
// from contact requests and blocked contacts.
//
// User can still access the call and accept it
// via the chat in case of contact requests.
false
}
}
}),
WhoCanCallMe::Everybody => ChatIdBlocked::lookup_by_contact(self, from_id)
.await?
.is_none_or(|chat_id_blocked| chat_id_blocked.blocked != Blocked::Yes),
WhoCanCallMe::Nobody => false,
})
}

pub(crate) async fn handle_call_msg(
&self,
call_id: MsgId,
Expand All @@ -360,50 +389,33 @@ impl Context {
};

if call.is_incoming() {
if call.is_stale() {
let missed_call_str = stock_str::missed_call(self);
call.update_text(self, &missed_call_str).await?;
self.emit_incoming_msg(call.msg.chat_id, call_id); // notify missed call
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it on purpose that no IncomingMsg event is emitted anymore when missing a call?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is in the commit message:

  • IncomingMsg is replaced with CallMissed for stale calls.

If this creates some incompatibility, i'll revert this. IncomingMsg was emitted before because there was no other suitable event.

Copy link
Copy Markdown
Collaborator

@Hocuri Hocuri Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know whether it creates any incompatibility, because I was not involved in calls development so far. But if we remove an event, then we need to check whether UIs were relying on this event, or ask the UI developers.

@r10s said

this PR should only fire CallMissed additionally, and not remove any other event.

so, @r10s - what do you think, is it fine to remove the IncomingMsg event? @Amzd just said that he thinks it's fine to remove it, but @r10s might have the best overview over this

let call_str = match call.is_stale() {
true => stock_str::missed_call(self),
false => stock_str::incoming_call(self, call.has_video_initially()),
};
call.update_text(self, &call_str).await?;
let (msg_id, chat_id) = (call_id, call.msg.chat_id);
let can_call_me = self.can_call_me(from_id).await?;
if !can_call_me {
} else if call.is_stale() {
self.emit_event(EventType::CallMissed { msg_id, chat_id });
Comment on lines +400 to +401
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, we notified about missed call even if the contact was not allowed to call. Now, we don't. Is this on purpose?

Copy link
Copy Markdown
Collaborator Author

@iequidoo iequidoo Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is also in the commit message:

This doesn't emit CallMissed for those who aren't allowed to call us. Also, don't emit CallEnded
if the caller isn't allowed to call us and the call wasn't accepted, as there's no previous
IncomingCall event in this case.

I think this should be fine, e.g. we don't show notifications for messages from blocked contacts, so why show missed call notifications. Again, if it was on purpose, i'll revert this, but this looked to me like a bug.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idk if it was on purpose - @r10s?

} else {
let incoming_call_str =
stock_str::incoming_call(self, call.has_video_initially());
call.update_text(self, &incoming_call_str).await?;
self.emit_msgs_changed(call.msg.chat_id, call_id); // ringing calls are not additionally notified
let can_call_me = match who_can_call_me(self).await? {
WhoCanCallMe::Contacts => ChatIdBlocked::lookup_by_contact(self, from_id)
.await?
.is_some_and(|chat_id_blocked| {
match chat_id_blocked.blocked {
Blocked::Not => true,
Blocked::Yes | Blocked::Request => {
// Do not notify about incoming calls
// from contact requests and blocked contacts.
//
// User can still access the call and accept it
// via the chat in case of contact requests.
false
}
}
}),
WhoCanCallMe::Everybody => ChatIdBlocked::lookup_by_contact(self, from_id)
.await?
.is_none_or(|chat_id_blocked| chat_id_blocked.blocked != Blocked::Yes),
WhoCanCallMe::Nobody => false,
};
if can_call_me {
self.emit_event(EventType::IncomingCall {
msg_id: call.msg.id,
chat_id: call.msg.chat_id,
place_call_info: call.place_call_info.to_string(),
has_video: call.has_video_initially(),
});
}
self.emit_event(EventType::IncomingCall {
msg_id,
chat_id,
place_call_info: call.place_call_info.to_string(),
has_video: call.has_video_initially(),
});
}
self.emit_msgs_changed(chat_id, msg_id);
if !call.is_stale() {
let wait = call.remaining_ring_seconds();
let context = self.get_weak_context();
task::spawn(Context::emit_end_call_if_unaccepted(
task::spawn(Context::finalize_call_if_unaccepted(
context,
wait.try_into()?,
call.msg.id,
can_call_me,
));
}
} else {
Expand Down Expand Up @@ -455,6 +467,7 @@ impl Context {
return Ok(());
}

let (msg_id, chat_id) = (call_id, call.msg.chat_id);
if !call.is_accepted() {
if call.is_incoming() {
if from_id == ContactId::SELF {
Expand All @@ -465,6 +478,9 @@ impl Context {
call.mark_as_canceled(self).await?;
let missed_call_str = stock_str::missed_call(self);
call.update_text(self, &missed_call_str).await?;
if self.can_call_me(from_id).await? {
self.emit_event(EventType::CallMissed { msg_id, chat_id });
}
}
} else {
// outgoing
Expand All @@ -482,12 +498,8 @@ impl Context {
call.mark_as_ended(self).await?;
call.update_text_duration(self).await?;
}

self.emit_event(EventType::CallEnded { msg_id, chat_id });
self.emit_msgs_changed(call.msg.chat_id, call_id);
self.emit_event(EventType::CallEnded {
msg_id: call.msg.id,
chat_id: call.msg.chat_id,
});
}
_ => {}
}
Expand Down
Loading
Loading