Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
10
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Open sidebar
savoirfairelinux
jami-daemon
Commits
e85f8af2
Commit
e85f8af2
authored
Aug 26, 2011
by
Rafaël Carré
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
* #6740 : remove time updating thread from calls
parent
02e208b7
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
73 additions
and
231 deletions
+73
-231
gnome/src/actions.c
gnome/src/actions.c
+10
-61
gnome/src/actions.h
gnome/src/actions.h
+1
-20
gnome/src/callable_obj.c
gnome/src/callable_obj.c
+0
-63
gnome/src/callable_obj.h
gnome/src/callable_obj.h
+0
-9
gnome/src/contacts/calltree.c
gnome/src/contacts/calltree.c
+52
-57
gnome/src/contacts/calltree.h
gnome/src/contacts/calltree.h
+3
-3
gnome/src/dbus/dbus.c
gnome/src/dbus/dbus.c
+3
-5
gnome/src/mainwindow.c
gnome/src/mainwindow.c
+4
-13
No files found.
gnome/src/actions.c
View file @
e85f8af2
...
...
@@ -155,45 +155,30 @@ void
status_bar_display_account
()
{
gchar
*
msg
;
account_t
*
acc
;
statusbar_pop_message
(
__MSG_ACCOUNT_DEFAULT
);
DEBUG
(
"status_bar_display_account begin"
);
acc
=
account_list_get_current
();
account_t
*
acc
=
account_list_get_current
();
status_tray_icon_online
(
acc
!=
NULL
);
if
(
acc
)
{
status_tray_icon_online
(
TRUE
);
msg
=
g_markup_printf_escaped
(
"%s %s (%s)"
,
_
(
"Using account"
),
(
gchar
*
)
g_hash_table_lookup
(
acc
->
properties
,
ACCOUNT_ALIAS
),
(
gchar
*
)
g_hash_table_lookup
(
acc
->
properties
,
ACCOUNT_TYPE
));
}
else
{
status_tray_icon_online
(
FALSE
);
msg
=
g_markup_printf_escaped
(
_
(
"No registered accounts"
));
}
statusbar_push_message
(
msg
,
NULL
,
__MSG_ACCOUNT_DEFAULT
);
g_free
(
msg
);
DEBUG
(
"status_bar_display_account_end"
);
}
gboolean
void
sflphone_quit
()
{
gboolean
quit
=
FALSE
;
guint
count
=
calllist_get_size
(
current_calls
);
if
(
count
>
0
)
{
quit
=
main_window_ask_quit
();
}
else
{
quit
=
TRUE
;
}
if
(
quit
)
{
if
(
calllist_get_size
(
current_calls
)
==
0
||
main_window_ask_quit
())
{
// Save the history
sflphone_save_history
();
...
...
@@ -205,8 +190,6 @@ sflphone_quit ()
//account_list_clean()
gtk_main_quit
();
}
return
quit
;
}
void
...
...
@@ -251,8 +234,7 @@ sflphone_hung_up (callable_obj_t * c)
status_tray_icon_blink
(
FALSE
);
#endif
stop_call_clock
(
c
);
calltree_update_clock
();
statusbar_update_clock
(
""
);
}
/** Internal to actions: Fill account list */
...
...
@@ -454,7 +436,7 @@ sflphone_hang_up()
calltree_update_call
(
history
,
selectedCall
,
NULL
);
calltree
_update_clock
();
statusbar
_update_clock
(
""
);
}
void
...
...
@@ -1353,15 +1335,13 @@ static void hist_free_elt(gpointer list)
void
sflphone_save_history
(
void
)
{
gint
size
;
gint
i
;
QueueElement
*
current
;
conference_obj_t
*
conf
;
GHashTable
*
result
=
g_hash_table_new_full
(
NULL
,
g_str_equal
,
g_free
,
hist_free_elt
);
size
=
calllist_get_size
(
history
);
for
(
i
=
0
;
i
<
size
;
i
++
)
{
gint
size
=
calllist_get_size
(
history
);
for
(
gint
i
=
0
;
i
<
size
;
i
++
)
{
current
=
calllist_get_nth
(
history
,
i
);
if
(
!
current
)
{
WARN
(
"SFLphone: Warning: %dth element is null"
,
i
);
...
...
@@ -1384,7 +1364,7 @@ void sflphone_save_history (void)
}
size
=
conferencelist_get_size
(
history
);
for
(
i
=
0
;
i
<
size
;
i
++
)
{
for
(
gint
i
=
0
;
i
<
size
;
i
++
)
{
conf
=
conferencelist_get_nth
(
history
,
i
);
if
(
!
conf
)
{
DEBUG
(
"SFLphone: Error: Could not get %dth conference"
,
i
);
...
...
@@ -1443,36 +1423,13 @@ sflphone_srtp_zrtp_off (callable_obj_t * c)
void
sflphone_srtp_zrtp_show_sas
(
callable_obj_t
*
c
,
const
gchar
*
sas
,
const
gboolean
verified
)
{
if
(
c
==
NULL
)
{
DEBUG
(
"Panic callable obj is NULL in %s at %d"
,
__FILE__
,
__LINE__
);
}
c
->
_sas
=
g_strdup
(
sas
);
if
(
verified
==
TRUE
)
{
c
->
_srtp_state
=
SRTP_STATE_ZRTP_SAS_CONFIRMED
;
}
else
{
c
->
_srtp_state
=
SRTP_STATE_ZRTP_SAS_UNCONFIRMED
;
}
c
->
_srtp_state
=
verified
?
SRTP_STATE_ZRTP_SAS_CONFIRMED
:
SRTP_STATE_ZRTP_SAS_UNCONFIRMED
;
calltree_update_call
(
current_calls
,
c
,
NULL
);
update_actions
();
}
void
sflphone_srtp_zrtp_not_supported
(
callable_obj_t
*
c
)
{
DEBUG
(
"ZRTP not supported"
);
main_window_zrtp_not_supported
(
c
);
}
/* Method on sflphoned */
void
sflphone_set_confirm_go_clear
(
callable_obj_t
*
c
)
{
dbus_set_confirm_go_clear
(
c
);
}
void
sflphone_request_go_clear
(
void
)
{
...
...
@@ -1483,14 +1440,6 @@ sflphone_request_go_clear (void)
}
}
/* Signal sent by sflphoned */
void
sflphone_confirm_go_clear
(
callable_obj_t
*
c
)
{
main_window_confirm_go_clear
(
c
);
}
void
sflphone_call_state_changed
(
callable_obj_t
*
c
,
const
gchar
*
description
,
const
guint
code
)
{
...
...
gnome/src/actions.h
View file @
e85f8af2
...
...
@@ -58,9 +58,8 @@ gboolean sflphone_init () ;
/**
* Steps when closing the application. Will ask for confirmation if a call is in progress.
* @return TRUE if the user wants to quit, FALSE otherwise.
*/
gboolean
sflphone_quit
()
;
void
sflphone_quit
()
;
/**
* Hang up / refuse the current call
...
...
@@ -263,24 +262,6 @@ void sflphone_srtp_zrtp_off (callable_obj_t * c);
*/
void
sflphone_srtp_zrtp_show_sas
(
callable_obj_t
*
c
,
const
gchar
*
sas
,
const
gboolean
verified
);
/**
* Called when the remote peer does not support ZRTP
* @param c* The current call
*/
void
sflphone_srtp_zrtp_not_supported
(
callable_obj_t
*
c
);
/**
* Called when user wants to confirm go clear request.
* @param c* The call to confirm the go clear request.
*/
void
sflphone_set_confirm_go_clear
(
callable_obj_t
*
c
);
/**
* Called when user wants to confirm go clear request.
* @param c* The call to confirm the go clear request.
*/
void
sflphone_confirm_go_clear
(
callable_obj_t
*
c
);
/**
* Called when user wants to clear.
* @param c* The call on which to go clear
...
...
gnome/src/callable_obj.c
View file @
e85f8af2
...
...
@@ -105,60 +105,6 @@ void call_remove_all_errors (callable_obj_t * call)
g_ptr_array_foreach
(
call
->
_error_dialogs
,
(
GFunc
)
gtk_widget_destroy
,
NULL
);
}
static
void
threaded_clock_incrementer
(
void
*
pc
)
{
callable_obj_t
*
call
=
(
callable_obj_t
*
)
pc
;
for
(;;)
{
gdk_threads_enter
();
int
duration
=
difftime
(
time
(
NULL
),
call
->
_time_start
);
g_snprintf
(
call
->
_timestr
,
20
,
"%.2d:%.2d"
,
duration
%
60
,
duration
/
60
);
// Update clock only if call is active (current, hold, recording transfer)
if
(
(
call
->
_state
!=
CALL_STATE_INVALID
)
&&
(
call
->
_state
!=
CALL_STATE_INCOMING
)
&&
(
call
->
_state
!=
CALL_STATE_RINGING
)
&&
(
call
->
_state
!=
CALL_STATE_DIALING
)
&&
(
call
->
_state
!=
CALL_STATE_FAILURE
)
&&
(
call
->
_state
!=
CALL_STATE_BUSY
))
{
calltree_update_clock
();
}
gdk_threads_leave
();
GTimeVal
abs_time
;
g_get_current_time
(
&
abs_time
);
g_time_val_add
(
&
abs_time
,
1000000
);
g_mutex_lock
(
call
->
mutex
);
g_cond_timed_wait
(
call
->
cond
,
call
->
mutex
,
&
abs_time
);
gboolean
ret
=
call
->
exitClockThread
;
g_mutex_unlock
(
call
->
mutex
);
if
(
ret
==
TRUE
)
break
;
}
g_thread_exit
(
NULL
);
}
void
stop_call_clock
(
callable_obj_t
*
c
)
{
assert
(
c
->
_type
==
CALL
);
g_mutex_lock
(
c
->
mutex
);
c
->
exitClockThread
=
TRUE
;
g_cond_signal
(
c
->
cond
);
g_mutex_unlock
(
c
->
mutex
);
g_thread_join
(
c
->
clock_thread
);
g_mutex_free
(
c
->
mutex
);
g_cond_free
(
c
->
cond
);
c
->
clock_thread
=
NULL
;
}
callable_obj_t
*
create_new_call
(
callable_type_t
type
,
call_state_t
state
,
const
gchar
*
const
callID
,
const
gchar
*
const
accountID
,
...
...
@@ -182,13 +128,6 @@ callable_obj_t *create_new_call (callable_type_t type, call_state_t state,
obj
->
_peer_number
=
g_strdup
(
peer_number
);
obj
->
_peer_info
=
get_peer_info
(
peer_name
,
peer_number
);
if
(
obj
->
_type
==
CALL
)
{
obj
->
mutex
=
g_mutex_new
();
obj
->
cond
=
g_cond_new
();
obj
->
exitClockThread
=
FALSE
;
obj
->
clock_thread
=
g_thread_create
((
GThreadFunc
)
threaded_clock_incrementer
,
(
void
*
)
obj
,
TRUE
,
NULL
);
}
return
obj
;
}
...
...
@@ -283,8 +222,6 @@ void free_callable_obj_t (callable_obj_t *c)
g_free
(
c
->
_recordfile
);
g_free
(
c
);
calltree_update_clock
();
}
gchar
*
get_peer_info
(
const
gchar
*
const
number
,
const
gchar
*
const
name
)
...
...
gnome/src/callable_obj.h
View file @
e85f8af2
...
...
@@ -110,7 +110,6 @@ typedef struct {
gchar
*
_accountID
;
// The account the call is made with
time_t
_time_start
;
// The timestamp the call was initiating
time_t
_time_stop
;
// The timestamp the call was over
gchar
_timestr
[
20
];
// The timestamp as a string format for disply in statusbar
history_state_t
_history_state
;
// The history state if necessary
srtp_state_t
_srtp_state
;
// The state of security on the call
gchar
*
_srtp_cipher
;
// Cipher used for the srtp session
...
...
@@ -164,11 +163,6 @@ typedef struct {
GtkWidget
*
_im_widget
;
time_t
_time_added
;
GThread
*
clock_thread
;
GCond
*
cond
;
GMutex
*
mutex
;
gboolean
exitClockThread
;
}
callable_obj_t
;
callable_obj_t
*
create_new_call
(
callable_type_t
,
call_state_t
,
const
gchar
*
const
,
const
gchar
*
const
,
const
gchar
*
const
,
const
gchar
*
const
);
...
...
@@ -210,9 +204,6 @@ gchar* call_get_peer_number (const gchar*);
void
free_callable_obj_t
(
callable_obj_t
*
c
);
void
stop_call_clock
(
callable_obj_t
*
c
);
gchar
*
get_peer_info
(
const
gchar
*
const
,
const
gchar
*
const
);
history_state_t
get_history_state_from_id
(
gchar
*
indice
);
...
...
gnome/src/contacts/calltree.c
View file @
e85f8af2
...
...
@@ -604,8 +604,6 @@ void
calltree_remove_call
(
calltab_t
*
tab
,
callable_obj_t
*
c
,
GtkTreeIter
*
parent
)
{
GtkTreeIter
iter
;
GValue
val
;
callable_obj_t
*
iterCall
;
GtkTreeStore
*
store
=
tab
->
store
;
if
(
!
c
)
...
...
@@ -614,17 +612,15 @@ calltree_remove_call (calltab_t* tab, callable_obj_t * c, GtkTreeIter *parent)
DEBUG
(
"CallTree: Remove call %s"
,
c
->
_callID
);
int
nbChild
=
gtk_tree_model_iter_n_children
(
GTK_TREE_MODEL
(
store
),
parent
);
int
i
;
for
(
i
=
0
;
i
<
nbChild
;
i
++
)
{
for
(
int
i
=
0
;
i
<
nbChild
;
i
++
)
{
if
(
gtk_tree_model_iter_nth_child
(
GTK_TREE_MODEL
(
store
),
&
iter
,
parent
,
i
))
{
if
(
gtk_tree_model_iter_has_child
(
GTK_TREE_MODEL
(
store
),
&
iter
))
calltree_remove_call
(
tab
,
c
,
&
iter
);
val
.
g_type
=
0
;
GValue
val
=
{
.
g_type
=
0
}
;
gtk_tree_model_get_value
(
GTK_TREE_MODEL
(
store
),
&
iter
,
COLUMN_ACCOUNT_PTR
,
&
val
);
iterCall
=
(
callable_obj_t
*
)
g_value_get_pointer
(
&
val
);
callable_obj_t
*
iterCall
=
g_value_get_pointer
(
&
val
);
g_value_unset
(
&
val
);
if
(
iterCall
==
c
)
...
...
@@ -632,16 +628,12 @@ calltree_remove_call (calltab_t* tab, callable_obj_t * c, GtkTreeIter *parent)
}
}
callable_obj_t
*
selectedCall
=
calltab_get_selected_call
(
tab
);
if
(
selectedCall
==
c
)
if
(
calltab_get_selected_call
(
tab
)
==
c
)
calltab_select_call
(
tab
,
NULL
);
update_actions
();
calltree_update_clock
();
DEBUG
(
"Calltree remove call ended"
);
statusbar_update_clock
(
""
);
}
void
...
...
@@ -1202,50 +1194,47 @@ void calltree_remove_conference (calltab_t* tab, const conference_obj_t* conf, G
conference_obj_t
*
tempconf
=
NULL
;
GtkTreeStore
*
store
=
tab
->
store
;
int
nbParticipant
;
int
i
,
j
;
DEBUG
(
"CallTree: Remove conference %s"
,
conf
->
_confID
);
int
nbChild
=
gtk_tree_model_iter_n_children
(
GTK_TREE_MODEL
(
store
),
parent
);
for
(
i
=
0
;
i
<
nbChild
;
i
++
)
{
if
(
gtk_tree_model_iter_nth_child
(
GTK_TREE_MODEL
(
store
),
&
iter_parent
,
parent
,
i
))
{
if
(
gtk_tree_model_iter_has_child
(
GTK_TREE_MODEL
(
store
),
&
iter_parent
))
{
for
(
int
i
=
0
;
i
<
nbChild
;
i
++
)
{
if
(
!
gtk_tree_model_iter_nth_child
(
GTK_TREE_MODEL
(
store
),
&
iter_parent
,
parent
,
i
))
continue
;
if
(
!
gtk_tree_model_iter_has_child
(
GTK_TREE_MODEL
(
store
),
&
iter_parent
))
continue
;
calltree_remove_conference
(
tab
,
conf
,
&
iter_parent
);
calltree_remove_conference
(
tab
,
conf
,
&
iter_parent
);
confval
.
g_type
=
0
;
gtk_tree_model_get_value
(
GTK_TREE_MODEL
(
store
),
&
iter_parent
,
COLUMN_ACCOUNT_PTR
,
&
confval
);
confval
.
g_type
=
0
;
gtk_tree_model_get_value
(
GTK_TREE_MODEL
(
store
),
&
iter_parent
,
COLUMN_ACCOUNT_PTR
,
&
confval
);
tempconf
=
(
conference_obj_t
*
)
g_value_get_pointer
(
&
confval
);
g_value_unset
(
&
confval
);
tempconf
=
(
conference_obj_t
*
)
g_value_get_pointer
(
&
confval
);
g_value_unset
(
&
confval
);
if
(
tempconf
==
conf
)
{
if
(
tempconf
!=
conf
)
continue
;
nbParticipant
=
gtk_tree_model_iter_n_children
(
GTK_TREE_MODEL
(
store
),
&
iter_parent
);
DEBUG
(
"CallTree: nbParticipant: %d"
,
nbParticipant
);
nbParticipant
=
gtk_tree_model_iter_n_children
(
GTK_TREE_MODEL
(
store
),
&
iter_parent
);
DEBUG
(
"CallTree: nbParticipant: %d"
,
nbParticipant
);
for
(
j
=
0
;
j
<
nbParticipant
;
j
++
)
{
callable_obj_t
*
call
=
NULL
;
if
(
gtk_tree_model_iter_nth_child
(
GTK_TREE_MODEL
(
store
),
&
iter_child
,
&
iter_parent
,
j
))
{
for
(
int
j
=
0
;
j
<
nbParticipant
;
j
++
)
{
if
(
!
gtk_tree_model_iter_nth_child
(
GTK_TREE_MODEL
(
store
),
&
iter_child
,
&
iter_parent
,
j
))
continue
;
callval
.
g_type
=
0
;
gtk_tree_model_get_value
(
GTK_TREE_MODEL
(
store
),
&
iter_child
,
COLUMN_ACCOUNT_PTR
,
&
callval
);
callval
.
g_type
=
0
;
gtk_tree_model_get_value
(
GTK_TREE_MODEL
(
store
),
&
iter_child
,
COLUMN_ACCOUNT_PTR
,
&
callval
);
call
=
(
callable_obj_t
*
)
g_value_get_pointer
(
&
callval
);
g_value_unset
(
&
callval
);
callable_obj_t
*
call
=
g_value_get_pointer
(
&
callval
);
g_value_unset
(
&
callval
);
// do not add back call in history calltree when cleaning it
if
(
call
&&
tab
!=
history
)
calltree_add_call
(
tab
,
call
,
NULL
);
}
}
gtk_tree_store_remove
(
store
,
&
iter_parent
);
}
}
// do not add back call in history calltree when cleaning it
if
(
call
&&
tab
!=
history
)
calltree_add_call
(
tab
,
call
,
NULL
);
}
gtk_tree_store_remove
(
store
,
&
iter_parent
);
}
update_actions
();
...
...
@@ -1343,25 +1332,31 @@ void calltree_display (calltab_t *tab)
}
void
calltree_update_clock
()
gboolean
calltree_update_clock
(
gpointer
data
UNUSED
)
{
char
timestr
[
20
];
char
*
msg
=
""
;
long
duration
;
callable_obj_t
*
c
=
calltab_get_selected_call
(
current_calls
);
if
(
!
c
||
!
c
->
_timestr
)
{
statusbar_update_clock
(
""
);
return
;
}
if
((
c
->
_state
!=
CALL_STATE_INVALID
)
&&
(
c
->
_state
!=
CALL_STATE_INCOMING
)
&&
(
c
->
_state
!=
CALL_STATE_RINGING
)
&&
(
c
->
_state
!=
CALL_STATE_DIALING
)
&&
(
c
->
_state
!=
CALL_STATE_FAILURE
)
&&
(
c
->
_state
!=
CALL_STATE_BUSY
))
{
if
(
c
)
switch
(
c
->
_state
)
{
case
CALL_STATE_INVALID
:
case
CALL_STATE_INCOMING
:
case
CALL_STATE_RINGING
:
case
CALL_STATE_FAILURE
:
case
CALL_STATE_DIALING
:
case
CALL_STATE_BUSY
:
break
;
default:
duration
=
difftime
(
time
(
NULL
),
c
->
_time_start
);
if
(
duration
<
0
)
duration
=
0
;
g_snprintf
(
timestr
,
sizeof
(
timestr
),
"%.2ld:%.2ld"
,
duration
/
60
,
duration
%
60
);
msg
=
timestr
;
}
// TODO this make the whole thing crash...
statusbar_update_clock
(
c
->
_timestr
);
}
else
statusbar_update_clock
(
""
);
statusbar_update_clock
(
msg
);
}
...
...
gnome/src/contacts/calltree.h
View file @
e85f8af2
...
...
@@ -118,10 +118,10 @@ void
row_activated
(
GtkTreeView
*
,
GtkTreePath
*
,
GtkTreeViewColumn
*
,
void
*
);
/**
* Update elap
c
ed time based on selected calltree's call
* Update elap
s
ed time based on selected calltree's call
*/
void
calltree_update_clock
();
gboolean
calltree_update_clock
(
gpointer
);
/**
* Get the iter to a row provided the callID/confID
...
...
gnome/src/dbus/dbus.c
View file @
e85f8af2
...
...
@@ -652,10 +652,8 @@ confirm_go_clear_cb (DBusGProxy *proxy UNUSED, const gchar* callID, void * foo
DEBUG
(
"DBUS: Confirm Go Clear request"
);
callable_obj_t
*
c
=
calllist_get_call
(
current_calls
,
callID
);
if
(
c
)
{
sflphone_confirm_go_clear
(
c
);
}
if
(
c
)
main_window_confirm_go_clear
(
c
);
}
static
void
...
...
@@ -665,7 +663,7 @@ zrtp_not_supported_cb (DBusGProxy *proxy UNUSED, const gchar* callID, void * foo
callable_obj_t
*
c
=
calllist_get_call
(
current_calls
,
callID
);
if
(
c
)
{
sflphone_srtp
_zrtp_not_supported
(
c
);
main_window
_zrtp_not_supported
(
c
);
notify_zrtp_not_supported
(
c
);
}
}
...
...
gnome/src/mainwindow.c
View file @
e85f8af2
...
...
@@ -297,14 +297,13 @@ create_main_window ()
/* dont't show the contact list */
gtk_widget_hide
(
contacts
->
tree
);
//history_init(); // init search
/* don't show waiting layer */
gtk_widget_hide
(
waitingLayer
);
// pthread_mutex_init (&statusbar_message_mutex, NULL);
gmutex
=
g_mutex_new
();
g_timeout_add
(
1000
,
calltree_update_clock
,
NULL
);
// Configuration wizard
if
(
account_list_get_size
()
==
1
)
{
#if GTK_CHECK_VERSION(2,10,0)
...
...
@@ -441,16 +440,8 @@ statusbar_pop_message (guint id)
void
statusbar_update_clock
(
const
gchar
*
const
msg
)
{
gchar
*
message
=
NULL
;
if
(
!
msg
)
{
statusbar_pop_message
(
__MSG_ACCOUNT_DEFAULT
);
statusbar_push_message
(
message
,
NULL
,
__MSG_ACCOUNT_DEFAULT
);
}
g_mutex_lock
(
gmutex
);
message
=
g_strdup
(
status_current_message
);
gchar
*
message
=
g_strdup
(
status_current_message
);
g_mutex_unlock
(
gmutex
);
if
(
message
)
{
...
...
@@ -554,7 +545,7 @@ main_window_confirm_go_clear (callable_obj_t * c)
mini_dialog
=
pidgin_mini_dialog_new
(
_
(
"Confirm Go Clear"
),
desc
,
GTK_STOCK_STOP
);
pidgin_mini_dialog_add_button
(
mini_dialog
,
_
(
"Confirm"
),
(
PidginMiniDialogCallback
)
sflphone
_set_confirm_go_clear
,
NULL
);
(
PidginMiniDialogCallback
)
dbus
_set_confirm_go_clear
,
NULL
);
pidgin_mini_dialog_add_button
(
mini_dialog
,
_
(
"Stop Call"
),
sflphone_hang_up
,
NULL
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment