commit 07eb23da0f1e4854881c8a7f091cac9577c37c8e Author: Bob Ham Date: Thu May 17 14:16:51 2018 +0100 Initial import of cleaned Calls working tree diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..df2c4f6 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1 @@ +((nil . ((indent-tabs-mode . nil)))) diff --git a/README.md b/README.md new file mode 100644 index 0000000..63d082b --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# Calls + +A phone dialer and call handler. + +## License + +Calls is licensed under the GPLv3+. + +## Dependencies + + sudo apt-get install libgtk-3-dev libhandy-0.0-dev + +## Building + +We use the meson and thereby Ninja. The quickest way to get going is +to do the following: + + meson ../calls-build + ninja -C ../calls-build + ninja -C ../calls-build install + + +## Running +Calls depends on oFono Modem objects being present on D-Bus. To run +oFono with useful output: + + sudo OFONO_AT_DEBUG=1 ofonod -n -d + +The test programs within the (oFono source +tree)[https://git.kernel.org/pub/scm/network/ofono/ofono.git] are +useful to bring up a modem to a suitable state. For example: + + cd $OFONO_SOURCE/test + ./list-modems + ./enable-modem /sim7100 + ./online-modem /sim7100 + +Then run Calls. + +### Phonesim +One can also make use of the modem simulator, phonesim (in the +ofono-phonesim package in Debian): + + ofono-phonesim -p 12345 -gui /usr/local/share/phonesim/default.xml + +then, ensuring /etc/ofono/phonesim.conf has appropriate contents like: + + [phonesim] + Address=127.0.0.1 + Port=12345 + +run oFono as above, then: + + cd $OFONO_SOURCE/test + ./enable-modem /phonesim + ./online-modem /phonesim + +And again run Calls. diff --git a/calls.doap b/calls.doap new file mode 100644 index 0000000..50c32d3 --- /dev/null +++ b/calls.doap @@ -0,0 +1,25 @@ + + + + Calls + Calls + A phone call dialer + Calls is a dialer for phone calls, initially PSTN calls + but eventually other systems like SIP in future. + + + + C + + + + Bob Ham + + + + + diff --git a/doc/provider-abstraction-02.dia b/doc/provider-abstraction-02.dia new file mode 100644 index 0000000..a87476f Binary files /dev/null and b/doc/provider-abstraction-02.dia differ diff --git a/libgdbofono/call.xml b/libgdbofono/call.xml new file mode 100644 index 0000000..6dfe712 --- /dev/null +++ b/libgdbofono/call.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/libgdbofono/dbus-introspect.sh b/libgdbofono/dbus-introspect.sh new file mode 100755 index 0000000..5a851ad --- /dev/null +++ b/libgdbofono/dbus-introspect.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +DEST="$1" +OBJ_PATH="$2" +METHOD="$3" +shift 3 + +dbus-send "$@" --print-reply --dest="$DEST" "$OBJ_PATH" "$METHOD" | \ + grep -v '^method return' | \ + sed -e 's/^[[:space:]]\+string ""__' diff --git a/libgdbofono/gen.sh b/libgdbofono/gen.sh new file mode 100755 index 0000000..92b4d5d --- /dev/null +++ b/libgdbofono/gen.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +#set -x +#echo "$@" + +INPUT="$1" +OUTPUT0="$2" + +BASENAME="$( basename "$INPUT" .xml )" +WD="$PWD" +DIR="$( dirname "$OUTPUT0" )" + +cd "$DIR" +gdbus-codegen \ + --generate-c-code "gdbo-${BASENAME}" \ + --c-namespace GDBO \ + --interface-prefix org.ofono. \ + "${WD}/${INPUT}" diff --git a/libgdbofono/manager.xml b/libgdbofono/manager.xml new file mode 100644 index 0000000..13e9d56 --- /dev/null +++ b/libgdbofono/manager.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/libgdbofono/meson.build b/libgdbofono/meson.build new file mode 100644 index 0000000..796e7b4 --- /dev/null +++ b/libgdbofono/meson.build @@ -0,0 +1,38 @@ +gdbus_codegen = find_program('gdbus-codegen') + +gen_sh = find_program('gen.sh') + +gdbofono_gen = generator(gen_sh, + output : [ 'gdbo-@BASENAME@.c', + 'gdbo-@BASENAME@.h' ], + arguments : [ '@INPUT@', '@OUTPUT0@' ]) + +#manager_src = gdbus_ofono_gen.process('manager.xml') +#modem_src = gdbus_ofono_gen.process('modem.xml') +#call_src = gdbus_ofono_gen.process('call.xml') + +manager_src = custom_target('manager', + input : 'manager.xml', + output : [ 'gdbo-manager.h', + 'gdbo-manager.c' ], + command : [gen_sh, '@INPUT@', '@OUTPUT0@']) + +modem_src = custom_target('modem', + input : 'modem.xml', + output : [ 'gdbo-modem.h', + 'gdbo-modem.c' ], + command : [gen_sh, '@INPUT@', '@OUTPUT0@']) + +call_src = custom_target('call', + input : 'call.xml', + output : [ 'gdbo-call.h', + 'gdbo-call.c' ], + command : [gen_sh, '@INPUT@', '@OUTPUT0@']) + +deps = [ dependency('gio-2.0'), + dependency('gio-unix-2.0'), + ] + +gdbofono_lib = shared_library('gdbofono', + manager_src, modem_src, call_src, + dependencies : deps ) diff --git a/libgdbofono/modem-full.xml b/libgdbofono/modem-full.xml new file mode 100644 index 0000000..5319672 --- /dev/null +++ b/libgdbofono/modem-full.xml @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libgdbofono/modem.xml b/libgdbofono/modem.xml new file mode 100644 index 0000000..c02d250 --- /dev/null +++ b/libgdbofono/modem.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..f7c5174 --- /dev/null +++ b/meson.build @@ -0,0 +1,33 @@ +# +# Copyright (C) 2018 Purism SPC +# +# This file is part of Dialer. +# +# Dialer is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Dialer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Dialer. If not, see . +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +project('call', 'c', + version: '0.0.0', + license: 'GPLv3+', + meson_version: '>= 0.40.1', + default_options: [ 'warning_level=1', 'buildtype=debugoptimized', 'c_std=gnu11' ], +) + + +topdir_includes = include_directories('.') + +subdir('libgdbofono') +subdir('src') diff --git a/src/calls-call-data.c b/src/calls-call-data.c new file mode 100644 index 0000000..d7d6e52 --- /dev/null +++ b/src/calls-call-data.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-call-data.h" +#include "util.h" + +#include +#include + +/** + * SECTION:calls-call-data + * @short_description: An object to hold both a #CallsCall object and + * the #CallsParty participating in the call + * @Title: CallsCallData + */ + +struct _CallsCallData +{ + GObject parent_instance; + + CallsCall *call; + CallsParty *party; +}; + +G_DEFINE_TYPE (CallsCallData, calls_call_data, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_CALL, + PROP_PARTY, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +CallsCallData * +calls_call_data_new (CallsCall *call, CallsParty *party) +{ + return g_object_new (CALLS_TYPE_CALL_DATA, + "call", call, + "party", party, + NULL); +} + +CallsCall * +calls_call_data_get_call (CallsCallData *data) +{ + g_return_val_if_fail (CALLS_IS_CALL_DATA (data), NULL); + return data->call; +} + + +CallsParty * +calls_call_data_get_party (CallsCallData *data) +{ + g_return_val_if_fail (CALLS_IS_CALL_DATA (data), NULL); + return data->party; +} + + +static void +get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + CallsCallData *self = CALLS_CALL_DATA (object); + + switch (property_id) { + case PROP_CALL: + g_value_set_object (value, self->call); + break; + + case PROP_PARTY: + g_value_set_object (value, self->party); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsCallData *self = CALLS_CALL_DATA (object); + + switch (property_id) { + case PROP_CALL: + CALLS_SET_OBJECT_PROPERTY (self->call, CALLS_CALL (g_value_get_object (value))); + break; + + case PROP_PARTY: + CALLS_SET_OBJECT_PROPERTY (self->party, CALLS_PARTY (g_value_get_object (value))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +calls_call_data_init (CallsCallData *self) +{ +} + + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsCallData *self = CALLS_CALL_DATA (object); + + CALLS_DISPOSE_OBJECT (self->call); + CALLS_DISPOSE_OBJECT (self->party); + + parent_class->dispose (object); +} + + +static void +calls_call_data_class_init (CallsCallDataClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + + props[PROP_CALL] = + g_param_spec_object ("call", + _("Call"), + _("The call"), + CALLS_TYPE_CALL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + props[PROP_PARTY] = + g_param_spec_object ("party", + _("Party"), + _("The party participating in the call"), + CALLS_TYPE_PARTY, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} diff --git a/src/calls-call-data.h b/src/calls-call-data.h new file mode 100644 index 0000000..1a95015 --- /dev/null +++ b/src/calls-call-data.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_CALL_DATA_H__ +#define CALLS_CALL_DATA_H__ + +#include "calls-call.h" +#include "calls-party.h" + +G_BEGIN_DECLS + +#define CALLS_TYPE_CALL_DATA (calls_call_data_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsCallData, calls_call_data, CALLS, CALL_DATA, GObject); + +CallsCallData *calls_call_data_new (CallsCall *call, CallsParty *party); +CallsCall *calls_call_data_get_call (CallsCallData *data); +CallsParty *calls_call_data_get_party (CallsCallData *data); + +G_END_DECLS + +#endif /* CALLS_CALL_DATA_H__ */ diff --git a/src/calls-call-display.c b/src/calls-call-display.c new file mode 100644 index 0000000..1fab03b --- /dev/null +++ b/src/calls-call-display.c @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-call-display.h" +#include "calls-call-data.h" +#include "util.h" + +#include +#include +#include + +#define HANDY_USE_UNSTABLE_API +#include + +struct _CallsCallDisplay +{ + GtkBox parent_instance; + + CallsCall *call; + GTimer *timer; + guint timeout; + + GtkBox *party_box; + GtkLabel *name; + GtkLabel *status; + GtkLabel *time; + + GtkButton *answer; + GtkToggleButton *hold; + GtkButton *hang_up; + GtkToggleButton *speaker; +}; + +G_DEFINE_TYPE (CallsCallDisplay, calls_call_display, GTK_TYPE_BOX); + +enum { + PROP_0, + PROP_CALL_DATA, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + +static void +answer_clicked_cb (GtkButton *button, + CallsCallDisplay *self) +{ + g_return_if_fail (CALLS_IS_CALL_DISPLAY (self)); + + if (self->call) + { + calls_call_answer (self->call); + } +} + +static void +hang_up_clicked_cb (GtkButton *button, + CallsCallDisplay *self) +{ + g_return_if_fail (CALLS_IS_CALL_DISPLAY (self)); + + if (self->call) + { + calls_call_hang_up (self->call); + } +} + +static void +hold_toggled_cb (GtkToggleButton *togglebutton, + CallsCallDisplay *self) +{ +} + +static void +speaker_toggled_cb (GtkToggleButton *togglebutton, + CallsCallDisplay *self) +{ +} + + +static gboolean +timeout_cb (CallsCallDisplay *self) +{ +#define MINUTE 60 +#define HOUR (60 * MINUTE) +#define DAY (24 * HOUR) + + gdouble elapsed; + GString *str; + gboolean printing; + guint minutes; + + g_return_val_if_fail (CALLS_IS_CALL_DISPLAY (self), FALSE); + if (!self->call) + { + return FALSE; + } + + elapsed = g_timer_elapsed (self->timer, NULL); + + str = g_string_new (""); + + if ( (printing = (elapsed > DAY)) ) + { + guint days = (guint)(elapsed / DAY); + g_string_append_printf (str, "%ud ", days); + elapsed -= (days * DAY); + } + + if (printing || elapsed > HOUR) + { + guint hours = (guint)(elapsed / HOUR); + g_string_append_printf (str, "%u:", hours); + elapsed -= (hours * HOUR); + } + + minutes = (guint)(elapsed / MINUTE); + g_string_append_printf (str, "%u:", minutes); + elapsed -= (minutes * MINUTE); + + g_string_append_printf (str, "%02u", (guint)elapsed); + + gtk_label_set_text (self->time, str->str); + + g_string_free (str, TRUE); + return TRUE; + +#undef DAY +#undef HOUR +#undef MINUTE +} + +static void +call_state_changed_cb (CallsCallDisplay *self, + CallsCallState state) +{ + GString *state_str = g_string_new(""); + + g_return_if_fail (CALLS_IS_CALL_DISPLAY (self)); + + calls_call_state_to_string (state_str, state); + gtk_label_set_text (self->status, state_str->str); + g_string_free (state_str, TRUE); + + switch (state) + { + case CALLS_CALL_STATE_INCOMING: + gtk_widget_show (GTK_WIDGET (self->answer)); + gtk_widget_hide (GTK_WIDGET (self->hold)); + gtk_widget_hide (GTK_WIDGET (self->speaker)); + break; + case CALLS_CALL_STATE_ACTIVE: + case CALLS_CALL_STATE_HELD: + case CALLS_CALL_STATE_DIALING: + case CALLS_CALL_STATE_ALERTING: + case CALLS_CALL_STATE_WAITING: + gtk_widget_hide (GTK_WIDGET (self->answer)); + gtk_widget_show (GTK_WIDGET (self->hold)); + gtk_widget_show (GTK_WIDGET (self->speaker)); + break; + case CALLS_CALL_STATE_DISCONNECTED: + break; + } +} + + +CallsCallDisplay * +calls_call_display_new (CallsCallData *data) +{ + return g_object_new (CALLS_TYPE_CALL_DISPLAY, + "call-data", data, + NULL); +} + + +static void +set_call (CallsCallDisplay *self, CallsCall *call) +{ + g_signal_connect_object (call, "state-changed", + G_CALLBACK (call_state_changed_cb), + self, + G_CONNECT_SWAPPED); + self->call = call; + g_object_ref (G_OBJECT (call)); +} + + +static void +set_party (CallsCallDisplay *self, CallsParty *party) +{ + GtkWidget *image; + + image = calls_party_create_image (party); + gtk_box_pack_start (self->party_box, image, TRUE, TRUE, 0); + gtk_image_set_pixel_size (GTK_IMAGE (image), 100); + gtk_widget_show (image); + + gtk_label_set_text (self->name, calls_party_get_label (party)); +} + + +static void +set_call_data (CallsCallDisplay *self, CallsCallData *data) +{ + set_call (self, calls_call_data_get_call (data)); + set_party (self, calls_call_data_get_party (data)); +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsCallDisplay *self = CALLS_CALL_DISPLAY (object); + + switch (property_id) { + case PROP_CALL_DATA: + set_call_data (self, CALLS_CALL_DATA (g_value_get_object (value))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +constructed (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (GTK_TYPE_BOX); + CallsCallDisplay *self = CALLS_CALL_DISPLAY (object); + + self->timer = g_timer_new (); + self->timeout = g_timeout_add (500, (GSourceFunc)timeout_cb, self); + timeout_cb (self); + + call_state_changed_cb (self, calls_call_get_state (self->call)); + + parent_class->constructed (object); +} + +static void +calls_call_display_init (CallsCallDisplay *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (GTK_TYPE_BOX); + CallsCallDisplay *self = CALLS_CALL_DISPLAY (object); + + CALLS_DISPOSE_OBJECT (self->call); + + parent_class->dispose (object); +} + +static void +finalize (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (GTK_TYPE_BOX); + CallsCallDisplay *self = CALLS_CALL_DISPLAY (object); + + g_source_remove (self->timeout); + g_timer_destroy (self->timer); + + parent_class->finalize (object); +} + +static void +calls_call_display_class_init (CallsCallDisplayClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = constructed; + object_class->set_property = set_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + props[PROP_CALL_DATA] = + g_param_spec_object ("call-data", + _("Call data"), + _("Data for the call this display will be associated with"), + CALLS_TYPE_CALL_DATA, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); + + + gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/calls/ui/call-display.ui"); + gtk_widget_class_bind_template_child (widget_class, CallsCallDisplay, party_box); + gtk_widget_class_bind_template_child (widget_class, CallsCallDisplay, name); + gtk_widget_class_bind_template_child (widget_class, CallsCallDisplay, status); + gtk_widget_class_bind_template_child (widget_class, CallsCallDisplay, time); + gtk_widget_class_bind_template_child (widget_class, CallsCallDisplay, answer); + gtk_widget_class_bind_template_child (widget_class, CallsCallDisplay, hold); + gtk_widget_class_bind_template_child (widget_class, CallsCallDisplay, hang_up); + gtk_widget_class_bind_template_child (widget_class, CallsCallDisplay, speaker); + gtk_widget_class_bind_template_callback (widget_class, answer_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, hang_up_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, hold_toggled_cb); + gtk_widget_class_bind_template_callback (widget_class, speaker_toggled_cb); +} diff --git a/src/calls-call-display.h b/src/calls-call-display.h new file mode 100644 index 0000000..6d099f8 --- /dev/null +++ b/src/calls-call-display.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_CALL_DISPLAY_H__ +#define CALLS_CALL_DISPLAY_H__ + +#include + +#include "calls-call-data.h" + +G_BEGIN_DECLS + +#define CALLS_TYPE_CALL_DISPLAY (calls_call_display_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsCallDisplay, calls_call_display, CALLS, CALL_DISPLAY, GtkBox); + +CallsCallDisplay *calls_call_display_new (CallsCallData *data); + +G_END_DECLS + +#endif /* CALLS_CALL_DISPLAY_H__ */ diff --git a/src/calls-call-holder.c b/src/calls-call-holder.c new file mode 100644 index 0000000..2a49dda --- /dev/null +++ b/src/calls-call-holder.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-call-holder.h" +#include "util.h" + +#include +#include + +/** + * SECTION:calls-call-holder + * @short_description: An object to hold both a #CallsCall object and + * data about it and widgets + * @Title: CallsCallHolder + */ + +struct _CallsCallHolder +{ + GObject parent_instance; + + CallsCallData *data; + CallsCallDisplay *display; + CallsCallSelectorItem *selector_item; +}; + +G_DEFINE_TYPE (CallsCallHolder, calls_call_holder, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_CALL, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +CallsCallHolder * +calls_call_holder_new (CallsCall *call) +{ + return g_object_new (CALLS_TYPE_CALL_HOLDER, + "call", call, + NULL); +} + + +CallsCallData * +calls_call_holder_get_data (CallsCallHolder *holder) +{ + g_return_val_if_fail (CALLS_IS_CALL_HOLDER (holder), NULL); + return holder->data; +} + + +CallsCallDisplay * +calls_call_holder_get_display (CallsCallHolder *holder) +{ + g_return_val_if_fail (CALLS_IS_CALL_HOLDER (holder), NULL); + return holder->display; +} + + +CallsCallSelectorItem * +calls_call_holder_get_selector_item (CallsCallHolder *holder) +{ + g_return_val_if_fail (CALLS_IS_CALL_HOLDER (holder), NULL); + return holder->selector_item; +} + + +static void +set_call (CallsCallHolder *self, CallsCall *call) +{ + CallsParty *party + = calls_party_new (NULL, calls_call_get_number (call)); + + self->data = calls_call_data_new (call, party); + g_object_unref (party); + + self->display = calls_call_display_new (self->data); + self->selector_item = + calls_call_selector_item_new (self); +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsCallHolder *self = CALLS_CALL_HOLDER (object); + + switch (property_id) { + case PROP_CALL: + set_call (self, CALLS_CALL (g_value_get_object (value))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +calls_call_holder_init (CallsCallHolder *self) +{ +} + + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsCallHolder *self = CALLS_CALL_HOLDER (object); + + CALLS_DISPOSE_OBJECT (self->selector_item); + CALLS_DISPOSE_OBJECT (self->display); + CALLS_DISPOSE_OBJECT (self->data); + + parent_class->dispose (object); +} + + +static void +calls_call_holder_class_init (CallsCallHolderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = set_property; + object_class->dispose = dispose; + + props[PROP_CALL] = + g_param_spec_object ("call", + _("Call"), + _("The call to hold"), + CALLS_TYPE_CALL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} diff --git a/src/calls-call-holder.h b/src/calls-call-holder.h new file mode 100644 index 0000000..544f2c9 --- /dev/null +++ b/src/calls-call-holder.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_CALL_HOLDER_H__ +#define CALLS_CALL_HOLDER_H__ + +#include "calls-call-data.h" +#include "calls-call-display.h" +#include "calls-call-selector-item.h" + +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_CALL_HOLDER (calls_call_holder_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsCallHolder, calls_call_holder, CALLS, CALL_HOLDER, GObject); + +CallsCallHolder *calls_call_holder_new (CallsCall *call); +CallsCallData *calls_call_holder_get_data (CallsCallHolder *holder); +CallsCallDisplay *calls_call_holder_get_display (CallsCallHolder *holder); +CallsCallSelectorItem *calls_call_holder_get_selector_item (CallsCallHolder *holder); + +G_END_DECLS + +#endif /* CALLS_CALL_HOLDER_H__ */ diff --git a/src/calls-call-selector-item.c b/src/calls-call-selector-item.c new file mode 100644 index 0000000..5b4fa2a --- /dev/null +++ b/src/calls-call-selector-item.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-call-selector-item.h" +#include "calls-call-holder.h" +#include "util.h" + +#include +#include +#include + +#define HANDY_USE_UNSTABLE_API +#include + +struct _CallsCallSelectorItem +{ + GtkEventBox parent_instance; + + CallsCallHolder *holder; + + GtkBox *main_box; + GtkLabel *name; + GtkLabel *status; +}; + +G_DEFINE_TYPE (CallsCallSelectorItem, calls_call_selector_item, GTK_TYPE_EVENT_BOX); + +enum { + PROP_0, + PROP_HOLDER, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +static void +call_state_changed_cb (CallsCallSelectorItem *self, + CallsCallState state) +{ + GString *state_str = g_string_new(""); + calls_call_state_to_string (state_str, state); + gtk_label_set_text (self->status, state_str->str); + g_string_free (state_str, TRUE); +} + + +CallsCallSelectorItem * +calls_call_selector_item_new (CallsCallHolder *holder) +{ + g_return_val_if_fail (CALLS_IS_CALL_HOLDER (holder), NULL); + + return g_object_new (CALLS_TYPE_CALL_SELECTOR_ITEM, + "holder", holder, + NULL); +} + +CallsCallHolder * +calls_call_selector_item_get_holder (CallsCallSelectorItem *item) +{ + g_return_val_if_fail (CALLS_IS_CALL_SELECTOR_ITEM (item), NULL); + return item->holder; +} + + +static void +get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + CallsCallSelectorItem *self = CALLS_CALL_SELECTOR_ITEM (object); + + switch (property_id) { + case PROP_HOLDER: + g_value_set_object (value, self->holder); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +set_call (CallsCallSelectorItem *self, CallsCall *call) +{ + g_signal_connect_object (call, "state-changed", + G_CALLBACK (call_state_changed_cb), + self, + G_CONNECT_SWAPPED); +} + + +static void +set_party (CallsCallSelectorItem *self, CallsParty *party) +{ + GtkWidget *image; + + image = calls_party_create_image (party); + gtk_box_pack_start (self->main_box, image, TRUE, TRUE, 0); + gtk_widget_show (image); + + gtk_label_set_text (self->name, calls_party_get_label (party)); +} + + +static void +set_call_data (CallsCallSelectorItem *self, CallsCallData *data) +{ + CallsCall *call = calls_call_data_get_call (data); + + set_call (self, call); + set_party (self, calls_call_data_get_party (data)); +} + + +static void +set_call_holder (CallsCallSelectorItem *self, CallsCallHolder *holder) +{ + set_call_data (self, calls_call_holder_get_data (holder)); + CALLS_SET_OBJECT_PROPERTY (self->holder, holder); +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsCallSelectorItem *self = CALLS_CALL_SELECTOR_ITEM (object); + + switch (property_id) { + case PROP_HOLDER: + set_call_holder + (self, CALLS_CALL_HOLDER (g_value_get_object (value))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +calls_call_selector_item_init (CallsCallSelectorItem *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +constructed (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (GTK_TYPE_EVENT_BOX); + CallsCallSelectorItem *self = CALLS_CALL_SELECTOR_ITEM (object); + CallsCallData *data = calls_call_holder_get_data (self->holder); + CallsCall *call = calls_call_data_get_call (data); + + call_state_changed_cb (self, calls_call_get_state (call)); + + parent_class->constructed (object); +} + + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (GTK_TYPE_EVENT_BOX); + CallsCallSelectorItem *self = CALLS_CALL_SELECTOR_ITEM (object); + + CALLS_DISPOSE_OBJECT (self->holder); + + parent_class->dispose (object); +} + +static void +calls_call_selector_item_class_init (CallsCallSelectorItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->dispose = dispose; + + props[PROP_HOLDER] = + g_param_spec_object ("holder", + _("Call holder"), + _("The holder for this call"), + CALLS_TYPE_CALL_HOLDER, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); + + + gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/calls/ui/call-selector-item.ui"); + gtk_widget_class_bind_template_child (widget_class, CallsCallSelectorItem, main_box); + gtk_widget_class_bind_template_child (widget_class, CallsCallSelectorItem, name); + gtk_widget_class_bind_template_child (widget_class, CallsCallSelectorItem, status); +} diff --git a/src/calls-call-selector-item.h b/src/calls-call-selector-item.h new file mode 100644 index 0000000..434dc23 --- /dev/null +++ b/src/calls-call-selector-item.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_CALL_SELECTOR_ITEM_H__ +#define CALLS_CALL_SELECTOR_ITEM_H__ + +#include + +typedef struct _CallsCallHolder CallsCallHolder; + +G_BEGIN_DECLS + +#define CALLS_TYPE_CALL_SELECTOR_ITEM (calls_call_selector_item_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsCallSelectorItem, calls_call_selector_item, + CALLS, CALL_SELECTOR_ITEM, GtkEventBox); + +CallsCallSelectorItem *calls_call_selector_item_new (CallsCallHolder *holder); +CallsCallHolder *calls_call_selector_item_get_holder (CallsCallSelectorItem *item); + +G_END_DECLS + +#endif /* CALLS_CALL_SELECTOR_ITEM_H__ */ diff --git a/src/calls-call.c b/src/calls-call.c new file mode 100644 index 0000000..992f84e --- /dev/null +++ b/src/calls-call.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-call.h" +#include "calls-message-source.h" +#include "enum-types.h" +#include "util.h" + + +void +calls_call_state_to_string (GString *string, + CallsCallState state) +{ + GEnumClass *klass; + GEnumValue *value; + + klass = g_type_class_ref (CALLS_TYPE_CALL_STATE); + value = g_enum_get_value (klass, (gint)state); + + g_string_assign (string, value->value_nick); + string->str[0] = g_ascii_toupper (string->str[0]); + + g_type_class_unref (klass); +} + + +gboolean +calls_call_state_parse_nick (CallsCallState *state, + const gchar *nick) +{ + GEnumClass *klass; + GEnumValue *value; + gboolean ret; + + g_return_val_if_fail (state != NULL, FALSE); + g_return_val_if_fail (nick != NULL, FALSE); + + klass = g_type_class_ref (CALLS_TYPE_CALL_STATE); + value = g_enum_get_value_by_nick (klass, nick); + + if (value) + { + *state = (CallsCallState) value->value; + ret = TRUE; + } + else + { + ret = FALSE; + } + + g_type_class_unref (klass); + return ret; +} + + +/** + * SECTION:calls-call + * @short_description: A call. + * @Title: CallsCall + */ + + +G_DEFINE_INTERFACE (CallsCall, calls_call, CALLS_TYPE_MESSAGE_SOURCE); + +enum { + SIGNAL_STATE_CHANGED, + SIGNAL_LAST_SIGNAL, +}; +static guint signals [SIGNAL_LAST_SIGNAL]; + +static void +calls_call_default_init (CallsCallInterface *iface) +{ + GType arg_types = CALLS_TYPE_CALL_STATE; + + signals[SIGNAL_STATE_CHANGED] = + g_signal_newv ("state-changed", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + NULL, NULL, NULL, NULL, + G_TYPE_NONE, + 1, &arg_types); +} + + +#define DEFINE_CALL_FUNC(function,rettype,errval) \ + CALLS_DEFINE_IFACE_FUNC(call, Call, CALL, \ + function, rettype, errval) + +#define DEFINE_CALL_FUNC_VOID(function) \ + CALLS_DEFINE_IFACE_FUNC_VOID(call, Call, CALL, function) + + +DEFINE_CALL_FUNC(get_number, const gchar *, NULL); +DEFINE_CALL_FUNC(get_name, const gchar *, NULL); + +/** + * calls_call_get_state: + * @self: a #CallsCall + * + * Get the current state of the call + * + * Returns: the state + */ +DEFINE_CALL_FUNC(get_state, CallsCallState, ((CallsCallState)0)); + +DEFINE_CALL_FUNC_VOID(answer); +DEFINE_CALL_FUNC_VOID(hang_up); + + +static inline gboolean +tone_key_is_valid (gchar key) +{ + return + (key >= '0' && key <= '9') + || (key >= 'A' && key <= 'D') + || key == '*' + || key == '#'; +} + +#define DEFINE_CALL_TONE_FUNC(which) \ + void \ + calls_call_tone_##which (CallsCall *self, \ + gchar key) \ + { \ + CallsCallInterface *iface; \ + \ + g_return_if_fail (CALLS_IS_CALL (self)); \ + g_return_if_fail (tone_key_is_valid (key)); \ + \ + iface = CALLS_CALL_GET_IFACE (self); \ + g_return_if_fail (iface->tone_##which != NULL); \ + \ + return iface->tone_##which (self, key); \ + } + +DEFINE_CALL_TONE_FUNC (start); +DEFINE_CALL_TONE_FUNC (stop); diff --git a/src/calls-call.h b/src/calls-call.h new file mode 100644 index 0000000..d18a3e2 --- /dev/null +++ b/src/calls-call.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_CALL_H__ +#define CALLS_CALL_H__ + +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_CALL (calls_call_get_type ()) + +G_DECLARE_INTERFACE (CallsCall, calls_call, CALLS, CALL, GObject); + +typedef enum +{ + CALLS_CALL_STATE_ACTIVE, + CALLS_CALL_STATE_HELD, + CALLS_CALL_STATE_DIALING, + CALLS_CALL_STATE_ALERTING, + CALLS_CALL_STATE_INCOMING, + CALLS_CALL_STATE_WAITING, + CALLS_CALL_STATE_DISCONNECTED +} CallsCallState; + +void calls_call_state_to_string (GString *string, + CallsCallState state); +gboolean calls_call_state_parse_nick (CallsCallState *state, + const gchar *nick); + +struct _CallsCallInterface +{ + GTypeInterface parent_iface; + + const gchar * (*get_number) (CallsCall *self); + const gchar * (*get_name) (CallsCall *self); + CallsCallState (*get_state) (CallsCall *self); + void (*answer) (CallsCall *self); + void (*hang_up) (CallsCall *self); + void (*tone_start) (CallsCall *self, gchar key); + void (*tone_stop) (CallsCall *self, gchar key); +}; + + +const gchar * calls_call_get_number (CallsCall *self); +const gchar * calls_call_get_name (CallsCall *self); +CallsCallState calls_call_get_state (CallsCall *self); +void calls_call_answer (CallsCall *self); +void calls_call_hang_up (CallsCall *self); +void calls_call_tone_start (CallsCall *self, gchar key); +void calls_call_tone_stop (CallsCall *self, gchar key); + +G_END_DECLS + +#endif /* CALLS_CALL_H__ */ diff --git a/src/calls-dummy-call.c b/src/calls-dummy-call.c new file mode 100644 index 0000000..daf6312 --- /dev/null +++ b/src/calls-dummy-call.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-dummy-call.h" +#include "calls-call.h" + +#include + + +struct _CallsDummyCall +{ + GObject parent_instance; + gchar *number; + CallsCallState state; +}; + +static void calls_dummy_call_call_interface_init (CallsCallInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (CallsDummyCall, calls_dummy_call, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CALLS_TYPE_CALL, + calls_dummy_call_call_interface_init)) + +enum { + PROP_0, + PROP_NUMBER, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + +static const gchar * +get_number (CallsCall *iface) +{ + CallsDummyCall *self; + + g_return_val_if_fail (CALLS_IS_DUMMY_CALL (iface), NULL); + self = CALLS_DUMMY_CALL (iface); + + return self->number; +} + +static const gchar * +get_name (CallsCall *iface) +{ + return NULL; +} + +static CallsCallState +get_state (CallsCall *call) +{ + CallsDummyCall *self; + + g_return_val_if_fail (CALLS_IS_DUMMY_CALL (call), 0); + self = CALLS_DUMMY_CALL (call); + + return self->state; +} + +static void +change_state (CallsCall *call, + CallsDummyCall *self, + CallsCallState state) +{ + self->state = state; + g_signal_emit_by_name (call, "state-changed", state); +} + +static void +answer (CallsCall *call) +{ + CallsDummyCall *self; + + g_return_if_fail (CALLS_IS_DUMMY_CALL (call)); + self = CALLS_DUMMY_CALL (call); + + g_return_if_fail (self->state == CALLS_CALL_STATE_INCOMING); + + change_state (call, self, CALLS_CALL_STATE_ACTIVE); +} + +static void +hang_up (CallsCall *call) +{ + CallsDummyCall *self; + + g_return_if_fail (CALLS_IS_DUMMY_CALL (call)); + self = CALLS_DUMMY_CALL (call); + + change_state (call, self, CALLS_CALL_STATE_DISCONNECTED); +} + +static void +tone_start (CallsCall *call, gchar key) +{ + g_info ("Beep! (%c)", (int)key); +} + +static void +tone_stop (CallsCall *call, gchar key) +{ + g_info ("Beep end (%c)", (int)key); +} + +CallsDummyCall * +calls_dummy_call_new (const gchar *number) +{ + g_return_val_if_fail (number != NULL, NULL); + + return g_object_new (CALLS_TYPE_DUMMY_CALL, + "number", number, + NULL); +} + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsDummyCall *self = CALLS_DUMMY_CALL (object); + + switch (property_id) { + case PROP_NUMBER: + self->number = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +finalize (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsDummyCall *self = CALLS_DUMMY_CALL (object); + + g_free (self->number); + + parent_class->finalize (object); +} + + +static void +calls_dummy_call_class_init (CallsDummyCallClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = finalize; + object_class->set_property = set_property; + + props[PROP_NUMBER] = + g_param_spec_string ("number", + _("Number"), + _("The dialed number"), + "+441234567890", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + +static void +calls_dummy_call_call_interface_init (CallsCallInterface *iface) +{ + iface->get_number = get_number; + iface->get_name = get_name; + iface->get_state = get_state; + iface->answer = answer; + iface->hang_up = hang_up; + iface->tone_start = tone_start; + iface->tone_stop = tone_stop; +} + +static void +calls_dummy_call_init (CallsDummyCall *self) +{ +} diff --git a/src/calls-dummy-call.h b/src/calls-dummy-call.h new file mode 100644 index 0000000..8330fb5 --- /dev/null +++ b/src/calls-dummy-call.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_DUMMY_CALL_H__ +#define CALLS_DUMMY_CALL_H__ + +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_DUMMY_CALL (calls_dummy_call_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsDummyCall, calls_dummy_call, CALLS, DUMMY_CALL, GObject); + +CallsDummyCall *calls_dummy_call_new (const gchar *number); + +G_END_DECLS + +#endif /* CALLS_DUMMY_CALL_H__ */ diff --git a/src/calls-dummy-origin.c b/src/calls-dummy-origin.c new file mode 100644 index 0000000..b889a9a --- /dev/null +++ b/src/calls-dummy-origin.c @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-dummy-origin.h" +#include "calls-origin.h" +#include "calls-dummy-call.h" + +#include +#include + + +struct _CallsDummyOrigin +{ + GObject parent_instance; + GString *name; + GList *calls; +}; + +static void calls_dummy_origin_origin_interface_init (CallsOriginInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (CallsDummyOrigin, calls_dummy_origin, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CALLS_TYPE_ORIGIN, + calls_dummy_origin_origin_interface_init)) + +enum { + PROP_0, + PROP_NAME, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +static const gchar * +get_name (CallsOrigin *origin) +{ + CallsDummyOrigin *self; + + g_return_val_if_fail (CALLS_IS_DUMMY_ORIGIN (origin), NULL); + self = CALLS_DUMMY_ORIGIN (origin); + + return self->name->str; +} + + +static GList * +get_calls (CallsOrigin *origin) +{ + CallsDummyOrigin *self; + + g_return_val_if_fail (CALLS_IS_DUMMY_ORIGIN (origin), NULL); + self = CALLS_DUMMY_ORIGIN (origin); + + return g_list_copy (self->calls); +} + + +static void +remove_call (CallsDummyOrigin *self, + CallsCall *call, + const gchar *reason) +{ + CallsOrigin *origin; + + origin = CALLS_ORIGIN (self); + g_signal_emit_by_name (origin, "call-removed", call, reason); + + self->calls = g_list_remove (self->calls, call); + g_object_unref (G_OBJECT (call)); +} + + +static void +remove_calls (CallsDummyOrigin *self, const gchar *reason) +{ + GList *node, *next; + + for (node = self->calls; node; node = next) + { + next = node->next; + + remove_call (self, CALLS_CALL (node->data), reason); + } +} + + +static void +call_state_changed_cb (CallsDummyOrigin *self, + CallsCallState state, + CallsCall *call) +{ + if (state != CALLS_CALL_STATE_DISCONNECTED) + { + return; + } + + g_return_if_fail (CALLS_IS_DUMMY_ORIGIN (self)); + g_return_if_fail (CALLS_IS_CALL (call)); + + remove_call (self, call, "Disconnected"); +} + + +static void +dial (CallsOrigin *origin, const gchar *number) +{ + CallsDummyOrigin *self; + CallsDummyCall *dummy_call; + CallsCall *call; + + g_return_if_fail (number != NULL); + g_return_if_fail (CALLS_IS_DUMMY_ORIGIN (origin)); + + self = CALLS_DUMMY_ORIGIN (origin); + + dummy_call = calls_dummy_call_new (number); + g_return_if_fail (dummy_call != NULL); + + call = CALLS_CALL (dummy_call); + g_signal_connect_swapped (call, "state-changed", + G_CALLBACK (call_state_changed_cb), + self); + + self->calls = g_list_append (self->calls, dummy_call); + + g_signal_emit_by_name (origin, "call-added", call); +} + + +CallsDummyOrigin * +calls_dummy_origin_new (const gchar *name) +{ + /* + guint n_properties, i; + const gchar *names[1] = { NULL }; + GValue values[1] = { G_VALUE_INIT }; + GObject *object; + + if (name == NULL) + { + n_properties = 0; + } + else + { + n_properties = 1; + names[0] = "name"; + g_value_init (&values[0], G_TYPE_STRING); + g_value_set_string (&values[0], name); + } + + object = g_object_new_with_properties (CALLS_TYPE_DUMMY_ORIGIN, + n_properties, names, values); + + for (i = 0; i < n_properties; ++i) + { + g_value_unset (&values[i]); + } + + return CALLS_DUMMY_ORIGIN(object); + */ + // FIXME + return NULL; +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsDummyOrigin *self = CALLS_DUMMY_ORIGIN (object); + + switch (property_id) { + case PROP_NAME: + g_string_assign (self->name, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsDummyOrigin *self = CALLS_DUMMY_ORIGIN (object); + + remove_calls (self, NULL); + + parent_class->dispose (object); +} + + +static void +finalize (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsDummyOrigin *self = CALLS_DUMMY_ORIGIN (object); + + g_string_free (self->name, TRUE); + + parent_class->finalize (object); +} + + +static void +calls_dummy_origin_class_init (CallsDummyOriginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = dispose; + object_class->finalize = finalize; + object_class->set_property = set_property; + + props[PROP_NAME] = + g_param_spec_string ("name", + _("Name"), + _("The name of the origin"), + "Dummy origin", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + + +static void +calls_dummy_origin_origin_interface_init (CallsOriginInterface *iface) +{ + iface->get_name = get_name; + iface->get_calls = get_calls; + iface->dial = dial; +} + + +static void +calls_dummy_origin_init (CallsDummyOrigin *self) +{ + self->name = g_string_new (NULL); +} diff --git a/src/calls-dummy-origin.h b/src/calls-dummy-origin.h new file mode 100644 index 0000000..eb91618 --- /dev/null +++ b/src/calls-dummy-origin.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_DUMMY_ORIGIN_H__ +#define CALLS_DUMMY_ORIGIN_H__ + +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_DUMMY_ORIGIN (calls_dummy_origin_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsDummyOrigin, calls_dummy_origin, CALLS, DUMMY_ORIGIN, GObject); + +CallsDummyOrigin *calls_dummy_origin_new (const gchar *name); + +G_END_DECLS + +#endif /* CALLS_DUMMY_ORIGIN_H__ */ diff --git a/src/calls-dummy-provider.c b/src/calls-dummy-provider.c new file mode 100644 index 0000000..7fb2ded --- /dev/null +++ b/src/calls-dummy-provider.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-dummy-provider.h" +#include "calls-provider.h" +#include "calls-dummy-origin.h" + +struct _CallsDummyProvider +{ + GObject parent_instance; + + GList *origins; +}; + +static void calls_dummy_provider_provider_interface_init (CallsProviderInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (CallsDummyProvider, calls_dummy_provider, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CALLS_TYPE_PROVIDER, + calls_dummy_provider_provider_interface_init)) + +static const gchar * +get_name (CallsProvider *iface) +{ + return "Dummy provider"; +} + +static GList * +get_origins (CallsProvider *iface) +{ + CallsDummyProvider *self = CALLS_DUMMY_PROVIDER (iface); + return self->origins; +} + +CallsDummyProvider * +calls_dummy_provider_new () +{ + return g_object_new (CALLS_TYPE_DUMMY_PROVIDER, NULL); +} + + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsDummyProvider *self = CALLS_DUMMY_PROVIDER (object); + + g_list_free_full (self->origins, g_object_unref); + self->origins = NULL; + + parent_class->dispose (object); +} + +static void +calls_dummy_provider_class_init (CallsDummyProviderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = dispose; +} + +static void +calls_dummy_provider_provider_interface_init (CallsProviderInterface *iface) +{ + iface->get_name = get_name; + iface->get_origins = get_origins; +} + +static void +calls_dummy_provider_init (CallsDummyProvider *self) +{ + self->origins = g_list_append (self->origins, + calls_dummy_origin_new (NULL)); + self->origins = g_list_append (self->origins, + calls_dummy_origin_new ("Dummy origin 2")); +} diff --git a/src/calls-dummy-provider.h b/src/calls-dummy-provider.h new file mode 100644 index 0000000..f7b3071 --- /dev/null +++ b/src/calls-dummy-provider.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_DUMMY_PROVIDER_H__ +#define CALLS_DUMMY_PROVIDER_H__ + +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_DUMMY_PROVIDER (calls_dummy_provider_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsDummyProvider, calls_dummy_provider, CALLS, DUMMY_PROVIDER, GObject); + +CallsDummyProvider *calls_dummy_provider_new (); + +G_END_DECLS + +#endif /* CALLS_DUMMY_PROVIDER_H__ */ diff --git a/src/calls-main-window.c b/src/calls-main-window.c new file mode 100644 index 0000000..c83eb3f --- /dev/null +++ b/src/calls-main-window.c @@ -0,0 +1,741 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-main-window.h" +#include "calls-origin.h" +#include "calls-call-holder.h" +#include "calls-call-selector-item.h" +#include "util.h" + +#include +#include + +#define HANDY_USE_UNSTABLE_API +#include + + +struct _CallsMainWindow +{ + GtkApplicationWindow parent_instance; + + CallsProvider *provider; + GListStore *call_holders; + CallsCallHolder *focus; + + GtkInfoBar *info; + GtkLabel *info_label; + + GtkStack *main_stack; + GtkButton *back; + GtkStack *call_stack; + GtkScrolledWindow *call_scroll; + GtkFlowBox *call_selector; + GtkBox *dial_box; + GtkExpander *new_call; + GtkBox *dial_controls; + GtkComboBox *origin; + GtkSearchEntry *search; + HdyDialer *dial_pad; + + GtkListStore *origin_store; + GtkListStore *history_store; +}; + +enum { + ORIGIN_STORE_COLUMN_NAME, + ORIGIN_STORE_COLUMN_ORIGIN +}; + +G_DEFINE_TYPE (CallsMainWindow, calls_main_window, GTK_TYPE_APPLICATION_WINDOW); + +enum { + PROP_0, + PROP_PROVIDER, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +CallsMainWindow * +calls_main_window_new (GtkApplication *application, CallsProvider *provider) +{ + return g_object_new (CALLS_TYPE_MAIN_WINDOW, + "application", application, + "provider", provider, + NULL); +} + + +static void +show_message (CallsMainWindow *self, const gchar *text, GtkMessageType type) +{ + gtk_info_bar_set_message_type (self->info, type); + gtk_label_set_text (self->info_label, text); + gtk_widget_show (GTK_WIDGET (self->info)); + gtk_widget_queue_allocate (GTK_WIDGET (self)); +} + + +static void +info_response_cb (GtkInfoBar *infobar, + gint response_id, + CallsMainWindow *self) +{ + gtk_widget_hide (GTK_WIDGET (self->info)); + gtk_widget_queue_allocate (GTK_WIDGET (self)); +} + + +static GtkWidget * +call_holders_create_widget_cb (CallsCallHolder *holder, + CallsMainWindow *self) +{ + return GTK_WIDGET (calls_call_holder_get_selector_item (holder)); +} + +static void +search_append_symbol (CallsMainWindow *self, gchar symbol) +{ + GtkEntryBuffer *buf = gtk_entry_get_buffer (GTK_ENTRY (self->search)); + guint len = gtk_entry_buffer_get_length (buf); + + gtk_entry_buffer_insert_text (buf, len, &symbol, 1); +} + +static void +dial_pad_symbol_clicked_cb (CallsMainWindow *self, gchar symbol, HdyDialer *dialer) +{ + if (self->focus && !gtk_expander_get_expanded (self->new_call)) + { + CallsCallData *data = calls_call_holder_get_data (self->focus); + CallsCall *call = calls_call_data_get_call (data); + + calls_call_tone_start (call, symbol); + } + else + { + search_append_symbol (self, symbol); + } +} + + +static void +dial_pad_deleted_cb (CallsMainWindow *self, HdyDialer *dialer) +{ + GtkEntryBuffer *buf = gtk_entry_get_buffer (GTK_ENTRY (self->search)); + guint len = gtk_entry_buffer_get_length (buf); + + gtk_entry_buffer_delete_text (buf, len - 1, 1); +} + + +static void +dial_pad_submitted_cb (CallsMainWindow *self, const gchar *unused, HdyDialer *dialer) +{ + GtkTreeIter iter; + gboolean ok; + CallsOrigin *origin; + const gchar *number; + + g_return_if_fail (CALLS_IS_MAIN_WINDOW (self)); + + if (gtk_widget_get_visible (GTK_WIDGET (self->new_call)) + && !gtk_expander_get_expanded (self->new_call)) + { + return; + } + + ok = gtk_combo_box_get_active_iter (self->origin, &iter); + g_return_if_fail (ok); + + gtk_tree_model_get (GTK_TREE_MODEL (self->origin_store), &iter, + ORIGIN_STORE_COLUMN_ORIGIN, &origin, + -1); + g_return_if_fail (CALLS_IS_ORIGIN (origin)); + + number = gtk_entry_get_text (GTK_ENTRY (self->search)); + + calls_origin_dial (origin, number); +} + + +typedef gboolean (*FindCallHolderFunc) (CallsCallHolder *holder, + gpointer user_data); + + +static gboolean +find_call_holder_by_call (CallsCallHolder *holder, + gpointer user_data) +{ + CallsCallData *data = calls_call_holder_get_data (holder); + + return calls_call_data_get_call (data) == user_data; +} + + +/** Search through the list of call holders, returning the total + number of items in the list, the position of the holder within the + list and a pointer to the holder itself. */ +static gboolean +find_call_holder (CallsMainWindow *self, + guint *n_itemsp, + guint *positionp, + CallsCallHolder **holderp, + FindCallHolderFunc predicate, + gpointer user_data) +{ + GListModel * const model = G_LIST_MODEL (self->call_holders); + const guint n_items = g_list_model_get_n_items (model); + guint position = 0; + CallsCallHolder *holder; + + for (position = 0; position < n_items; ++position) + { + holder = CALLS_CALL_HOLDER (g_list_model_get_item (model, position)); + + if (predicate (holder, user_data)) + { +#define out(var) \ + if (var##p) \ + { \ + *var##p = var ; \ + } + + out (n_items); + out (position); + out (holder); + +#undef out + + return TRUE; + } + } + + return FALSE; +} + + +static void +set_focus (CallsMainWindow *self, CallsCallHolder *holder) +{ + if (!holder) + { + holder = g_list_model_get_item (G_LIST_MODEL (self->call_holders), 0); + + if (!holder) + { + /* No calls */ + self->focus = NULL; + return; + } + } + + self->focus = holder; + + gtk_stack_set_visible_child + (self->call_stack, + GTK_WIDGET (calls_call_holder_get_display (holder))); +} + + +/* When we have an active call, we hide the dialpad action buttons and + * put the dial_controls inside the new_call expander and use the + * expanded state to determine whether key presses should be + * considered dialing a new call or entering DTMF tones for the active + * call. + */ +static void +show_new_call (CallsMainWindow *self) +{ + GtkWidget *dial_controls_widget = GTK_WIDGET (self->dial_controls); + GObject *dial_controls_object = G_OBJECT (dial_controls_widget); + + hdy_dialer_set_show_action_buttons (self->dial_pad, FALSE); + + g_object_ref (dial_controls_object); + gtk_container_remove (GTK_CONTAINER (self->dial_box), dial_controls_widget); + + gtk_container_add (GTK_CONTAINER (self->new_call), dial_controls_widget); + g_object_unref (dial_controls_object); + + gtk_expander_set_expanded (self->new_call, FALSE); + gtk_widget_show (GTK_WIDGET (self->new_call)); + + gtk_widget_queue_allocate (GTK_WIDGET (self)); +} + + +static void +hide_new_call (CallsMainWindow *self) +{ + GtkWidget *dial_controls_widget = GTK_WIDGET (self->dial_controls); + GObject *dial_controls_object = G_OBJECT (dial_controls_widget); + + gtk_widget_hide (GTK_WIDGET (self->new_call)); + + g_object_ref (dial_controls_object); + gtk_container_remove (GTK_CONTAINER (self->new_call), dial_controls_widget); + + gtk_box_pack_start (self->dial_box, dial_controls_widget, FALSE, TRUE, 0); + g_object_unref (dial_controls_object); + + gtk_box_reorder_child (self->dial_box, dial_controls_widget, 0); + + hdy_dialer_set_show_action_buttons (self->dial_pad, TRUE); + + gtk_widget_queue_allocate (GTK_WIDGET (self)); +} + + +static void +new_call_expanded_notify_cb (GtkExpander *new_call, + GParamSpec *param_spec, + CallsMainWindow *self) +{ + hdy_dialer_set_show_action_buttons (self->dial_pad, + gtk_expander_get_expanded (new_call)); +} + + +static void +back_clicked_cb (GtkButton *back, + CallsMainWindow *self) +{ + gtk_stack_set_visible_child (self->call_stack, GTK_WIDGET (self->call_scroll)); + gtk_stack_set_visible_child (self->main_stack, GTK_WIDGET (self->call_stack)); +} + + +static void +call_selector_child_activated_cb (GtkFlowBox *box, + GtkFlowBoxChild *child, + CallsMainWindow *self) +{ + GtkWidget *widget = gtk_bin_get_child (GTK_BIN (child)); + CallsCallSelectorItem *item = CALLS_CALL_SELECTOR_ITEM (widget); + CallsCallHolder *holder = calls_call_selector_item_get_holder (item); + + set_focus (self, holder); +} + + +/** Possibly show various call widgets */ +static void +show_calls (CallsMainWindow *self, guint old_call_count) +{ + if (old_call_count == 0) + { + gtk_stack_add_titled (self->main_stack, + GTK_WIDGET (self->call_stack), + "call", "Call"); + show_new_call (self); + } + + if (old_call_count > 0) + { + gtk_widget_show (GTK_WIDGET (self->back)); + } +} + + +static void +hide_calls (CallsMainWindow *self, guint call_count) +{ + if (call_count == 0) + { + hide_new_call (self); + gtk_container_remove (GTK_CONTAINER (self->main_stack), + GTK_WIDGET (self->call_stack)); + } + + if (call_count <= 1) + { + gtk_widget_hide (GTK_WIDGET (self->back)); + } +} + + +static void +add_call (CallsMainWindow *self, CallsCall *call) +{ + CallsCallHolder *holder; + CallsCallDisplay *display; + + g_signal_connect_swapped (call, "message", + G_CALLBACK (show_message), self); + + show_calls (self, g_list_model_get_n_items (G_LIST_MODEL (self->call_holders))); + + holder = calls_call_holder_new (call); + + display = calls_call_holder_get_display (holder); + gtk_stack_add_named (self->call_stack, GTK_WIDGET (display), + calls_call_get_number (call)); + gtk_stack_set_visible_child (self->main_stack, GTK_WIDGET (self->call_stack)); + + g_list_store_append (self->call_holders, holder); + + set_focus (self, holder); + +} + + +static void +remove_call_holder (CallsMainWindow *self, + guint n_items, + guint position, + CallsCallHolder *holder) +{ + g_list_store_remove (self->call_holders, position); + gtk_container_remove (GTK_CONTAINER (self->call_stack), + GTK_WIDGET (calls_call_holder_get_display (holder))); + + if (self->focus == holder) + { + set_focus (self, NULL); + } + + hide_calls (self, n_items - 1); +} + +static void +remove_call (CallsMainWindow *self, CallsCall *call, const gchar *reason) +{ + guint n_items, position; + CallsCallHolder *holder; + gboolean found; + + g_return_if_fail (CALLS_IS_MAIN_WINDOW (self)); + g_return_if_fail (CALLS_IS_CALL (call)); + + found = find_call_holder (self, &n_items, &position, &holder, + find_call_holder_by_call, call); + g_return_if_fail (found); + + remove_call_holder (self, n_items, position, holder); + + if (!reason) + { + reason = "Call ended for unknown reason"; + } + show_message(self, reason, GTK_MESSAGE_INFO); +} + + +static void +remove_calls (CallsMainWindow *self) +{ + GListModel * model = G_LIST_MODEL (self->call_holders); + guint n_items = g_list_model_get_n_items (model); + gpointer *item; + CallsCallHolder *holder; + + while ( (item = g_list_model_get_item (model, 0)) ) + { + holder = CALLS_CALL_HOLDER (item); + remove_call_holder (self, n_items--, 0, holder); + } +} + + +static void +add_origin_calls (CallsMainWindow *self, CallsOrigin *origin) +{ + GList *calls, *node; + + calls = calls_origin_get_calls (origin); + + for (node = calls; node; node = node->next) + { + add_call (self, CALLS_CALL (node->data)); + } + + g_list_free (calls); +} + + +static void +add_origin (CallsMainWindow *self, CallsOrigin *origin) +{ + const gint n_origins = gtk_tree_model_iter_n_children + (GTK_TREE_MODEL (self->origin_store), NULL); + GtkTreeIter iter; + + if (n_origins == 1) + { + /* We have more than one origin now so show the origin combo box */ + gtk_widget_show (GTK_WIDGET (self->origin)); + } + + gtk_list_store_append (self->origin_store, &iter); + gtk_list_store_set (self->origin_store, &iter, + ORIGIN_STORE_COLUMN_NAME, calls_origin_get_name(origin), + ORIGIN_STORE_COLUMN_ORIGIN, G_OBJECT (origin), + -1); + + if (gtk_combo_box_get_active (self->origin) == -1) + { + /* We always want an item active */ + gtk_combo_box_set_active (self->origin, 0); + } + + + g_signal_connect_swapped (origin, "message", + G_CALLBACK (show_message), self); + g_signal_connect_swapped (origin, "call-added", + G_CALLBACK (add_call), self); + g_signal_connect_swapped (origin, "call-removed", + G_CALLBACK (remove_call), self); + + add_origin_calls(self, origin); +} + + +static void +dump_list_store (GtkListStore *store) +{ + GtkTreeIter iter; + GtkTreeModel *model = GTK_TREE_MODEL (store); + gboolean ok; + + ok = gtk_tree_model_get_iter_first (model, &iter); + if (!ok) + { + return; + } + + g_debug ("List store:"); + do + { + gchararray name; + gtk_tree_model_get (model, &iter, + ORIGIN_STORE_COLUMN_NAME, &name, + -1); + g_debug (" name: `%s'", name); + } + while (gtk_tree_model_iter_next (model, &iter)); +} + + +static void +update_origin (CallsMainWindow *self) +{ + if (gtk_tree_model_iter_n_children + (GTK_TREE_MODEL (self->origin_store), NULL) < 2) + { + /* User has only one choice so hide the origin combo box */ + gtk_widget_hide (GTK_WIDGET (self->origin)); + } +} + +static void +remove_origin (CallsMainWindow *self, CallsOrigin *origin) +{ + GtkTreeIter iter; + gboolean ok; + + ok = calls_list_store_find (self->origin_store, origin, + ORIGIN_STORE_COLUMN_ORIGIN, &iter); + g_return_if_fail (ok); + + gtk_list_store_remove (self->origin_store, &iter); + + update_origin (self); +} + + +static void +remove_origins (CallsMainWindow *self) +{ + GtkTreeModel *model = GTK_TREE_MODEL (self->origin_store); + GtkTreeIter iter; + + while (gtk_tree_model_get_iter_first (model, &iter)) + { + gtk_list_store_remove (self->origin_store, &iter); + } + + update_origin (self); +} + + +static void +add_provider_origins (CallsMainWindow *self, CallsProvider *provider) +{ + GList *origins, *node; + + origins = calls_provider_get_origins (provider); + + for (node = origins; node; node = node->next) + { + add_origin (self, CALLS_ORIGIN (node->data)); + } + + g_list_free (origins); + + dump_list_store (self->origin_store); +} + + +static void +set_provider (CallsMainWindow *self, CallsProvider *provider) +{ + g_signal_connect_swapped (provider, "message", + G_CALLBACK (show_message), self); + g_signal_connect_swapped (provider, "origin-added", + G_CALLBACK (add_origin), self); + g_signal_connect_swapped (provider, "origin-removed", + G_CALLBACK (remove_origin), self); + + self->provider = provider; + g_object_ref (G_OBJECT (provider)); + + add_provider_origins (self, provider); +} + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsMainWindow *self = CALLS_MAIN_WINDOW (object); + GObject *val_obj; + + switch (property_id) { + case PROP_PROVIDER: + val_obj = g_value_get_object (value); + if (val_obj == NULL) + { + g_warning("Null provider"); + self->provider = NULL; + } + else + { + g_return_if_fail (CALLS_IS_PROVIDER (val_obj)); + set_provider (self, CALLS_PROVIDER (val_obj)); + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +constructed (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (GTK_TYPE_APPLICATION_WINDOW); + CallsMainWindow *self = CALLS_MAIN_WINDOW (object); + + gtk_container_remove (GTK_CONTAINER (self->main_stack), + GTK_WIDGET (self->call_stack)); + + gtk_flow_box_bind_model (self->call_selector, + G_LIST_MODEL (self->call_holders), + (GtkFlowBoxCreateWidgetFunc) call_holders_create_widget_cb, + NULL, NULL); + + parent_class->constructed (object); +} + + +static void +calls_main_window_init (CallsMainWindow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + self->call_holders = g_list_store_new (CALLS_TYPE_CALL_HOLDER); +} + + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (GTK_TYPE_APPLICATION_WINDOW); + CallsMainWindow *self = CALLS_MAIN_WINDOW (object); + + if (self->call_holders) + { + remove_calls (self); + } + + if (self->origin_store) + { + remove_origins (self); + } + + CALLS_DISPOSE_OBJECT (self->call_holders); + CALLS_DISPOSE_OBJECT (self->provider); + + parent_class->dispose (object); +} + + +static void +calls_main_window_class_init (CallsMainWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->dispose = dispose; + + props[PROP_PROVIDER] = + g_param_spec_object ("provider", + _("Provider"), + _("An object implementing low-level call-making functionality"), + CALLS_TYPE_PROVIDER, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); + + + gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/calls/ui/main-window.ui"); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, info); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, info_label); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, main_stack); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, back); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, call_stack); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, call_scroll); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, call_selector); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, dial_box); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, new_call); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, dial_controls); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, origin); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, search); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, dial_pad); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, origin_store); + gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, history_store); + gtk_widget_class_bind_template_callback (widget_class, info_response_cb); + gtk_widget_class_bind_template_callback (widget_class, new_call_expanded_notify_cb); + gtk_widget_class_bind_template_callback (widget_class, call_selector_child_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, back_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, dial_pad_submitted_cb); + gtk_widget_class_bind_template_callback (widget_class, dial_pad_deleted_cb); + gtk_widget_class_bind_template_callback (widget_class, dial_pad_symbol_clicked_cb); +} diff --git a/src/calls-main-window.h b/src/calls-main-window.h new file mode 100644 index 0000000..bb707b6 --- /dev/null +++ b/src/calls-main-window.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_MAIN_WINDOW_H__ +#define CALLS_MAIN_WINDOW_H__ + +#include + +#include "calls-provider.h" + +G_BEGIN_DECLS + +#define CALLS_TYPE_MAIN_WINDOW (calls_main_window_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsMainWindow, calls_main_window, CALLS, MAIN_WINDOW, GtkApplicationWindow); + +CallsMainWindow *calls_main_window_new (GtkApplication *application, + CallsProvider *provider); + +G_END_DECLS + +#endif /* CALLS_MAIN_WINDOW_H__ */ diff --git a/src/calls-message-source.c b/src/calls-message-source.c new file mode 100644 index 0000000..c0906f8 --- /dev/null +++ b/src/calls-message-source.c @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-message-source.h" + + +G_DEFINE_INTERFACE (CallsMessageSource, calls_message_source, G_TYPE_OBJECT); + +enum { + SIGNAL_MESSAGE, + SIGNAL_LAST_SIGNAL, +}; +static guint signals [SIGNAL_LAST_SIGNAL]; + + +static void +calls_message_source_default_init (CallsMessageSourceInterface *iface) +{ + signals[SIGNAL_MESSAGE] = + g_signal_newv ("message", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + NULL, NULL, NULL, NULL, + G_TYPE_NONE, + 2, calls_message_signal_arg_types()); +} + + +GType * +calls_message_signal_arg_types() +{ + static gsize initialization_value = 0; + static GType arg_types[2]; + + if (g_once_init_enter (&initialization_value)) + { + arg_types[0] = G_TYPE_STRING; + arg_types[1] = GTK_TYPE_MESSAGE_TYPE; + + g_once_init_leave (&initialization_value, 1); + } + + return arg_types; +} diff --git a/src/calls-message-source.h b/src/calls-message-source.h new file mode 100644 index 0000000..a97fa84 --- /dev/null +++ b/src/calls-message-source.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_MESSAGE_SOURCE_H__ +#define CALLS_MESSAGE_SOURCE_H__ + +#include "util.h" + +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_MESSAGE_SOURCE (calls_message_source_get_type ()) + +G_DECLARE_INTERFACE (CallsMessageSource, calls_message_source, CALLS, MESSAGE_SOURCE, GObject); + +struct _CallsMessageSourceInterface +{ + GTypeInterface parent_iface; +}; + +#define CALLS_ERROR(obj,error) \ + CALLS_EMIT_ERROR (CALLS_MESSAGE_SOURCE (obj), error) + +/** Array of GTypes for message signals */ +GType * calls_message_signal_arg_types(); + +G_END_DECLS + +#endif /* CALLS_MESSAGE_SOURCE_H__ */ diff --git a/src/calls-ofono-call.c b/src/calls-ofono-call.c new file mode 100644 index 0000000..201c8a3 --- /dev/null +++ b/src/calls-ofono-call.c @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-ofono-call.h" +#include "calls-call.h" +#include "calls-message-source.h" +#include "util.h" + +#include + + +struct _CallsOfonoCall +{ + GObject parent_instance; + GDBOVoiceCall *voice_call; + gchar *number; + gchar *name; + CallsCallState state; + gchar *disconnect_reason; +}; + +static void calls_ofono_call_message_source_interface_init (CallsCallInterface *iface); +static void calls_ofono_call_call_interface_init (CallsCallInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (CallsOfonoCall, calls_ofono_call, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE, + calls_ofono_call_message_source_interface_init) + G_IMPLEMENT_INTERFACE (CALLS_TYPE_CALL, + calls_ofono_call_call_interface_init)) + +enum { + PROP_0, + PROP_VOICE_CALL, + PROP_PROPERTIES, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +#define DEFINE_GET_BODY(member) \ + get_##member (CallsCall *iface) \ + { \ + CallsOfonoCall *self = CALLS_OFONO_CALL (iface); \ + return self-> member ; \ + } + +static const gchar * +DEFINE_GET_BODY(number); + +static const gchar * +DEFINE_GET_BODY(name); + +static CallsCallState +DEFINE_GET_BODY(state); + +#undef DEFINE_GET_BODY + + +static void +change_state (CallsOfonoCall *self, + CallsCallState state) +{ + self->state = state; + g_signal_emit_by_name (CALLS_CALL (self), + "state-changed", state); +} + + +struct CallsCallOperationData +{ + const gchar *desc; + CallsOfonoCall *self; + gboolean (*finish_func) (GDBOVoiceCall *, GAsyncResult *, GError **); +}; + + +static void +operation_cb (GDBOVoiceCall *voice_call, + GAsyncResult *res, + struct CallsCallOperationData *data) +{ + gboolean ok; + GError *error = NULL; + + ok = data->finish_func (voice_call, res, &error); + if (!ok) + { + g_warning ("Error %s oFono voice call to `%s': %s", + data->desc, data->self->number, error->message); + CALLS_ERROR (data->self, error); + } + + g_free (data); +} + + +static void +answer (CallsCall *call) +{ + CallsOfonoCall *self = CALLS_OFONO_CALL (call); + struct CallsCallOperationData *data; + + data = g_new0 (struct CallsCallOperationData, 1); + data->desc = "answering"; + data->self = self; + data->finish_func = gdbo_voice_call_call_answer_finish; + + gdbo_voice_call_call_answer + (self->voice_call, NULL, + (GAsyncReadyCallback) operation_cb, + data); +} + + +static void +hang_up (CallsCall *call) +{ + CallsOfonoCall *self = CALLS_OFONO_CALL (call); + struct CallsCallOperationData *data; + + data = g_new0 (struct CallsCallOperationData, 1); + data->desc = "hanging up"; + data->self = self; + data->finish_func = gdbo_voice_call_call_hangup_finish; + + gdbo_voice_call_call_hangup + (self->voice_call, NULL, + (GAsyncReadyCallback) operation_cb, + data); +} + + +static void +tone_start (CallsCall *call, gchar key) +{ + g_info ("Beep! (%c)", (int)key); +} + + +static void +tone_stop (CallsCall *call, gchar key) +{ + g_info ("Beep end (%c)", (int)key); +} + + +static void +set_properties (CallsOfonoCall *self, + GVariant *props) +{ + const gchar *str = NULL; + + g_return_if_fail (props != NULL); + + g_variant_lookup (props, "LineIdentification", "s", &self->number); + g_variant_lookup (props, "Name", "s", &self->name); + + g_variant_lookup (props, "State", "&s", &str); + if (str) + { + calls_call_state_parse_nick (&self->state, str); + } +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsOfonoCall *self = CALLS_OFONO_CALL (object); + + switch (property_id) { + case PROP_VOICE_CALL: + CALLS_SET_OBJECT_PROPERTY + (self->voice_call, GDBO_VOICE_CALL (g_value_get_object (value))); + break; + + case PROP_PROPERTIES: + set_properties (self, g_value_get_variant (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +property_changed_cb (CallsOfonoCall *self, + const gchar *name, + GVariant *value) +{ + GVariant *str_var; + gchar *str = NULL; + CallsCallState state; + gboolean ok; + + { + gchar *text = g_variant_print (value, TRUE); + g_debug ("Property `%s' for oFono call to `%s' changed to: %s", + name, self->number, text); + g_free (text); + } + + if (g_strcmp0 (name, "State") != 0) + { + return; + } + + g_variant_get (value, "v", &str_var); + g_variant_get (str_var, "&s", &str); + g_return_if_fail (str != NULL); + + ok = calls_call_state_parse_nick (&state, str); + if (ok) + { + change_state (self, state); + } + else + { + g_warning ("Could not parse new state `%s'" + " of oFono call to `%s'", + str, self->number); + } + + g_variant_unref (str_var); +} + + +static void +disconnect_reason_cb (CallsOfonoCall *self, + const gchar *reason) +{ + if (reason) + { + CALLS_SET_PTR_PROPERTY (self->disconnect_reason, + g_strdup (reason)); + } +} + + +static void +constructed (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsOfonoCall *self = CALLS_OFONO_CALL (object); + + g_return_if_fail (self->voice_call != NULL); + + g_signal_connect_swapped (self->voice_call, "property-changed", + G_CALLBACK (property_changed_cb), self); + g_signal_connect_swapped (self->voice_call, "disconnect-reason", + G_CALLBACK (disconnect_reason_cb), self); + + parent_class->constructed (object); +} + + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsOfonoCall *self = CALLS_OFONO_CALL (object); + + CALLS_DISPOSE_OBJECT (self->voice_call); + + parent_class->dispose (object); +} + + +static void +finalize (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsOfonoCall *self = CALLS_OFONO_CALL (object); + + CALLS_FREE_PTR_PROPERTY (self->disconnect_reason); + CALLS_FREE_PTR_PROPERTY (self->name); + CALLS_FREE_PTR_PROPERTY (self->number); + + parent_class->finalize (object); +} + + +static void +calls_ofono_call_class_init (CallsOfonoCallClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->finalize = finalize; + + props[PROP_VOICE_CALL] = + g_param_spec_object ("voice-call", + _("Voice call"), + _("A GDBO proxy object for the underlying call object"), + GDBO_TYPE_VOICE_CALL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + props[PROP_PROPERTIES] = + g_param_spec_variant ("properties", + _("Properties"), + _("The a{sv} dictionary of properties for the voice call object"), + G_VARIANT_TYPE_ARRAY, + NULL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + + +static void +calls_ofono_call_message_source_interface_init (CallsCallInterface *iface) +{ +} + + +static void +calls_ofono_call_call_interface_init (CallsCallInterface *iface) +{ + iface->get_number = get_number; + iface->get_name = get_name; + iface->get_state = get_state; + iface->answer = answer; + iface->hang_up = hang_up; + iface->tone_start = tone_start; + iface->tone_stop = tone_stop; +} + + +static void +calls_ofono_call_init (CallsOfonoCall *self) +{ +} + + +CallsOfonoCall * +calls_ofono_call_new (GDBOVoiceCall *voice_call, + GVariant *properties) +{ + g_return_val_if_fail (GDBO_IS_VOICE_CALL (voice_call), NULL); + g_return_val_if_fail (properties != NULL, NULL); + + return g_object_new (CALLS_TYPE_OFONO_CALL, + "voice-call", voice_call, + "properties", properties, + NULL); +} + + +const gchar * +calls_ofono_call_get_object_path (CallsOfonoCall *call) +{ + return g_dbus_proxy_get_object_path (G_DBUS_PROXY (call->voice_call)); +} + + +const gchar * +calls_ofono_call_get_disconnect_reason (CallsOfonoCall *call) +{ + return call->disconnect_reason; +} diff --git a/src/calls-ofono-call.h b/src/calls-ofono-call.h new file mode 100644 index 0000000..597c945 --- /dev/null +++ b/src/calls-ofono-call.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_OFONO_CALL_H__ +#define CALLS_OFONO_CALL_H__ + +#include + +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_OFONO_CALL (calls_ofono_call_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsOfonoCall, calls_ofono_call, CALLS, OFONO_CALL, GObject); + +CallsOfonoCall *calls_ofono_call_new (GDBOVoiceCall *voice_call, + GVariant *properties); +const gchar *calls_ofono_call_get_object_path (CallsOfonoCall *call); +const gchar *calls_ofono_call_get_disconnect_reason (CallsOfonoCall *call); + + +G_END_DECLS + +#endif /* CALLS_OFONO_CALL_H__ */ diff --git a/src/calls-ofono-object.c b/src/calls-ofono-object.c new file mode 100644 index 0000000..2cf715b --- /dev/null +++ b/src/calls-ofono-object.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-call-holder.h" +#include "util.h" + +#include +#include + +/** + * SECTION:calls-ofono-object + * @short_description: Object to encapsulate common + * @Title: CallsOfonoObject + */ + +struct _CallsOfonoObject +{ + GObject parent_instance; + +}; + +G_DEFINE_TYPE (CallsOfonoObject, calls_ofono_object, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_CALL, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +CallsOfonoObject * +calls_ofono_object_new (CallsCall *call) +{ + return g_object_new (CALLS_TYPE_OFONO_OBJECT, + "call", call, + NULL); +} + + +CallsCallData * +calls_ofono_object_get_data (CallsOfonoObject *holder) +{ + g_return_val_if_fail (CALLS_IS_OFONO_OBJECT (holder), NULL); + return holder->data; +} + + +CallsCallDisplay * +calls_ofono_object_get_display (CallsOfonoObject *holder) +{ + g_return_val_if_fail (CALLS_IS_OFONO_OBJECT (holder), NULL); + return holder->display; +} + + +CallsCallSelectorItem * +calls_ofono_object_get_selector_item (CallsOfonoObject *holder) +{ + g_return_val_if_fail (CALLS_IS_OFONO_OBJECT (holder), NULL); + return holder->selector_item; +} + + +static void +set_call (CallsOfonoObject *self, CallsCall *call) +{ + CallsParty *party + = calls_party_new (NULL, calls_call_get_identifier (call)); + + self->data = calls_call_data_new (call, party); + g_object_unref (party); + + self->display = calls_call_display_new (self->data); + self->selector_item = + calls_call_selector_item_new (self); +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsOfonoObject *self = CALLS_OFONO_OBJECT (object); + + switch (property_id) { + case PROP_CALL: + set_call (self, CALLS_CALL (g_value_get_object (value))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +calls_ofono_object_init (CallsOfonoObject *self) +{ +} + + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsOfonoObject *self = CALLS_OFONO_OBJECT (object); + + CALLS_DISPOSE_OBJECT (self->selector_item); + CALLS_DISPOSE_OBJECT (self->display); + CALLS_DISPOSE_OBJECT (self->data); + + parent_class->dispose (object); +} + + +static void +calls_ofono_object_class_init (CallsOfonoObjectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = set_property; + object_class->dispose = dispose; + + props[PROP_CALL] = + g_param_spec_object ("call", + _("Call"), + _("The call to hold"), + CALLS_TYPE_CALL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} diff --git a/src/calls-ofono-object.h b/src/calls-ofono-object.h new file mode 100644 index 0000000..2a8090a --- /dev/null +++ b/src/calls-ofono-object.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_OFONO_OBJECT_H__ +#define CALLS_OFONO_OBJECT_H__ + +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_OFONO_OBJECT (calls_ofono_object_get_type ()) + +G_DECLARE_DERIVABLE_TYPE (CallsOfonoObject, calls_ofono_object, CALLS, OFONO_OBJECT, GDBusProxy); + + +typedef void (*CallsOfonoSignalCallback) (CallsOfonoObject *object, + gchar *signal_name, + GVariant *parameters, + gpointer user_data); + +/** + * CallsOfonoObjectClass: + * @parent_class: The parent class + * @get_callbacks: An abstract function which must return a + * + */ +struct _CallsOfonoObjectClass +{ + GDBusProxyClass parent_class; + + GHashTable *(*get_callbacks) (CallsOfonoObject *self); +}; + +CallsOfonoObject *calls_ofono_object_new (); + +G_END_DECLS + +#endif /* CALLS_OFONO_OBJECT_H__ */ diff --git a/src/calls-ofono-origin.c b/src/calls-ofono-origin.c new file mode 100644 index 0000000..e95dd18 --- /dev/null +++ b/src/calls-ofono-origin.c @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-ofono-origin.h" +#include "calls-origin.h" +#include "calls-ofono-call.h" +#include "calls-message-source.h" + +#include + + +struct _CallsOfonoOrigin +{ + GObject parent_instance; + GDBusConnection *connection; + GDBOModem *modem; + gchar *name; + GDBOVoiceCallManager *voice; + GHashTable *calls; +}; + +static void calls_ofono_origin_message_source_interface_init (CallsOriginInterface *iface); +static void calls_ofono_origin_origin_interface_init (CallsOriginInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (CallsOfonoOrigin, calls_ofono_origin, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE, + calls_ofono_origin_message_source_interface_init) + G_IMPLEMENT_INTERFACE (CALLS_TYPE_ORIGIN, + calls_ofono_origin_origin_interface_init)) + +enum { + PROP_0, + PROP_MODEM, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +static const gchar * +get_name (CallsOrigin *origin) +{ + CallsOfonoOrigin *self = CALLS_OFONO_ORIGIN (origin); + return self->name; +} + + +static GList * +get_calls (CallsOrigin * origin) +{ + CallsOfonoOrigin *self = CALLS_OFONO_ORIGIN (origin); + return g_hash_table_get_values (self->calls); +} + +static void +dial_cb (GDBOVoiceCallManager *voice, + GAsyncResult *res, + CallsOfonoOrigin *self) +{ + gboolean ok; + GError *error = NULL; + + ok = gdbo_voice_call_manager_call_dial_finish + (voice, NULL, res, &error); + if (!ok) + { + g_warning ("Error dialing number on modem `%s': %s", + self->name, error->message); + CALLS_ERROR (self, error); + return; + } + + /* We will add the call through the call-added signal */ +} + + +static void +dial (CallsOrigin *origin, const gchar *number) +{ + CallsOfonoOrigin *self = CALLS_OFONO_ORIGIN (origin); + + g_return_if_fail (self->voice != NULL); + + gdbo_voice_call_manager_call_dial + (self->voice, + number, + "default" /* default caller id settings */, + NULL, + (GAsyncReadyCallback) dial_cb, + self); + /* + CallsOfonoCall *ofono_call; + CallsCall *call; + + g_return_if_fail (number != NULL); + g_return_if_fail (CALLS_IS_OFONO_ORIGIN (origin)); + + self = CALLS_OFONO_ORIGIN (origin); + + ofono_call = calls_ofono_call_new (number); + g_return_if_fail (ofono_call != NULL); + + call = CALLS_CALL (ofono_call); + g_signal_connect_swapped (call, "state-changed", + G_CALLBACK (call_state_changed_cb), + self); + + self->calls = g_list_append (self->calls, ofono_call); + + g_signal_emit_by_name (origin, "call-added", call); + */ +} + + +CallsOfonoOrigin * +calls_ofono_origin_new (GDBOModem *modem) +{ + g_return_val_if_fail (GDBO_IS_MODEM (modem), NULL); + + return g_object_new (CALLS_TYPE_OFONO_ORIGIN, + "modem", modem, + NULL); +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsOfonoOrigin *self = CALLS_OFONO_ORIGIN (object); + + switch (property_id) { + case PROP_MODEM: + CALLS_SET_OBJECT_PROPERTY + (self->modem, GDBO_MODEM (g_value_get_object (value))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +remove_call (CallsOfonoOrigin *self, + CallsOfonoCall *call, + const gchar *reason) +{ + const gchar *path = calls_ofono_call_get_object_path (call); + + g_signal_emit_by_name (CALLS_ORIGIN(self), "call-removed", + CALLS_CALL(call), reason); + g_hash_table_remove (self->calls, path); +} + + +struct CallsRemoveCallsData +{ + CallsOrigin *origin; + const gchar *reason; +}; + +static gboolean +remove_calls_cb (const gchar *path, + CallsOfonoCall *call, + struct CallsRemoveCallsData *data) +{ + g_signal_emit_by_name (data->origin, "call-removed", + CALLS_CALL(call), data->reason); + return TRUE; +} + +static void +remove_calls (CallsOfonoOrigin *self, const gchar *reason) +{ + struct CallsRemoveCallsData data = { CALLS_ORIGIN (self), reason }; + + g_hash_table_foreach_remove (self->calls, + (GHRFunc) remove_calls_cb, + &data); +} + + +struct CallsVoiceCallProxyNewData +{ + CallsOfonoOrigin *self; + GVariant *properties; +}; + + +static void +voice_call_proxy_new_cb (GDBusConnection *connection, + GAsyncResult *res, + struct CallsVoiceCallProxyNewData *data) +{ + CallsOfonoOrigin *self = data->self; + GDBOVoiceCall *voice_call; + GError *error = NULL; + const gchar *path; + CallsOfonoCall *call; + + voice_call = gdbo_voice_call_proxy_new_finish (res, &error); + if (!voice_call) + { + g_variant_unref (data->properties); + g_free (data); + g_warning ("Error creating oFono VoiceCall proxy: %s", + error->message); + CALLS_ERROR (self, error); + return; + } + + call = calls_ofono_call_new (voice_call, data->properties); + + path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (voice_call)); + g_hash_table_insert (self->calls, g_strdup(path), call); + + g_signal_emit_by_name (CALLS_ORIGIN(self), "call-added", + CALLS_CALL(call)); + + g_debug ("Call `%s' added", path); +} + + +static void +call_added_cb (GDBOVoiceCallManager *voice, + const gchar *path, + GVariant *properties, + CallsOfonoOrigin *self) +{ + struct CallsVoiceCallProxyNewData *data; + + g_debug ("Adding call `%s'", path); + + if (g_hash_table_lookup (self->calls, path)) + { + g_warning ("Call `%s' already exists", path); + return; + } + + data = g_new0 (struct CallsVoiceCallProxyNewData, 1); + data->self = self; + data->properties = properties; + g_variant_ref (properties); + + gdbo_voice_call_proxy_new + (self->connection, + G_DBUS_PROXY_FLAGS_NONE, + g_dbus_proxy_get_name (G_DBUS_PROXY (voice)), + path, + NULL, + (GAsyncReadyCallback) voice_call_proxy_new_cb, + data); + + g_debug ("Call `%s' addition in progress", path); +} + + +static void +call_removed_cb (GDBOVoiceCallManager *voice, + const gchar *path, + CallsOfonoOrigin *self) +{ + CallsOfonoCall *ofono_call; + GString *reason; + const gchar *ofono_reason; + + g_debug ("Removing call `%s'", path); + + ofono_call = g_hash_table_lookup (self->calls, path); + if (!ofono_call) + { + g_warning ("Could not find removed call `%s'", path); + return; + } + + reason = g_string_new ("Call removed"); + + ofono_reason = calls_ofono_call_get_disconnect_reason (ofono_call); + if (ofono_reason) + { + /* The oFono reason is either "local", "remote" or "network". + * We just capitalise that to create a nice reason string. + */ + g_string_assign (reason, ofono_reason); + reason->str[0] = g_ascii_toupper (reason->str[0]); + g_string_append (reason, " disconnection"); + } + + remove_call (self, ofono_call, reason->str); + + g_string_free (reason, TRUE); + + g_debug ("Removed call `%s'", path); +} + +static void +get_calls_cb (GDBOVoiceCallManager *voice, + GAsyncResult *res, + CallsOfonoOrigin *self) +{ + gboolean ok; + GVariant *calls_with_properties = NULL; + GError *error = NULL; + GVariantIter *iter = NULL; + const gchar *path; + GVariant *properties; + + ok = gdbo_voice_call_manager_call_get_calls_finish + (voice, &calls_with_properties, res, &error); + if (!ok) + { + g_warning ("Error getting calls from oFono" + " VoiceCallManager `%s': %s", + self->name, error->message); + CALLS_ERROR (self, error); + return; + } + + { + char *text = g_variant_print (calls_with_properties, TRUE); + g_debug ("Received calls from oFono" + " VoiceCallManager `%s': %s", + self->name, text); + g_free (text); + } + + g_variant_get (calls_with_properties, "a(oa{sv})", &iter); + while (g_variant_iter_loop (iter, "(&o@a{sv})", + &path, &properties)) + { + g_debug ("Got call object path `%s'", path); + call_added_cb (voice, path, properties, self); + } + g_variant_iter_free (iter); + + g_variant_unref (calls_with_properties); +} + +static void +voice_new_cb (GDBusConnection *connection, + GAsyncResult *res, + CallsOfonoOrigin *self) +{ + GError *error = NULL; + + self->voice = gdbo_voice_call_manager_proxy_new_finish + (res, &error); + if (!self->voice) + { + g_warning ("Error creating oFono" + " VoiceCallManager `%s' proxy: %s", + self->name, error->message); + CALLS_ERROR (self, error); + return; + } + + g_signal_connect (self->voice, "call-added", + G_CALLBACK (call_added_cb), self); + g_signal_connect (self->voice, "call-removed", + G_CALLBACK (call_removed_cb), self); + + gdbo_voice_call_manager_call_get_calls + (self->voice, + NULL, + (GAsyncReadyCallback) get_calls_cb, + self); +} + + +static void +constructed (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsOfonoOrigin *self = CALLS_OFONO_ORIGIN (object); + GDBusProxy *modem_proxy; + gchar *name; + + g_return_if_fail (self->modem != NULL); + + modem_proxy = G_DBUS_PROXY (self->modem); + + self->connection = g_dbus_proxy_get_connection (modem_proxy); + g_object_ref (self->connection); + + name = g_object_get_data (G_OBJECT (self->modem), + "calls-modem-name"); + if (name) + { + self->name = g_strdup (name); + } + + gdbo_voice_call_manager_proxy_new + (self->connection, + G_DBUS_PROXY_FLAGS_NONE, + g_dbus_proxy_get_name (modem_proxy), + g_dbus_proxy_get_object_path (modem_proxy), + NULL, + (GAsyncReadyCallback)voice_new_cb, + self); + + CALLS_DISPOSE_OBJECT (self->modem); + + parent_class->constructed (object); +} + + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsOfonoOrigin *self = CALLS_OFONO_ORIGIN (object); + + remove_calls (self, NULL); + CALLS_DISPOSE_OBJECT (self->modem); + CALLS_DISPOSE_OBJECT (self->connection); + + parent_class->dispose (object); +} + + +static void +finalize (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsOfonoOrigin *self = CALLS_OFONO_ORIGIN (object); + + if (self->name) + { + g_free (self->name); + } + + parent_class->finalize (object); +} + + +static void +calls_ofono_origin_class_init (CallsOfonoOriginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->finalize = finalize; + + props[PROP_MODEM] = + g_param_spec_object ("modem", + _("Modem"), + _("A GDBO proxy object for the underlying modem object"), + GDBO_TYPE_MODEM, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + + +static void +calls_ofono_origin_message_source_interface_init (CallsOriginInterface *iface) +{ +} + + +static void +calls_ofono_origin_origin_interface_init (CallsOriginInterface *iface) +{ + iface->get_name = get_name; + iface->get_calls = get_calls; + iface->dial = dial; +} + + +static void +calls_ofono_origin_init (CallsOfonoOrigin *self) +{ + self->calls = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_object_unref); +} diff --git a/src/calls-ofono-origin.h b/src/calls-ofono-origin.h new file mode 100644 index 0000000..2e3c30f --- /dev/null +++ b/src/calls-ofono-origin.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_OFONO_ORIGIN_H__ +#define CALLS_OFONO_ORIGIN_H__ + +#include + +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_OFONO_ORIGIN (calls_ofono_origin_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsOfonoOrigin, calls_ofono_origin, CALLS, OFONO_ORIGIN, GObject); + +CallsOfonoOrigin *calls_ofono_origin_new (GDBOModem *modem); + +G_END_DECLS + +#endif /* CALLS_OFONO_ORIGIN_H__ */ diff --git a/src/calls-ofono-provider.c b/src/calls-ofono-provider.c new file mode 100644 index 0000000..2f96a04 --- /dev/null +++ b/src/calls-ofono-provider.c @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-ofono-provider.h" +#include "calls-provider.h" +#include "calls-ofono-origin.h" +#include "calls-message-source.h" +#include "util.h" + +#include +#include + +#include + +struct _CallsOfonoProvider +{ + GObject parent_instance; + + /** D-Bus connection */ + GDBusConnection *connection; + /** D-Bus proxy for the oFono Manager object */ + GDBOManager *manager; + /** Map of D-Bus object paths to a struct CallsModemData */ + GHashTable *modems; + /** Map of D-Bus object paths to Origins */ + GHashTable *origins; +}; + +static void calls_ofono_provider_message_source_interface_init (CallsProviderInterface *iface); +static void calls_ofono_provider_provider_interface_init (CallsProviderInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (CallsOfonoProvider, calls_ofono_provider, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE, + calls_ofono_provider_message_source_interface_init) + G_IMPLEMENT_INTERFACE (CALLS_TYPE_PROVIDER, + calls_ofono_provider_provider_interface_init)) + +enum { + PROP_0, + PROP_CONNECTION, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +static const gchar * +get_name (CallsProvider *iface) +{ + return "oFono"; +} + +static void +add_origin_to_list (const gchar *path, + CallsOfonoOrigin *origin, + GList **list) +{ + *list = g_list_prepend (*list, origin); +} + +static GList * +get_origins (CallsProvider *iface) +{ + CallsOfonoProvider *self = CALLS_OFONO_PROVIDER (iface); + GList *list = NULL; + + g_hash_table_foreach (self->origins, + (GHFunc)add_origin_to_list, &list); + + return g_list_reverse (list); +} + +CallsOfonoProvider * +calls_ofono_provider_new (GDBusConnection *connection) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + + return g_object_new (CALLS_TYPE_OFONO_PROVIDER, + "connection", connection, + NULL); +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsOfonoProvider *self = CALLS_OFONO_PROVIDER (object); + + switch (property_id) { + case PROP_CONNECTION: + CALLS_SET_OBJECT_PROPERTY (self->connection, + g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +add_origin (CallsOfonoProvider *self, + const gchar *path, + GDBOModem *modem) +{ + CallsOfonoOrigin *origin; + + g_debug ("Adding oFono Origin with path `%s'", path); + + origin = calls_ofono_origin_new (modem); + g_hash_table_insert (self->origins, g_strdup(path), origin); + + g_signal_emit_by_name (CALLS_PROVIDER (self), + "origin-added", origin); +} + +static void +remove_origin (CallsOfonoProvider *self, + const gchar *path, + CallsOfonoOrigin *origin) +{ + g_debug ("Removing oFono Origin with path `%s'", path); + + g_signal_emit_by_name (CALLS_PROVIDER (self), + "origin-removed", origin); + + g_hash_table_remove (self->origins, path); + g_object_unref (origin); +} + +static gboolean +object_array_includes (GVariantIter *iter, + const gchar *needle) +{ + const gchar *str; + gboolean found = FALSE; + while (g_variant_iter_loop (iter, "&s", &str)) + { + if (g_strcmp0 (str, needle) == 0) + { + found = TRUE; + break; + } + } + g_variant_iter_free (iter); + + return found; +} + +static void +modem_check_ifaces (CallsOfonoProvider *self, + GDBOModem *modem, + const gchar *modem_name, + GVariant *ifaces) +{ + gboolean voice; + GVariantIter *iter = NULL; + const gchar *path; + CallsOfonoOrigin *origin; + + g_variant_get (ifaces, "as", &iter); + + voice = object_array_includes + (iter, "org.ofono.VoiceCallManager"); + + path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (modem)); + origin = g_hash_table_lookup (self->origins, path); + + if (voice && !origin) + { + add_origin (self, path, modem); + } + else if (!voice && origin) + { + remove_origin (self, path, origin); + } +} + +static void +modem_property_changed_cb (GDBOModem *modem, + const gchar *name, + GVariant *value, + CallsOfonoProvider *self) +{ + gchar *modem_name; + + g_debug ("Modem property `%s' changed", name); + + if (g_strcmp0 (name, "Interfaces") != 0) + { + return; + } + + modem_name = g_object_get_data (G_OBJECT (modem), + "calls-modem-name"); + + modem_check_ifaces (self, modem, modem_name, value); +} + +struct CallsModemProxyNewData +{ + CallsOfonoProvider *self; + gchar *name; + GVariant *ifaces; +}; + +static void +modem_proxy_new_cb (GDBusConnection *connection, + GAsyncResult *res, + struct CallsModemProxyNewData *data) +{ + GDBOModem *modem; + GError *error = NULL; + const gchar *path; + + modem = gdbo_modem_proxy_new_finish (res, &error); + if (!modem) + { + g_variant_unref (data->ifaces); + g_free (data->name); + g_free (data); + g_error ("Error creating oFono Modem proxy: %s", + error->message); + return; + } + + g_signal_connect (modem, "property-changed", + G_CALLBACK (modem_property_changed_cb), + data->self); + + + /* We want to store the oFono modem's Name property so we can pass it + to our Origin when we create it */ + g_object_set_data_full (G_OBJECT (modem), "calls-modem-name", + data->name, g_free); + + path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (modem)); + + g_hash_table_insert (data->self->modems, g_strdup(path), modem); + + + if (data->ifaces) + { + modem_check_ifaces (data->self, modem, + data->name, data->ifaces); + g_variant_unref (data->ifaces); + } + + g_free (data); + + g_debug ("Modem `%s' added", path); +} + +static gchar * +modem_properties_get_name (GVariant *properties) +{ + gchar *name = NULL; + gboolean ok; + +#define try(prop) \ + ok = g_variant_lookup (properties, prop, "s", &name); \ + if (ok) { \ + return name; \ + } + + try ("Name"); + try ("Model"); + try ("Manufacturer"); + try ("Serial"); + try ("SystemPath"); + +#undef try + + return NULL; +} + +static void +modem_added_cb (GDBOManager *manager, + const gchar *path, + GVariant *properties, + CallsOfonoProvider *self) +{ + struct CallsModemProxyNewData *data; + + g_debug ("Adding modem `%s'", path); + + if (g_hash_table_lookup (self->modems, path)) + { + g_warning ("Modem `%s' already exists", path); + return; + } + + data = g_new0 (struct CallsModemProxyNewData, 1); + data->self = self; + data->name = modem_properties_get_name (properties); + + data->ifaces = g_variant_lookup_value + (properties, "Interfaces", G_VARIANT_TYPE_ARRAY); + if (data->ifaces) + { + g_variant_ref (data->ifaces); + } + + gdbo_modem_proxy_new + (self->connection, + G_DBUS_PROXY_FLAGS_NONE, + g_dbus_proxy_get_name (G_DBUS_PROXY (manager)), + path, + NULL, + (GAsyncReadyCallback) modem_proxy_new_cb, + data); + + g_debug ("Modem `%s' addition in progress", path); +} + +static void +modem_removed_cb (GDBOManager *manager, + const gchar *path, + CallsOfonoProvider *self) +{ + CallsOfonoOrigin *origin; + + g_debug ("Removing modem `%s'", path); + + origin = g_hash_table_lookup (self->origins, path); + if (origin) + { + remove_origin (self, path, origin); + } + + g_hash_table_remove (self->modems, path); + + g_debug ("Modem `%s' removed", path); +} + + +static void +get_modems_cb (GDBOManager *manager, + GAsyncResult *res, + CallsOfonoProvider *self) +{ + gboolean ok; + GVariant *modems; + GVariantIter *modems_iter = NULL; + GError *error = NULL; + const gchar *path; + GVariant *properties; + + ok = gdbo_manager_call_get_modems_finish (manager, &modems, + res, &error); + if (!ok) + { + g_warning ("Error getting modems from oFono Manager: %s", + error->message); + CALLS_ERROR (self, error); + return; + } + + { + char *text = g_variant_print (modems, TRUE); + g_debug ("Received modems from oFono Manager: %s", text); + g_free (text); + } + + g_variant_get (modems, "a(oa{sv})", &modems_iter); + while (g_variant_iter_loop (modems_iter, "(&o@a{sv})", + &path, &properties)) + { + g_debug ("Got modem object path `%s'", path); + modem_added_cb (manager, path, properties, self); + } + g_variant_iter_free (modems_iter); + + g_variant_unref (modems); +} + + +static void +constructed (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsOfonoProvider *self = CALLS_OFONO_PROVIDER (object); + GError *error = NULL; + + self->manager = gdbo_manager_proxy_new_sync + (self->connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.ofono", + "/", + NULL, + &error); + if (!self->manager) + { + g_error ("Error creating ModemManager object manager proxy: %s", + error->message); + return; + } + + g_signal_connect (self->manager, "modem-added", + G_CALLBACK (modem_added_cb), self); + g_signal_connect (self->manager, "modem-removed", + G_CALLBACK (modem_removed_cb), self); + + gdbo_manager_call_get_modems + (self->manager, + NULL, + (GAsyncReadyCallback) get_modems_cb, + self); + + parent_class->constructed (object); +} + + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsOfonoProvider *self = CALLS_OFONO_PROVIDER (object); + + // FIXME + + CALLS_DISPOSE_OBJECT (self->manager); + CALLS_DISPOSE_OBJECT (self->connection); + + parent_class->dispose (object); +} + +static void +finalize (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsOfonoProvider *self = CALLS_OFONO_PROVIDER (object); + + g_hash_table_unref (self->origins); + g_hash_table_unref (self->modems); + + parent_class->finalize (object); +} + + +static void +calls_ofono_provider_class_init (CallsOfonoProviderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->finalize = finalize; + + props[PROP_CONNECTION] = + g_param_spec_object ("connection", + _("Connection"), + _("The D-Bus connection to use for communication with oFono"), + G_TYPE_DBUS_CONNECTION, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + +static void +calls_ofono_provider_message_source_interface_init (CallsProviderInterface *iface) +{ +} + +static void +calls_ofono_provider_provider_interface_init (CallsProviderInterface *iface) +{ + iface->get_name = get_name; + iface->get_origins = get_origins; +} + +static void +calls_ofono_provider_init (CallsOfonoProvider *self) +{ + self->modems = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_object_unref); + self->origins = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); +} diff --git a/src/calls-ofono-provider.h b/src/calls-ofono-provider.h new file mode 100644 index 0000000..ccca1e6 --- /dev/null +++ b/src/calls-ofono-provider.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_OFONO_PROVIDER_H__ +#define CALLS_OFONO_PROVIDER_H__ + +#include +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_OFONO_PROVIDER (calls_ofono_provider_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsOfonoProvider, calls_ofono_provider, CALLS, OFONO_PROVIDER, GObject); + +CallsOfonoProvider *calls_ofono_provider_new (GDBusConnection *connection); + +G_END_DECLS + +#endif /* CALLS_OFONO_PROVIDER_H__ */ diff --git a/src/calls-origin.c b/src/calls-origin.c new file mode 100644 index 0000000..565e1f9 --- /dev/null +++ b/src/calls-origin.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-origin.h" +#include "calls-message-source.h" +#include "util.h" + +/** + * SECTION:calls-origin + * @short_description: An object that originates calls. + * @Title: CallsOrigin + */ + + +G_DEFINE_INTERFACE (CallsOrigin, calls_origin, CALLS_TYPE_MESSAGE_SOURCE); + +enum { + SIGNAL_CALL_ADDED, + SIGNAL_CALL_REMOVED, + SIGNAL_LAST_SIGNAL, +}; +static guint signals [SIGNAL_LAST_SIGNAL]; + +static void +calls_origin_default_init (CallsOriginInterface *iface) +{ + GType arg_types[2] = { CALLS_TYPE_CALL, G_TYPE_STRING }; + + signals[SIGNAL_CALL_ADDED] = + g_signal_newv ("call-added", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + NULL, NULL, NULL, NULL, + G_TYPE_NONE, + 1, arg_types); + + signals[SIGNAL_CALL_REMOVED] = + g_signal_newv ("call-removed", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + NULL, NULL, NULL, NULL, + G_TYPE_NONE, + 2, arg_types); +} + +#define DEFINE_ORIGIN_FUNC(function,rettype,errval) \ + CALLS_DEFINE_IFACE_FUNC(origin, Origin, ORIGIN, \ + function, rettype, errval) + +/** + * calls_origin_get_name: + * @self: a #CallsOrigin + * + * Get the user-presentable name of the origin. + * + * Returns: A string containing the name. The string must be freed by + * the caller. + */ +DEFINE_ORIGIN_FUNC(get_name, const gchar *, NULL); + +/** + * calls_origin_get_calls: + * @self: a #CallsOrigin + * @error: a #GError, or #NULL + * + * Get the list of current calls. + * + * Returns: A newly-allocated GList of objects implementing + * #CallsCall or NULL if there was an error. + */ +DEFINE_ORIGIN_FUNC(get_calls, GList *, NULL); + +/** + * calls_origin_dial: + * @self: a #CallsOrigin + * @number: the number to dial + * + * Dial a new number from this origin. If a new call is successfully + * created, the #call-added signal will be emitted with the call. If + * there is an error, an appropriate #message signal will be emitted. + */ +void +calls_origin_dial(CallsOrigin *self, + const gchar *number) +{ + CallsOriginInterface *iface; + + g_return_if_fail (CALLS_IS_ORIGIN (self)); + g_return_if_fail (number != NULL); + + iface = CALLS_ORIGIN_GET_IFACE (self); + g_return_if_fail (iface->dial != NULL); + + return iface->dial(self, number); +} diff --git a/src/calls-origin.h b/src/calls-origin.h new file mode 100644 index 0000000..6634e32 --- /dev/null +++ b/src/calls-origin.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_ORIGIN_H__ +#define CALLS_ORIGIN_H__ + +#include "calls-call.h" +#include "util.h" + +#include + +G_BEGIN_DECLS + + +#define CALLS_TYPE_ORIGIN (calls_origin_get_type ()) + +G_DECLARE_INTERFACE (CallsOrigin, calls_origin, CALLS, ORIGIN, GObject); + + +struct _CallsOriginInterface +{ + GTypeInterface parent_iface; + + const gchar * (*get_name) (CallsOrigin *self); + GList * (*get_calls) (CallsOrigin *self); + void (*dial) (CallsOrigin *self, + const gchar *number); +}; + + +const gchar * calls_origin_get_name (CallsOrigin *self); +GList * calls_origin_get_calls (CallsOrigin *self); +void calls_origin_dial(CallsOrigin *self, + const gchar *number); + +G_END_DECLS + +#endif /* CALLS_ORIGIN_H__ */ diff --git a/src/calls-party.c b/src/calls-party.c new file mode 100644 index 0000000..8c70b66 --- /dev/null +++ b/src/calls-party.c @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-party.h" +#include "util.h" + +#include + +/** + * SECTION:calls-party + * @short_description: A collection of data about an entity that party to a call. + * @Title: CallsParty + */ + +struct _CallsParty +{ + GObject parent_instance; + gchar *name; + gchar *number; +}; + +G_DEFINE_TYPE(CallsParty, calls_party, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_NAME, + PROP_NUMBER, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +CallsParty * +calls_party_new (const gchar *name, const gchar *number) +{ + return g_object_new (CALLS_TYPE_PARTY, + "name", name, + "number", number, + NULL); +} + + +/** + * calls_party_create_image: + * @party: a #CallsParty + * + * Create a new #GtkImage widget that displays an appropriate image for + * the party. + * + * Returns: a new #GtkImage widget. + */ +GtkWidget * +calls_party_create_image (CallsParty *party) +{ + return gtk_image_new_from_icon_name ("face-smile", GTK_ICON_SIZE_DIALOG); +} + + +const gchar * +calls_party_get_name (CallsParty *party) +{ + g_return_val_if_fail (CALLS_IS_PARTY (party), NULL); + return CALLS_PARTY (party)->name; +} + + +const gchar * +calls_party_get_number (CallsParty *party) +{ + g_return_val_if_fail (CALLS_IS_PARTY (party), NULL); + return CALLS_PARTY (party)->number; +} + + +/** + * calls_party_get_label: + * @party: a #CallsParty + * + * Get an appropriate user-visible label for the party. + * + * Returns: the party's name if the name is non-NULL, otherwise the + * party's number. + */ +const gchar * calls_party_get_label (CallsParty *party) +{ + if (party->name) + { + return party->name; + } + + return party->number; +} + + +static void +get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + CallsParty *self = CALLS_PARTY (object); + + switch (property_id) { + case PROP_NAME: + g_value_set_string (value, self->name); + break; + + case PROP_NUMBER: + g_value_set_string (value, self->number); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsParty *self = CALLS_PARTY (object); + + switch (property_id) { + case PROP_NAME: + CALLS_SET_PTR_PROPERTY (self->name, g_value_dup_string (value)); + break; + + case PROP_NUMBER: + CALLS_SET_PTR_PROPERTY (self->number, g_value_dup_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +finalize (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsParty *self = CALLS_PARTY (object); + + CALLS_SET_PTR_PROPERTY (self->number, NULL); + CALLS_SET_PTR_PROPERTY (self->name, NULL); + + parent_class->finalize (object); +} + + +static void +calls_party_class_init (CallsPartyClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = finalize; + object_class->get_property = get_property; + object_class->set_property = set_property; + + props[PROP_NAME] = + g_param_spec_string ("name", + _("Name"), + _("The party's name"), + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + props[PROP_NUMBER] = + g_param_spec_string ("number", + _("Number"), + _("The party's telephone number"), + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + + +static void +calls_party_init (CallsParty *self) +{ +} + + diff --git a/src/calls-party.h b/src/calls-party.h new file mode 100644 index 0000000..c691fb2 --- /dev/null +++ b/src/calls-party.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_PARTY_H__ +#define CALLS_PARTY_H__ + +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_PARTY (calls_party_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsParty, calls_party, CALLS, PARTY, GObject); + +CallsParty *calls_party_new (const gchar *name, + const gchar *number); +GtkWidget *calls_party_create_image (CallsParty *party); +const gchar * calls_party_get_name (CallsParty *party); +const gchar * calls_party_get_number (CallsParty *party); +const gchar * calls_party_get_label (CallsParty *party); + +G_END_DECLS + +#endif /* CALLS_PARTY_H__ */ diff --git a/src/calls-provider.c b/src/calls-provider.c new file mode 100644 index 0000000..6125c8d --- /dev/null +++ b/src/calls-provider.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-provider.h" +#include "calls-origin.h" +#include "calls-message-source.h" +#include "util.h" + +/** + * SECTION:calls-provider + * @short_description: An abstraction of call providers, such as + * oFono, Telepathy or some SIP library. + * @Title: CallsProvider + * + * The #CallsProvider interface is the root of the interface tree that + * needs to be implemented by a call provider. A #CallsProvider + * provides access to a list of #CallsOrigin interfaces, through the + * #calls_provider_get_origins function and the #origin-added and + * #origin-removed signals. + */ + + +G_DEFINE_INTERFACE (CallsProvider, calls_provider, CALLS_TYPE_MESSAGE_SOURCE); + +enum { + SIGNAL_ORIGIN_ADDED, + SIGNAL_ORIGIN_REMOVED, + SIGNAL_LAST_SIGNAL, +}; +static guint signals [SIGNAL_LAST_SIGNAL]; + +static void +calls_provider_default_init (CallsProviderInterface *iface) +{ + GType arg_types = CALLS_TYPE_ORIGIN; + + signals[SIGNAL_ORIGIN_ADDED] = + g_signal_newv ("origin-added", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + NULL, NULL, NULL, NULL, + G_TYPE_NONE, + 1, &arg_types); + + signals[SIGNAL_ORIGIN_REMOVED] = + g_signal_newv ("origin-removed", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + NULL, NULL, NULL, NULL, + G_TYPE_NONE, + 1, &arg_types); +} + +#define DEFINE_PROVIDER_FUNC(function,rettype,errval) \ + CALLS_DEFINE_IFACE_FUNC(provider, Provider, PROVIDER, \ + function, rettype, errval) + +/** + * calls_provider_get_name: + * @self: a #CallsProvider + * + * Get the user-presentable name of the provider. + * + * Returns: A string containing the name. + */ +DEFINE_PROVIDER_FUNC(get_name, const gchar *, NULL); + +/** + * calls_provider_get_origins: + * @self: a #CallsProvider + * @error: a #GError, or #NULL + * + * Get the list of #CallsOrigin interfaces offered by this provider. + * + * Returns: A newly-allocated GList of objects implementing + * #CallsOrigin or NULL if there was an error. + */ +DEFINE_PROVIDER_FUNC(get_origins, GList *, NULL); diff --git a/src/calls-provider.h b/src/calls-provider.h new file mode 100644 index 0000000..564d8dd --- /dev/null +++ b/src/calls-provider.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_PROVIDER_H__ +#define CALLS_PROVIDER_H__ + +#include "util.h" + +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_PROVIDER (calls_provider_get_type ()) + +G_DECLARE_INTERFACE (CallsProvider, calls_provider, CALLS, PROVIDER, GObject); + +struct _CallsProviderInterface +{ + GTypeInterface parent_iface; + + const gchar * (*get_name) (CallsProvider *self); + GList * (*get_origins) (CallsProvider *self); +}; + + +const gchar * calls_provider_get_name (CallsProvider *self); +GList * calls_provider_get_origins (CallsProvider *self); + +G_END_DECLS + +#endif /* CALLS_PROVIDER_H__ */ diff --git a/src/calls.gresources.xml b/src/calls.gresources.xml new file mode 100644 index 0000000..6912186 --- /dev/null +++ b/src/calls.gresources.xml @@ -0,0 +1,8 @@ + + + + main-window.ui + call-display.ui + call-selector-item.ui + + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..1c2dc55 --- /dev/null +++ b/src/main.c @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include + +#define HANDY_USE_UNSTABLE_API +#include + +#include "calls-main-window.h" +#include "calls-ofono-provider.h" + +static void +show_window (GtkApplication *app) +{ + GError *error = NULL; + GDBusConnection *connection; + CallsProvider *provider; + CallsMainWindow *main_window; + + HDY_TYPE_DIALER; + + connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!connection) + { + g_error ("Error creating D-Bus connection: %s", + error->message); + return; + } + + provider = CALLS_PROVIDER (calls_ofono_provider_new (connection)); + + main_window = calls_main_window_new (app, provider); + + gtk_window_set_title (GTK_WINDOW (main_window), "Calls"); + + gtk_widget_show_all (GTK_WIDGET (main_window)); +} + +int +main (int argc, + char **argv) +{ + GtkApplication *app; + int status; + + app = gtk_application_new ("sm.puri.Calls", G_APPLICATION_FLAGS_NONE); + g_signal_connect (app, "activate", G_CALLBACK (show_window), NULL); + status = g_application_run (G_APPLICATION (app), argc, argv); + g_object_unref (app); + + return status; +} diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..926446b --- /dev/null +++ b/src/meson.build @@ -0,0 +1,72 @@ +# +# Copyright (C) 2018 Purism SPC +# +# This file is part of Calls. +# +# Calls is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Calls is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Calls. If not, see . +# +# Author: Bob Ham +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +gnome = import('gnome') + +#libgd = subproject('libgd', +# default_options: [ +# 'with-main-box=true' +# ] +#) + + +#libgd_dep = libgd.get_variable('libgd_dep') +deps = [ dependency('gobject-2.0'), + dependency('gtk+-3.0'), + dependency('libhandy-0.0'), +# libgd.get_variable('libgd_dep') + ] + +enum_headers = ['calls-call.h'] +sources = ['calls-message-source.c', 'calls-message-source.h', + 'calls-call.c', + 'calls-origin.c', 'calls-origin.h', + 'calls-provider.c', 'calls-provider.h', + 'calls-ofono-call.c', 'calls-ofono-call.h', + 'calls-ofono-origin.c', 'calls-ofono-origin.h', + 'calls-ofono-provider.c', 'calls-ofono-provider.h', + 'calls-party.c', 'calls-party.h', + 'calls-call-data.c', 'calls-call-data.h', + 'calls-call-holder.c', 'calls-call-holder.h', + 'calls-call-display.c', 'calls-call-display.h', + 'calls-call-selector-item.c', 'calls-call-selector-item.h', + 'calls-main-window.c', 'calls-main-window.h', + 'util.c', 'util.h', + 'main.c' + ] + +enum_sources = gnome.mkenums_simple('enum-types', sources : enum_headers) + +calls_resources = gnome.compile_resources( + 'calls-resources', + 'calls.gresources.xml', + source_dir: 'ui', + c_name: 'call', +) + +# Pass as dependency to another target + +executable('calls', sources, enum_sources, calls_resources, + dependencies : deps, + link_with : gdbofono_lib, + include_directories : topdir_includes) diff --git a/src/ui/call-display.ui b/src/ui/call-display.ui new file mode 100644 index 0000000..f654a1c --- /dev/null +++ b/src/ui/call-display.ui @@ -0,0 +1,156 @@ + + + + + + + + + + + + diff --git a/src/ui/call-selector-item.ui b/src/ui/call-selector-item.ui new file mode 100644 index 0000000..fa3ce17 --- /dev/null +++ b/src/ui/call-selector-item.ui @@ -0,0 +1,71 @@ + + + + + + diff --git a/src/ui/main-window.ui b/src/ui/main-window.ui new file mode 100644 index 0000000..f12a131 --- /dev/null +++ b/src/ui/main-window.ui @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..ecbe315 --- /dev/null +++ b/src/util.c @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "util.h" + +typedef struct +{ + gpointer needle; + guint needle_column; + GtkTreeIter *iter; + gboolean found; +} ListStoreFindData; + +static gboolean +list_store_find_foreach_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + ListStoreFindData *find_data = data; + gpointer value; + + gtk_tree_model_get (model, iter, find_data->needle_column, + &value, -1); + + if (value == find_data->needle) + { + *find_data->iter = *iter; + return (find_data->found = TRUE); + } + + return FALSE; +} + +gboolean +calls_list_store_find (GtkListStore *store, + gpointer needle, + gint needle_column, + GtkTreeIter *iter) +{ + ListStoreFindData find_data + = { needle, needle_column, iter, FALSE }; + + gtk_tree_model_foreach (GTK_TREE_MODEL (store), + list_store_find_foreach_cb, + &find_data); + + return find_data.found; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..85851d3 --- /dev/null +++ b/src/util.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS__UTIL_H__ +#define CALLS__UTIL_H__ + +#include + +G_BEGIN_DECLS + +/* + * For defining simple interface functions + */ +#define CALLS_DEFINE_IFACE_FUNC_BASE(prefix,iface,Prefix,Iface,PREFIX,IFACE,function,rettype,errval) \ + rettype \ + prefix##_##iface##_##function (Prefix##Iface *self) \ + { \ + Prefix##Iface##Interface *i; \ + \ + g_return_val_if_fail (PREFIX##_IS_##IFACE (self), errval); \ + \ + i = PREFIX##_##IFACE##_GET_IFACE (self); \ + g_return_val_if_fail (i-> function != NULL, errval); \ + \ + return i-> function (self); \ + } + + +#define CALLS_DEFINE_IFACE_FUNC_VOID_BASE(prefix,iface,Prefix,Iface,PREFIX,IFACE,function) \ + void \ + prefix##_##iface##_##function (Prefix##Iface *self) \ + { \ + Prefix##Iface##Interface *i; \ + \ + g_return_if_fail (PREFIX##_IS_##IFACE (self)); \ + \ + i = PREFIX##_##IFACE##_GET_IFACE (self); \ + g_return_if_fail (i-> function != NULL); \ + \ + i-> function (self); \ + } + + +#define CALLS_DEFINE_IFACE_FUNC(iface,Iface,IFACE,function,rettype,errval) \ + CALLS_DEFINE_IFACE_FUNC_BASE(calls,iface,Calls,Iface,CALLS,IFACE,function,rettype,errval) + + +#define CALLS_DEFINE_IFACE_FUNC_VOID(iface,Iface,IFACE,function) \ + CALLS_DEFINE_IFACE_FUNC_VOID_BASE(calls,iface,Calls,Iface,CALLS,IFACE,function) + + + +#define CALLS_DISPOSE_OBJECT(obj_ptr) \ + if (obj_ptr) \ + { \ + g_object_unref (G_OBJECT (obj_ptr)); \ + obj_ptr = NULL; \ + } + + +#define CALLS_SET_OBJECT_PROPERTY(obj_ptr,new_value) \ + if (obj_ptr) \ + { \ + g_object_unref (G_OBJECT (obj_ptr)); \ + } \ + obj_ptr = new_value; \ + g_object_ref (G_OBJECT (obj_ptr)); + + +#define CALLS_FREE_PTR_PROPERTY(ptr) \ + if (ptr) \ + { \ + g_free (ptr); \ + } \ + +#define CALLS_SET_PTR_PROPERTY(ptr,new_value) \ + CALLS_FREE_PTR_PROPERTY (ptr) \ + ptr = new_value; + + +#define CALLS_EMIT_MESSAGE(obj,text,type) \ + g_signal_emit_by_name (obj, "message", text, type) + +#define CALLS_EMIT_ERROR(obj,error) \ + CALLS_EMIT_MESSAGE (obj, error->message, GTK_MESSAGE_ERROR) + + +/** Find a particular pointer value in a GtkListStore */ +gboolean +calls_list_store_find (GtkListStore *store, + gpointer needle, + gint needle_column, + GtkTreeIter *iter); + +G_END_DECLS + +#endif /* CALLS__UTIL_H__ */