Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
L
liblinphone
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
10
Issues
10
List
Board
Labels
Milestones
Merge Requests
21
Merge Requests
21
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
External Wiki
External Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
BC
public
liblinphone
Commits
cfbd9b84
Commit
cfbd9b84
authored
Dec 04, 2013
by
Guillaume BIENKOWSKI
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
The LDAP suggestions are now correctly displayed in the suggestions
parent
8aa375d6
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
132 additions
and
107 deletions
+132
-107
ldapprovider.c
coreapi/ldap/ldapprovider.c
+36
-30
loginframe.c
gtk/loginframe.c
+0
-61
main.c
gtk/main.c
+96
-1
main.ui
gtk/main.ui
+0
-15
No files found.
coreapi/ldap/ldapprovider.c
View file @
cfbd9b84
...
...
@@ -73,6 +73,7 @@ struct _LinphoneLDAPContactSearch
char
*
filter
;
bool_t
complete
;
MSList
*
found_entries
;
int
found_count
;
};
...
...
@@ -80,7 +81,7 @@ struct _LinphoneLDAPContactSearch
* LinphoneLDAPContactSearch
* *************************/
LinphoneLDAPContactSearch
*
linphone_ldap_contact_search_create
(
LinphoneLDAPContactProvider
*
cp
,
const
char
*
predicate
,
ContactSearchCallback
cb
,
void
*
cb_data
)
LinphoneLDAPContactSearch
*
linphone_ldap_contact_search_create
(
LinphoneLDAPContactProvider
*
cp
,
const
char
*
predicate
,
ContactSearchCallback
cb
,
void
*
cb_data
)
{
LinphoneLDAPContactSearch
*
search
=
belle_sip_object_new
(
LinphoneLDAPContactSearch
);
...
...
@@ -118,9 +119,16 @@ LinphoneLDAPContactSearch*linphone_ldap_contact_search_create(LinphoneLDAPContac
return
search
;
}
void
linphone_ldap_contact_search_destroy_friend
(
void
*
entry
)
{
linphone_friend_destroy
((
LinphoneFriend
*
)
entry
);
}
static
void
linphone_ldap_contact_search_destroy
(
LinphoneLDAPContactSearch
*
obj
)
{
ms_message
(
"~LinphoneLDAPContactSearch(%p)"
,
obj
);
ms_list_for_each
(
obj
->
found_entries
,
linphone_ldap_contact_search_destroy_friend
);
obj
->
found_entries
=
ms_list_free
(
obj
->
found_entries
);
if
(
obj
->
filter
)
ms_free
(
obj
->
filter
);
}
...
...
@@ -136,6 +144,11 @@ BELLE_SIP_INSTANCIATE_VPTR(LinphoneLDAPContactSearch,LinphoneContactSearch,
/* ***************************
* LinphoneLDAPContactProvider
* ***************************/
static
inline
LinphoneLDAPContactSearch
*
linphone_ldap_request_entry_search
(
LinphoneLDAPContactProvider
*
obj
,
int
msgid
);
static
unsigned
int
linphone_ldap_contact_provider_cancel_search
(
LinphoneContactProvider
*
obj
,
LinphoneContactSearch
*
req
);
static
void
linphone_ldap_contact_provider_conf_destroy
(
LinphoneLDAPContactProvider
*
obj
);
/* Authentication methods */
struct
AuthMethodDescription
{
LDAPAuthMethod
method
;
const
char
*
description
;
...
...
@@ -147,10 +160,6 @@ static struct AuthMethodDescription ldap_auth_method_description[] = {
{
SASL
,
"sasl"
},
{
0
,
NULL
}
};
static
inline
LinphoneLDAPContactSearch
*
linphone_ldap_request_entry_search
(
LinphoneLDAPContactProvider
*
obj
,
int
msgid
);
static
unsigned
int
linphone_ldap_contact_provider_cancel_search
(
LinphoneContactProvider
*
obj
,
LinphoneContactSearch
*
req
);
static
void
linphone_ldap_contact_provider_conf_destroy
(
LinphoneLDAPContactProvider
*
obj
);
static
LDAPAuthMethod
linphone_ldap_contact_provider_auth_method
(
const
char
*
description
)
{
...
...
@@ -163,7 +172,7 @@ static LDAPAuthMethod linphone_ldap_contact_provider_auth_method( const char* de
return
ANONYMOUS
;
}
static
void
linphone_ldap_contact_provider_destroy_request
(
void
*
req
,
void
*
dummy
)
static
void
linphone_ldap_contact_provider_destroy_request
(
void
*
req
)
{
belle_sip_object_unref
(
req
);
}
...
...
@@ -171,7 +180,7 @@ static void linphone_ldap_contact_provider_destroy_request(void *req, void *dumm
static
void
linphone_ldap_contact_provider_destroy
(
LinphoneLDAPContactProvider
*
obj
)
{
// clean pending requests
ms_list_for_each
2
(
obj
->
requests
,
linphone_ldap_contact_provider_destroy_request
,
0
);
ms_list_for_each
(
obj
->
requests
,
linphone_ldap_contact_provider_destroy_request
);
if
(
obj
->
ld
)
ldap_unbind_ext
(
obj
->
ld
,
NULL
,
NULL
);
obj
->
ld
=
NULL
;
...
...
@@ -179,8 +188,7 @@ static void linphone_ldap_contact_provider_destroy( LinphoneLDAPContactProvider*
linphone_ldap_contact_provider_conf_destroy
(
obj
);
}
static
int
linphone_ldap_parse_bind_results
(
LinphoneLDAPContactProvider
*
obj
,
LDAPMessage
*
results
)
static
int
linphone_ldap_contact_provider_parse_bind_results
(
LinphoneLDAPContactProvider
*
obj
,
LDAPMessage
*
results
)
{
int
ret
=
ldap_parse_sasl_bind_result
(
obj
->
ld
,
results
,
NULL
,
0
);
if
(
ret
!=
LDAP_SUCCESS
){
...
...
@@ -191,23 +199,23 @@ static int linphone_ldap_parse_bind_results( LinphoneLDAPContactProvider* obj, L
return
ret
;
}
static
int
linphone_ldap_complete_contact
(
LinphoneLDAPContactProvider
*
obj
,
struct
LDAPFriendData
*
lf
,
const
char
*
attr_name
,
const
char
*
attr_value
)
static
int
linphone_ldap_co
ntact_provider_co
mplete_contact
(
LinphoneLDAPContactProvider
*
obj
,
struct
LDAPFriendData
*
lf
,
const
char
*
attr_name
,
const
char
*
attr_value
)
{
if
(
strcmp
(
attr_name
,
obj
->
name_attr
)
==
0
){
ms_message
(
"Got name attr: %s"
,
attr_value
);
lf
->
name
=
ms_strdup
(
attr_value
);
}
else
if
(
strcmp
(
attr_name
,
obj
->
sip_attr
)
==
0
)
{
ms_message
(
"Got sip attr: %s"
,
attr_value
);
lf
->
sip
=
ms_strdup
(
attr_value
);
}
// return 1 if the structure has enough data to create a linphone friend
if
(
lf
->
name
&&
lf
->
sip
)
return
1
;
else
return
0
;
if
(
lf
->
name
&&
lf
->
sip
)
return
1
;
else
return
0
;
}
static
void
linphone_ldap_handle_search_result
(
LinphoneLDAPContactProvider
*
obj
,
LinphoneLDAPContactSearch
*
req
,
LDAPMessage
*
message
)
static
void
linphone_ldap_
contact_provider_
handle_search_result
(
LinphoneLDAPContactProvider
*
obj
,
LinphoneLDAPContactSearch
*
req
,
LDAPMessage
*
message
)
{
int
msgtype
=
ldap_msgtype
(
message
);
...
...
@@ -222,7 +230,7 @@ static void linphone_ldap_handle_search_result( LinphoneLDAPContactProvider* obj
while
(
entry
!=
NULL
){
struct
LDAPFriendData
ldap_data
=
{
0
};
bool_t
all_found
=
FALSE
;
bool_t
contact_complete
=
FALSE
;
BerElement
*
ber
=
NULL
;
char
*
attr
=
ldap_first_attribute
(
obj
->
ld
,
entry
,
&
ber
);
char
*
dn
=
ldap_get_dn
(
obj
->
ld
,
entry
);
...
...
@@ -241,8 +249,8 @@ static void linphone_ldap_handle_search_result( LinphoneLDAPContactProvider* obj
{
ms_message
(
"%s -> %s"
,
attr
,
(
*
it
)
->
bv_val
);
all_found
=
linphone_ldap
_complete_contact
(
obj
,
&
ldap_data
,
attr
,
(
*
it
)
->
bv_val
);
if
(
all_found
)
break
;
contact_complete
=
linphone_ldap_contact_provider
_complete_contact
(
obj
,
&
ldap_data
,
attr
,
(
*
it
)
->
bv_val
);
if
(
contact_complete
)
break
;
it
++
;
}
...
...
@@ -250,18 +258,19 @@ static void linphone_ldap_handle_search_result( LinphoneLDAPContactProvider* obj
if
(
values
)
ldap_value_free_len
(
values
);
ldap_memfree
(
attr
);
if
(
all_found
)
break
;
if
(
contact_complete
)
break
;
attr
=
ldap_next_attribute
(
obj
->
ld
,
entry
,
ber
);
}
if
(
all_found
)
{
if
(
contact_complete
)
{
LinphoneAddress
*
la
=
linphone_core_interpret_url
(
lc
,
ldap_data
.
sip
);
if
(
la
){
LinphoneFriend
*
lf
=
linphone_core_create_friend
(
lc
);
linphone_friend_set_address
(
lf
,
la
);
linphone_friend_set_name
(
lf
,
ldap_data
.
name
);
req
->
found_entries
=
ms_list_append
(
req
->
found_entries
,
lf
);
req
->
found_count
++
;
ms_message
(
"Added friend %s / %s"
,
ldap_data
.
name
,
ldap_data
.
sip
);
ms_free
(
ldap_data
.
sip
);
ms_free
(
ldap_data
.
name
);
...
...
@@ -284,7 +293,6 @@ static void linphone_ldap_handle_search_result( LinphoneLDAPContactProvider* obj
break
;
default:
ms_message
(
"Unhandled message type %x"
,
msgtype
);
break
;
}
}
...
...
@@ -298,7 +306,7 @@ static bool_t linphone_ldap_contact_provider_iterate(void *data)
struct
timeval
timeout
=
{
0
,
0
};
LDAPMessage
*
results
=
NULL
;
int
ret
=
ldap_result
(
obj
->
ld
,
LDAP_RES_ANY
,
LDAP_MSG_
ALL
,
&
timeout
,
&
results
);
int
ret
=
ldap_result
(
obj
->
ld
,
LDAP_RES_ANY
,
LDAP_MSG_
ONE
,
&
timeout
,
&
results
);
if
(
ret
!=
0
&&
ret
!=
-
1
)
ms_message
(
"ldap_result %x"
,
ret
);
...
...
@@ -316,25 +324,25 @@ static bool_t linphone_ldap_contact_provider_iterate(void *data)
if
(
ldap_msgid
(
results
)
!=
obj
->
bind_msgid
)
{
ms_error
(
"Bad msgid"
);
}
else
{
linphone_ldap_parse_bind_results
(
obj
,
results
);
linphone_ldap_
contact_provider_
parse_bind_results
(
obj
,
results
);
obj
->
bind_msgid
=
0
;
// we're bound now, don't bother checking again
}
break
;
}
case
LDAP_RES_SEARCH_RESULT
:
case
LDAP_RES_EXTENDED
:
case
LDAP_RES_SEARCH_ENTRY
:
case
LDAP_RES_SEARCH_REFERENCE
:
case
LDAP_RES_INTERMEDIATE
:
case
LDAP_RES_SEARCH_RESULT
:
{
LDAPMessage
*
message
=
ldap_first_message
(
obj
->
ld
,
results
);
LinphoneLDAPContactSearch
*
req
=
linphone_ldap_request_entry_search
(
obj
,
ldap_msgid
(
message
));
while
(
message
!=
NULL
){
ms_message
(
"Message @%p:id %d / type %x / associated request: %p"
,
message
,
ldap_msgid
(
message
),
ldap_msgtype
(
message
),
req
);
linphone_ldap_handle_search_result
(
obj
,
req
,
message
);
linphone_ldap_
contact_provider_
handle_search_result
(
obj
,
req
,
message
);
message
=
ldap_next_message
(
obj
->
ld
,
message
);
}
if
(
req
)
linphone_ldap_contact_provider_cancel_search
(
LINPHONE_CONTACT_PROVIDER
(
obj
),
LINPHONE_CONTACT_SEARCH
(
req
));
if
(
req
&&
ret
==
LDAP_RES_SEARCH_RESULT
)
linphone_ldap_contact_provider_cancel_search
(
LINPHONE_CONTACT_PROVIDER
(
obj
),
LINPHONE_CONTACT_SEARCH
(
req
));
break
;
}
case
LDAP_RES_MODIFY
:
...
...
@@ -501,9 +509,8 @@ static int linphone_ldap_request_entry_compare_strong(const void*a, const void*
static
inline
LinphoneLDAPContactSearch
*
linphone_ldap_request_entry_search
(
LinphoneLDAPContactProvider
*
obj
,
int
msgid
)
{
LinphoneLDAPContactSearch
dummy
=
{
.
msgid
=
msgid
};
LinphoneLDAPContactSearch
dummy
=
{};
dummy
.
msgid
=
msgid
;
MSList
*
list_entry
=
ms_list_find_custom
(
obj
->
requests
,
linphone_ldap_request_entry_compare_weak
,
&
dummy
);
if
(
list_entry
)
return
list_entry
->
data
;
...
...
@@ -558,7 +565,6 @@ static int linphone_ldap_marshal(LinphoneLDAPContactProvider* obj, char* buff, s
error
=
belle_sip_snprintf
(
buff
,
buff_size
,
offset
,
"bind_msgid:%d,
\n
"
,
obj
->
bind_msgid
);
if
(
error
!=
BELLE_SIP_OK
)
return
error
;
error
=
belle_sip_snprintf
(
buff
,
buff_size
,
offset
,
"CONFIG:
\n
"
"tls: %d
\n
"
...
...
gtk/loginframe.c
View file @
cfbd9b84
...
...
@@ -167,67 +167,6 @@ void linphone_gtk_login_frame_connect_clicked(GtkWidget *button){
linphone_gtk_load_identities
();
}
void
test_cb
(
LinphoneContactSearch
*
req
,
MSList
*
friends
,
void
*
data
)
{
ms_message
(
"LDAP Search CB received:"
);
GtkEntry
*
uribar
=
GTK_ENTRY
(
linphone_gtk_get_widget
(
linphone_gtk_get_main_window
(),
"uribar"
));
GtkTreeModel
*
model
=
gtk_entry_completion_get_model
(
gtk_entry_get_completion
(
uribar
));
GtkListStore
*
list
=
GTK_LIST_STORE
(
model
);
GtkTreeIter
iter
;
// clear completion list from previous LDAP completion suggestions
if
(
!
gtk_tree_model_get_iter_first
(
model
,
&
iter
))
return
;
do
{
int
type
;
char
*
url
;
bool_t
valid
=
TRUE
;
gtk_tree_model_get
(
model
,
&
iter
,
1
,
&
type
,
0
,
&
url
,
-
1
);
if
(
type
==
COMPLETION_LDAP
)
{
ms_message
(
"Removing entry for %s"
,
url
?
url
:
"NULL"
);
valid
=
gtk_list_store_remove
(
list
,
&
iter
);
}
else
{
ms_message
(
"Keep entry for %s (type %d)"
,
url
?
url
:
"NULL"
,
type
);
}
if
(
url
)
g_free
(
url
);
if
(
!
valid
)
break
;
}
while
(
gtk_tree_model_iter_next
(
model
,
&
iter
));
while
(
friends
){
LinphoneFriend
*
lf
=
friends
->
data
;
if
(
lf
)
{
const
LinphoneAddress
*
la
=
linphone_friend_get_address
(
lf
);
if
(
la
){
char
*
addr
=
linphone_address_as_string
(
la
);
if
(
addr
){
ms_message
(
"Match: name=%s, addr=%s"
,
linphone_friend_get_name
(
lf
),
addr
);
gtk_list_store_insert_with_values
(
list
,
&
iter
,
-
1
,
0
,
addr
,
1
,
COMPLETION_LDAP
,
-
1
);
ms_free
(
addr
);
}
}
}
friends
=
friends
->
next
;
}
gtk_entry_completion_complete
(
gtk_entry_get_completion
(
uribar
));
}
void
test_btn_clicked_cb
(
GtkWidget
*
button
)
{
ms_message
(
"test_button_clicked_cb"
);
LinphoneCore
*
core
=
linphone_gtk_get_core
();
GtkWidget
*
uri_bar
=
linphone_gtk_get_widget
(
linphone_gtk_get_main_window
(),
"uribar"
);
const
gchar
*
pred
=
gtk_entry_buffer_get_text
(
gtk_entry_get_buffer
((
GtkEntry
*
)
uri_bar
));
linphone_core_ldap_launch_search
(
core
,
pred
,
test_cb
,
(
void
*
)
0x12345678
);
}
void
linphone_gtk_internet_kind_changed
(
GtkWidget
*
combo
){
int
netkind_id
=
gtk_combo_box_get_active
(
GTK_COMBO_BOX
(
combo
));
LinphoneCore
*
lc
=
linphone_gtk_get_core
();
...
...
gtk/main.c
View file @
cfbd9b84
...
...
@@ -69,7 +69,7 @@ void linphone_gtk_save_main_window_position(GtkWindow* mw, GdkEvent *event, gpoi
static
gboolean
linphone_gtk_auto_answer
(
LinphoneCall
*
call
);
void
linphone_gtk_status_icon_set_blinking
(
gboolean
val
);
void
_linphone_gtk_enable_video
(
gboolean
val
);
void
linphone_gtk_on_uribar_changed
(
GtkEditable
*
uribar
,
gpointer
user_data
);
#ifndef HAVE_GTK_OSX
static
gint
main_window_x
=
0
;
...
...
@@ -642,6 +642,10 @@ static gboolean uribar_completion_matchfunc(GtkEntryCompletion *completion, cons
ret
=
TRUE
;
g_free
(
tmp
);
}
if
(
address
)
g_free
(
address
);
return
ret
;
}
...
...
@@ -665,8 +669,11 @@ static void load_uri_history(){
}
gtk_entry_completion_set_model
(
gep
,
GTK_TREE_MODEL
(
model
));
gtk_entry_completion_set_text_column
(
gep
,
0
);
gtk_entry_completion_set_popup_completion
(
gep
,
TRUE
);
gtk_entry_completion_set_match_func
(
gep
,
uribar_completion_matchfunc
,
NULL
,
NULL
);
gtk_entry_completion_set_minimum_key_length
(
gep
,
3
);
gtk_entry_set_completion
(
uribar
,
gep
);
g_signal_connect
(
G_OBJECT
(
uribar
),
"changed"
,
G_CALLBACK
(
linphone_gtk_on_uribar_changed
),
NULL
);
}
static
void
save_uri_history
(){
...
...
@@ -718,6 +725,94 @@ static void completion_add_text(GtkEntry *entry, const char *text){
save_uri_history
();
}
void
on_contact_provider_search_results
(
LinphoneContactSearch
*
req
,
MSList
*
friends
,
void
*
data
)
{
GtkTreeIter
iter
;
GtkEntry
*
uribar
=
GTK_ENTRY
(
data
);
GtkEntryCompletion
*
compl
=
gtk_entry_get_completion
(
uribar
);
GtkTreeModel
*
model
=
gtk_entry_completion_get_model
(
compl
);
GtkListStore
*
list
=
GTK_LIST_STORE
(
model
);
gboolean
valid
;
// clear completion list from previous non-history completion suggestions
valid
=
gtk_tree_model_get_iter_first
(
model
,
&
iter
);
while
(
valid
)
{
char
*
url
;
int
type
;
gtk_tree_model_get
(
model
,
&
iter
,
0
,
&
url
,
1
,
&
type
,
-
1
);
if
(
type
!=
COMPLETION_HISTORY
)
{
valid
=
gtk_list_store_remove
(
list
,
&
iter
);
}
else
{
valid
=
gtk_tree_model_iter_next
(
model
,
&
iter
);
}
if
(
url
)
g_free
(
url
);
if
(
!
valid
)
break
;
}
// add new non-history related matches
while
(
friends
){
LinphoneFriend
*
lf
=
friends
->
data
;
if
(
lf
)
{
const
LinphoneAddress
*
la
=
linphone_friend_get_address
(
lf
);
if
(
la
){
char
*
addr
=
linphone_address_as_string
(
la
);
if
(
addr
){
ms_message
(
"[LDAP]Insert match: %s"
,
addr
);
gtk_list_store_insert_with_values
(
list
,
&
iter
,
-
1
,
0
,
addr
,
1
,
COMPLETION_LDAP
,
-
1
);
ms_free
(
addr
);
}
}
}
friends
=
friends
->
next
;
}
gtk_entry_completion_complete
(
compl
);
// Gtk bug? we need to emit a "changed" signal so that the completion appears if
// the list of results was previously empty
g_signal_handlers_block_by_func
(
uribar
,
linphone_gtk_on_uribar_changed
,
NULL
);
g_signal_emit_by_name
(
uribar
,
"changed"
);
g_signal_handlers_unblock_by_func
(
uribar
,
linphone_gtk_on_uribar_changed
,
NULL
);
}
struct
CompletionTimeout
{
guint
timeout_id
;
};
static
gboolean
launch_contact_provider_search
(
void
*
userdata
)
{
LinphoneCore
*
core
=
linphone_gtk_get_core
();
GtkWidget
*
uribar
=
GTK_WIDGET
(
userdata
);
const
gchar
*
predicate
=
gtk_entry_get_text
((
GtkEntry
*
)
uribar
);
if
(
strlen
(
predicate
)
>=
3
){
// don't search too small predicates
ms_message
(
"launch_contact_provider_search"
);
linphone_core_ldap_launch_search
(
core
,
predicate
,
on_contact_provider_search_results
,
uribar
);
}
return
FALSE
;
}
void
linphone_gtk_on_uribar_changed
(
GtkEditable
*
uribar
,
gpointer
user_data
)
{
gchar
*
text
=
gtk_editable_get_chars
(
uribar
,
0
,
-
1
);
gint
timeout
=
GPOINTER_TO_INT
(
gtk_object_get_data
(
GTK_OBJECT
(
uribar
),
"complete_timeout"
));
ms_message
(
"URIBAR changed, new text: %s, userdata %p uribar @%p"
,
text
,
user_data
,
uribar
);
if
(
text
)
g_free
(
text
);
if
(
timeout
!=
0
)
{
g_source_remove
(
timeout
);
}
timeout
=
g_timeout_add_seconds
(
1
,(
GSourceFunc
)
launch_contact_provider_search
,
uribar
);
gtk_object_set_data
(
GTK_OBJECT
(
uribar
),
"complete_timeout"
,
GINT_TO_POINTER
(
timeout
)
);
}
bool_t
linphone_gtk_video_enabled
(
void
){
const
LinphoneVideoPolicy
*
vpol
=
linphone_core_get_video_policy
(
linphone_gtk_get_core
());
return
vpol
->
automatically_accept
&&
vpol
->
automatically_initiate
;
...
...
gtk/main.ui
View file @
cfbd9b84
...
...
@@ -1063,21 +1063,6 @@
<property
name=
"position"
>
0
</property>
</packing>
</child>
<child>
<object
class=
"GtkButton"
id=
"test_btn"
>
<property
name=
"label"
translatable=
"yes"
>
TEST
</property>
<property
name=
"visible"
>
True
</property>
<property
name=
"can_focus"
>
True
</property>
<property
name=
"receives_default"
>
True
</property>
<property
name=
"use_action_appearance"
>
False
</property>
<signal
name=
"clicked"
handler=
"test_btn_clicked_cb"
swapped=
"no"
/>
</object>
<packing>
<property
name=
"expand"
>
False
</property>
<property
name=
"fill"
>
False
</property>
<property
name=
"position"
>
1
</property>
</packing>
</child>
<child>
<object
class=
"GtkButton"
id=
"add_call"
>
<property
name=
"can_focus"
>
True
</property>
...
...
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