Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
savoirfairelinux
jami-daemon
Commits
9b32b811
Commit
9b32b811
authored
Feb 19, 2009
by
Alexandre Savard
Browse files
Display codec used
parent
be5447f9
Changes
18
Hide whitespace changes
Inline
Side-by-side
sflphone-gtk/src/actions.c
View file @
9b32b811
...
...
@@ -135,6 +135,8 @@ sflphone_hung_up( call_t * c)
#if GTK_CHECK_VERSION(2,10,0)
status_tray_icon_blink
(
FALSE
);
#endif
statusbar_pop_message
(
__MSG_ACCOUNT_DEFAULT
);
}
/** Internal to actions: Fill account list */
...
...
@@ -733,6 +735,31 @@ sflphone_place_call ( call_t * c )
}
void
sflphone_display_selected_codec
(
const
gchar
*
codecName
)
{
call_t
*
selectedCall
=
call_get_selected
(
current_calls
);
gchar
*
msg
;
account_t
*
acc
;
if
(
selectedCall
->
accountID
!=
NULL
){
acc
=
account_list_get_by_id
(
selectedCall
->
accountID
);
msg
=
g_markup_printf_escaped
(
_
(
"%s account- %s %s"
)
,
(
gchar
*
)
g_hash_table_lookup
(
acc
->
properties
,
ACCOUNT_TYPE
),
(
gchar
*
)
g_hash_table_lookup
(
acc
->
properties
,
ACCOUNT_ALIAS
),
codecName
);
statusbar_push_message
(
msg
,
__MSG_ACCOUNT_DEFAULT
);
g_free
(
msg
);
}
}
gchar
*
sflphone_get_current_codec_name
()
{
call_t
*
selectedCall
=
call_get_selected
(
current_calls
);
return
dbus_get_current_codec_name
(
selectedCall
);
}
void
sflphone_rec_call
()
{
...
...
@@ -754,6 +781,9 @@ sflphone_rec_call()
}
update_call_tree
(
current_calls
,
selectedCall
);
update_menus
();
// gchar* codname = sflphone_get_current_codec_name();
// printf("sflphone_get_current_codec_name: %s \n",codname);
}
/* Internal to action - set the __CURRENT_ACCOUNT variable */
...
...
@@ -816,3 +846,4 @@ sflphone_fill_codec_list()
exit
(
0
);
}
}
sflphone-gtk/src/actions.h
View file @
9b32b811
...
...
@@ -168,4 +168,8 @@ void sflphone_fill_codec_list();
void
sflphone_record
(
call_t
*
c
);
void
sflphone_rec_call
(
void
);
gchar
*
sflphone_get_current_codec_name
();
void
sflphone_display_selected_codec
(
const
gchar
*
codecName
);
#endif
sflphone-gtk/src/callmanager-glue.h
View file @
9b32b811
...
...
@@ -529,6 +529,44 @@ org_sflphone_SFLphone_CallManager_get_current_call_id_async (DBusGProxy *proxy,
stuff
->
userdata
=
userdata
;
return
dbus_g_proxy_begin_call
(
proxy
,
"getCurrentCallID"
,
org_sflphone_SFLphone_CallManager_get_current_call_id_async_callback
,
stuff
,
g_free
,
G_TYPE_INVALID
);
}
static
#ifdef G_HAVE_INLINE
inline
#endif
gboolean
org_sflphone_SFLphone_CallManager_get_current_codec_name
(
DBusGProxy
*
proxy
,
const
char
*
IN_callID
,
char
**
OUT_codecName
,
GError
**
error
)
{
return
dbus_g_proxy_call
(
proxy
,
"getCurrentCodecName"
,
error
,
G_TYPE_STRING
,
IN_callID
,
G_TYPE_INVALID
,
G_TYPE_STRING
,
OUT_codecName
,
G_TYPE_INVALID
);
}
typedef
void
(
*
org_sflphone_SFLphone_CallManager_get_current_codec_name_reply
)
(
DBusGProxy
*
proxy
,
char
*
OUT_codecName
,
GError
*
error
,
gpointer
userdata
);
static
void
org_sflphone_SFLphone_CallManager_get_current_codec_name_async_callback
(
DBusGProxy
*
proxy
,
DBusGProxyCall
*
call
,
void
*
user_data
)
{
DBusGAsyncData
*
data
=
(
DBusGAsyncData
*
)
user_data
;
GError
*
error
=
NULL
;
char
*
OUT_codecName
;
dbus_g_proxy_end_call
(
proxy
,
call
,
&
error
,
G_TYPE_STRING
,
&
OUT_codecName
,
G_TYPE_INVALID
);
(
*
(
org_sflphone_SFLphone_CallManager_get_current_codec_name_reply
)
data
->
cb
)
(
proxy
,
OUT_codecName
,
error
,
data
->
userdata
);
return
;
}
static
#ifdef G_HAVE_INLINE
inline
#endif
DBusGProxyCall
*
org_sflphone_SFLphone_CallManager_get_current_codec_name_async
(
DBusGProxy
*
proxy
,
const
char
*
IN_callID
,
org_sflphone_SFLphone_CallManager_get_current_codec_name_reply
callback
,
gpointer
userdata
)
{
DBusGAsyncData
*
stuff
;
stuff
=
g_new
(
DBusGAsyncData
,
1
);
stuff
->
cb
=
G_CALLBACK
(
callback
);
stuff
->
userdata
=
userdata
;
return
dbus_g_proxy_begin_call
(
proxy
,
"getCurrentCodecName"
,
org_sflphone_SFLphone_CallManager_get_current_codec_name_async_callback
,
stuff
,
g_free
,
G_TYPE_STRING
,
IN_callID
,
G_TYPE_INVALID
);
}
#endif
/* defined DBUS_GLIB_CLIENT_WRAPPERS_org_sflphone_SFLphone_CallManager */
G_END_DECLS
sflphone-gtk/src/dbus.c
View file @
9b32b811
...
...
@@ -61,6 +61,15 @@ incoming_call_cb (DBusGProxy *proxy UNUSED,
sflphone_incoming_call
(
c
);
}
static
void
curent_selected_codec
(
DBusGProxy
*
proxy
UNUSED
,
const
gchar
*
callID
,
const
gchar
*
codecName
,
void
*
foo
UNUSED
)
{
g_print
(
"Codec decided! %s
\n
"
,
codecName
);
sflphone_display_selected_codec
(
codecName
);
}
static
void
volume_changed_cb
(
DBusGProxy
*
proxy
UNUSED
,
...
...
@@ -265,6 +274,14 @@ dbus_connect ()
dbus_g_proxy_connect_signal
(
callManagerProxy
,
"incomingCall"
,
G_CALLBACK
(
incoming_call_cb
),
NULL
,
NULL
);
/* Current codec */
dbus_g_object_register_marshaller
(
g_cclosure_user_marshal_VOID__STRING_STRING_STRING
,
G_TYPE_NONE
,
G_TYPE_STRING
,
G_TYPE_STRING
,
G_TYPE_INVALID
);
dbus_g_proxy_add_signal
(
callManagerProxy
,
"currentSelectedCodec"
,
G_TYPE_STRING
,
G_TYPE_STRING
,
G_TYPE_INVALID
);
dbus_g_proxy_connect_signal
(
callManagerProxy
,
"currentSelectedCodec"
,
G_CALLBACK
(
curent_selected_codec
),
NULL
,
NULL
);
/* Register a marshaller for STRING,STRING */
dbus_g_object_register_marshaller
(
g_cclosure_user_marshal_VOID__STRING_STRING
,
G_TYPE_NONE
,
G_TYPE_STRING
,
G_TYPE_STRING
,
G_TYPE_INVALID
);
...
...
@@ -782,6 +799,30 @@ dbus_codec_details( int payload )
return
array
;
}
gchar
*
dbus_get_current_codec_name
(
const
call_t
*
c
)
{
printf
(
"dbus_get_current_codec_name : CallID : %s
\n
"
,
c
->
callID
);
gchar
*
codecName
;
GError
*
error
=
NULL
;
org_sflphone_SFLphone_CallManager_get_current_codec_name
(
callManagerProxy
,
c
->
callID
,
&
codecName
,
&
error
);
if
(
error
)
{
g_error_free
(
error
);
}
printf
(
"dbus_get_current_codec_name : codecName : %s
\n
"
,
codecName
);
return
codecName
;
}
gchar
**
...
...
@@ -1298,7 +1339,6 @@ dbus_set_record(const call_t * c)
{
g_error_free
(
error
);
}
g_print
(
"called dbus_set_record on CallManager
\n
"
);
}
void
...
...
sflphone-gtk/src/dbus.h
View file @
9b32b811
...
...
@@ -177,6 +177,12 @@ gchar** dbus_get_active_codec_list( void );
*/
void
dbus_set_active_codec_list
(
const
gchar
**
list
);
/**
* CallManager - return the codec name
* @param call_t* current call
*/
gchar
*
dbus_get_current_codec_name
(
const
call_t
*
c
);
/**
* ConfigurationManager - Get the list of available input audio plugins
* @return gchar** The list of plugins
...
...
src/audio/audiortp.cpp
View file @
9b32b811
...
...
@@ -410,6 +410,8 @@ AudioRtpRTX::reSampleData(int sampleRate_codec, int nbSamples, int status)
return
0
;
}
void
AudioRtpRTX
::
run
()
{
//mic, we receive from soundcard in stereo, and we send encoded
...
...
src/audio/audiortp.h
View file @
9b32b811
...
...
@@ -69,7 +69,7 @@ class AudioRtpRTX : public ost::Thread, public ost::TimerPort {
/**
* Audio recording object
*/
AudioRecord
recAudio
;
//
AudioRecord recAudio;
/** A SIP call */
SIPCall
*
_ca
;
...
...
@@ -170,10 +170,6 @@ class AudioRtpRTX : public ost::Thread, public ost::TimerPort {
/** The audio codec used during the session */
AudioCodec
*
_audiocodec
;
/**
* Audio recording object
*/
// AudioRecord recAudio;
};
///////////////////////////////////////////////////////////////////////////////
...
...
@@ -206,7 +202,7 @@ class AudioRtp {
* Start recording
*/
void
setRecording
();
private:
// copy constructor
...
...
src/dbus/callmanager-glue.h
View file @
9b32b811
...
...
@@ -34,6 +34,7 @@ public:
register_method
(
CallManager_adaptor
,
setRecording
,
_setRecording_stub
);
register_method
(
CallManager_adaptor
,
getCallDetails
,
_getCallDetails_stub
);
register_method
(
CallManager_adaptor
,
getCurrentCallID
,
_getCurrentCallID_stub
);
register_method
(
CallManager_adaptor
,
getCurrentCodecName
,
_getCurrentCodecName_stub
);
}
::
DBus
::
IntrospectedInterface
*
const
introspect
()
const
...
...
@@ -115,6 +116,18 @@ public:
{
"callID"
,
"s"
,
false
},
{
0
,
0
,
0
}
};
static
::
DBus
::
IntrospectedArgument
getCurrentCodecName_args
[]
=
{
{
"callID"
,
"s"
,
true
},
{
"codecName"
,
"s"
,
false
},
{
0
,
0
,
0
}
};
static
::
DBus
::
IntrospectedArgument
currentSelectedCodec_args
[]
=
{
{
"callID"
,
"s"
,
false
},
{
"codecName"
,
"s"
,
false
},
{
0
,
0
,
0
}
};
static
::
DBus
::
IntrospectedArgument
incomingCall_args
[]
=
{
{
"accountID"
,
"s"
,
false
},
...
...
@@ -167,10 +180,12 @@ public:
{
"setRecording"
,
setRecording_args
},
{
"getCallDetails"
,
getCallDetails_args
},
{
"getCurrentCallID"
,
getCurrentCallID_args
},
{
"getCurrentCodecName"
,
getCurrentCodecName_args
},
{
0
,
0
}
};
static
::
DBus
::
IntrospectedMethod
CallManager_adaptor_signals
[]
=
{
{
"currentSelectedCodec"
,
currentSelectedCodec_args
},
{
"incomingCall"
,
incomingCall_args
},
{
"incomingMessage"
,
incomingMessage_args
},
{
"callStateChanged"
,
callStateChanged_args
},
...
...
@@ -218,11 +233,20 @@ public:
virtual
void
setRecording
(
const
std
::
string
&
callID
)
=
0
;
virtual
std
::
map
<
std
::
string
,
std
::
string
>
getCallDetails
(
const
std
::
string
&
callID
)
=
0
;
virtual
std
::
string
getCurrentCallID
()
=
0
;
virtual
std
::
string
getCurrentCodecName
(
const
std
::
string
&
callID
)
=
0
;
public:
/* signal emitters for this interface
*/
void
currentSelectedCodec
(
const
std
::
string
&
arg1
,
const
std
::
string
&
arg2
)
{
::
DBus
::
SignalMessage
sig
(
"currentSelectedCodec"
);
::
DBus
::
MessageIter
wi
=
sig
.
writer
();
wi
<<
arg1
;
wi
<<
arg2
;
emit_signal
(
sig
);
}
void
incomingCall
(
const
std
::
string
&
arg1
,
const
std
::
string
&
arg2
,
const
std
::
string
&
arg3
)
{
::
DBus
::
SignalMessage
sig
(
"incomingCall"
);
...
...
@@ -412,6 +436,17 @@ private:
wi
<<
argout1
;
return
reply
;
}
::
DBus
::
Message
_getCurrentCodecName_stub
(
const
::
DBus
::
CallMessage
&
call
)
{
::
DBus
::
MessageIter
ri
=
call
.
reader
();
std
::
string
argin1
;
ri
>>
argin1
;
std
::
string
argout1
=
getCurrentCodecName
(
argin1
);
::
DBus
::
ReturnMessage
reply
(
call
);
::
DBus
::
MessageIter
wi
=
reply
.
writer
();
wi
<<
argout1
;
return
reply
;
}
};
}
}
}
...
...
src/dbus/callmanager-introspec.xml
View file @
9b32b811
...
...
@@ -64,6 +64,16 @@
<method
name=
"getCurrentCallID"
>
<arg
type=
"s"
name=
"callID"
direction=
"out"
/>
</method>
<method
name=
"getCurrentCodecName"
>
<arg
type=
"s"
name=
"callID"
direction=
"in"
/>
<arg
type=
"s"
name=
"codecName"
direction=
"out"
/>
</method>
<signal
name=
"currentSelectedCodec"
>
<arg
type=
"s"
name=
"callID"
direction=
"out"
/>
<arg
type=
"s"
name=
"codecName"
direction=
"out"
/>
</signal>
<signal
name=
"incomingCall"
>
<arg
type=
"s"
name=
"accountID"
/>
...
...
@@ -91,7 +101,6 @@
<arg
type=
"s"
name=
"device"
direction=
"out"
/>
<arg
type=
"d"
name=
"value"
direction=
"out"
/>
</signal>
<signal
name=
"error"
>
...
...
src/dbus/callmanager.cpp
View file @
9b32b811
...
...
@@ -101,7 +101,7 @@ CallManager::setVolume( const std::string& device, const double& value )
double
CallManager
::
getVolume
(
const
std
::
string
&
device
)
{
_debug
(
"CallManager::getVolume received
\n
"
);
_debug
(
"CallManager::getVolume received
\n
"
);
if
(
device
==
"speaker"
)
{
_debug
(
"Current speaker = %d
\n
"
,
Manager
::
instance
().
getSpkrVolume
());
...
...
@@ -123,6 +123,14 @@ CallManager::setRecording(const std::string& callID)
}
std
::
string
CallManager
::
getCurrentCodecName
(
const
std
::
string
&
callID
)
{
_debug
(
"CallManager::getCurrentCodecName received %s
\n
"
,
Manager
::
instance
().
getCurrentCodecName
(
callID
).
c_str
());
return
Manager
::
instance
().
getCurrentCodecName
(
callID
).
c_str
();
}
std
::
map
<
std
::
string
,
std
::
string
>
CallManager
::
getCallDetails
(
const
std
::
string
&
callID
UNUSED
)
{
...
...
src/dbus/callmanager.h
View file @
9b32b811
...
...
@@ -49,6 +49,7 @@ public:
void
setVolume
(
const
std
::
string
&
device
,
const
double
&
value
);
double
getVolume
(
const
std
::
string
&
device
);
void
setRecording
(
const
std
::
string
&
callID
);
std
::
string
getCurrentCodecName
(
const
std
::
string
&
callID
);
std
::
map
<
std
::
string
,
std
::
string
>
getCallDetails
(
const
std
::
string
&
callID
);
std
::
string
getCurrentCallID
(
);
void
playDTMF
(
const
std
::
string
&
key
);
...
...
src/iaxvoiplink.cpp
View file @
9b32b811
...
...
@@ -213,6 +213,12 @@ IAXVoIPLink::getEvent()
}
_mutexIAX
.
leaveMutex
();
if
(
call
){
// _debug("Are we recording");
call
->
recAudio
.
recData
(
spkrDataConverted
,
micData
,
nbSampleForRec_
,
nbSampleForRec_
);
}
// Do the doodle-moodle to send audio from the microphone to the IAX channel.
sendAudioFromMic
();
...
...
@@ -220,12 +226,8 @@ IAXVoIPLink::getEvent()
if
(
_nextRefreshStamp
&&
_nextRefreshStamp
-
2
<
time
(
NULL
))
{
sendRegister
(
""
);
}
if
(
call
){
// _debug("Are we recording");
call
->
recAudio
.
recData
(
spkrDataConverted
,
micData
,
nbSampleForRec_
,
nbSampleForRec_
);
}
// _debug("IAXVoIPLink::getEvent() \n");
// reinitialize speaker buffer for recording (when recording a voice mail)
for
(
int
i
=
0
;
i
<
nbSampleForRec_
;
i
++
)
spkrDataConverted
[
i
]
=
0
;
...
...
@@ -239,6 +241,8 @@ IAXVoIPLink::getEvent()
void
IAXVoIPLink
::
sendAudioFromMic
(
void
)
{
// _debug("IAXVoIPLink::sendAudioFromMic");
int
maxBytesToGet
,
availBytesFromMic
,
bytesAvail
,
compSize
;
AudioCodec
*
ac
;
...
...
@@ -299,7 +303,9 @@ IAXVoIPLink::sendAudioFromMic(void)
nbSample_
=
audiolayer
->
getMic
(
micData
,
bytesAvail
)
/
sizeof
(
SFLDataFormat
);
// Store the number of samples for recording
nbSampleForRec_
=
nbSample_
;
nbSampleForRec_
=
nbSample_
;
// _debug("IAXVoIPLink::sendAudioFromMic : %i \n",nbSampleForRec_);
// resample
nbSample_
=
converter
->
downsampleData
(
micData
,
micDataConverted
,
(
int
)
ac
->
getClockRate
()
,
(
int
)
audiolayer
->
getSampleRate
()
,
nbSample_
);
...
...
@@ -600,6 +606,16 @@ IAXVoIPLink::carryingDTMFdigits(const CallID& id, char code)
}
std
::
string
IAXVoIPLink
::
getCurrentCodecName
()
{
IAXCall
*
call
=
getIAXCall
(
Manager
::
instance
().
getCurrentCallId
());
AudioCodec
*
ac
=
call
->
getCodecMap
().
getCodec
(
call
->
getAudioCodec
());
return
ac
->
getCodecName
();
}
bool
IAXVoIPLink
::
iaxOutgoingInvite
(
IAXCall
*
call
)
...
...
@@ -730,6 +746,7 @@ IAXVoIPLink::iaxHandleCallEvent(iax_event* event, IAXCall* call)
case
IAX_EVENT_VOICE
:
//if (!audiolayer->isCaptureActive ())
// audiolayer->startStream ();
// _debug("IAX_EVENT_VOICE: \n");
iaxHandleVoiceEvent
(
event
,
call
);
break
;
...
...
src/iaxvoiplink.h
View file @
9b32b811
...
...
@@ -190,6 +190,13 @@ class IAXVoIPLink : public VoIPLink
bool
isContactPresenceSupported
()
{
return
false
;
}
/**
* Return the codec protocol used for this call
* @param id The call identifier
*/
std
::
string
getCurrentCodecName
();
public:
// iaxvoiplink only
void
updateAudiolayer
(
void
);
...
...
src/managerimpl.cpp
View file @
9b32b811
...
...
@@ -239,6 +239,10 @@ ManagerImpl::answerCall(const CallID& id)
removeCallAccount
(
id
);
return
false
;
}
std
::
string
codecName
=
getCurrentCodecName
(
id
);
_debug
(
"ManagerImpl::hangupCall(): broadcast codec name %s
\n
"
,
codecName
.
c_str
());
if
(
_dbus
)
_dbus
->
getCallManager
()
->
currentSelectedCodec
(
id
,
codecName
.
c_str
());
// if it was waiting, it's waiting no more
if
(
_dbus
)
_dbus
->
getCallManager
()
->
callStateChanged
(
id
,
"CURRENT"
);
...
...
@@ -251,7 +255,7 @@ ManagerImpl::answerCall(const CallID& id)
bool
ManagerImpl
::
hangupCall
(
const
CallID
&
id
)
{
_debug
(
"ManagerImpl::hangupCall(): This function is called when user hangup
\n
"
);
_debug
(
"ManagerImpl::hangupCall(): This function is called when user hangup
\n
"
);
PulseLayer
*
pulselayer
;
AccountID
accountid
;
bool
returnValue
;
...
...
@@ -635,6 +639,10 @@ ManagerImpl::peerAnsweredCall(const CallID& id)
stopTone
(
false
);
}
if
(
_dbus
)
_dbus
->
getCallManager
()
->
callStateChanged
(
id
,
"CURRENT"
);
std
::
string
codecName
=
getCurrentCodecName
(
id
);
_debug
(
"ManagerImpl::hangupCall(): broadcast codec name %s
\n
"
,
codecName
.
c_str
());
if
(
_dbus
)
_dbus
->
getCallManager
()
->
currentSelectedCodec
(
id
,
codecName
.
c_str
());
}
//THREAD=VoIP Call=Outgoing
...
...
@@ -1186,6 +1194,15 @@ ManagerImpl::getCodecDetails( const int32_t& payload )
return
v
;
}
std
::
string
ManagerImpl
::
getCurrentCodecName
(
const
CallID
&
id
)
{
// _debug("ManagerImpl::getCurrentCodecName method called \n");
AccountID
accountid
=
getAccountFromCall
(
id
);
// _debug("ManagerImpl::getCurrentCodecName : %s \n",getAccountLink(accountid)->getCurrentCodecName().c_str());
return
getAccountLink
(
accountid
)
->
getCurrentCodecName
();
}
/**
* Get list of supported input audio plugin
*/
...
...
src/managerimpl.h
View file @
9b32b811
...
...
@@ -326,6 +326,13 @@ class ManagerImpl {
*/
std
::
vector
<
::
std
::
string
>
getCodecDetails
(
const
int32_t
&
payload
);
/**
* Get current codec name
* @param call id
* @return std::string The codec name
*/
std
::
string
getCurrentCodecName
(
const
CallID
&
id
);
/**
* Get a list of supported input audio plugin
* @return std::vector<std::string> List of names
...
...
src/sipvoiplink.cpp
View file @
9b32b811
...
...
@@ -799,6 +799,20 @@ SIPVoIPLink::isRecording(const CallID& id)
return
call
->
isRecording
();
}
std
::
string
SIPVoIPLink
::
getCurrentCodecName
()
{
// _debug("SIPVoIPLink::getCurrentCodecName : \n");
SIPCall
*
call
=
getSIPCall
(
Manager
::
instance
().
getCurrentCallId
());
AudioCodec
*
ac
=
call
->
getCodecMap
().
getCodec
(
call
->
getAudioCodec
());
return
ac
->
getCodecName
();
}
bool
SIPVoIPLink
::
carryingDTMFdigits
(
const
CallID
&
id
,
char
code
)
{
...
...
src/sipvoiplink.h
View file @
9b32b811
...
...
@@ -298,6 +298,13 @@ class SIPVoIPLink : public VoIPLink
*/
bool
isRecording
(
const
CallID
&
id
);
/**
* Return the codec protocol used for this call
* @param id The call identifier
*/
std
::
string
getCurrentCodecName
();
private:
/**
* Constructor
...
...
src/voiplink.h
View file @
9b32b811
...
...
@@ -173,6 +173,12 @@ class VoIPLink {
*/
virtual
bool
isRecording
(
const
CallID
&
id
)
=
0
;
/**
* Return the codec protocol used for this call
* @param id The call identifier
*/
virtual
std
::
string
getCurrentCodecName
()
=
0
;
bool
initDone
(
void
)
{
return
_initDone
;
}
void
initDone
(
bool
state
)
{
_initDone
=
state
;
}
...
...
Write
Preview
Supports
Markdown
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