Browse Source

Merge branch 'bboozzoo/dbus-support'

bboozzoo/travis-ci-ubuntu
Maciek Borzecki 7 years ago
parent
commit
cc8ca6f554
37 changed files with 2458 additions and 5538 deletions
  1. +7
    -0
      .dir-locals.el
  2. +9
    -20
      .travis.yml
  3. +36
    -2
      Makefile.am
  4. +68
    -10
      README.md
  5. +6
    -0
      extra/firewalld/mconnect.xml
  6. +142
    -0
      extra/travis-build
  7. +4
    -0
      gdb-script.in
  8. +140
    -122
      src/crypt/mconn-crypt.c
  9. +22
    -24
      src/crypt/mconn-crypt.h
  10. +107
    -0
      src/mconnect/application.vala
  11. +89
    -0
      src/mconnect/battery-proxy.vala
  12. +17
    -11
      src/mconnect/battery.vala
  13. +1
    -1
      src/mconnect/config.vala
  14. +3
    -3
      src/mconnect/core.vala
  15. +244
    -0
      src/mconnect/device-proxy.vala
  16. +294
    -65
      src/mconnect/device.vala
  17. +18
    -18
      src/mconnect/devicechannel.vala
  18. +170
    -0
      src/mconnect/devicemanager-proxy.vala
  19. +156
    -44
      src/mconnect/devicemanager.vala
  20. +82
    -0
      src/mconnect/discovereddevice.vala
  21. +8
    -8
      src/mconnect/discovery.vala
  22. +3
    -45
      src/mconnect/main.vala
  23. +14
    -10
      src/mconnect/mousepad.vala
  24. +9
    -6
      src/mconnect/notification.vala
  25. +41
    -2
      src/mconnect/packet.vala
  26. +27
    -0
      src/mconnect/packethandlerinterface-proxy.vala
  27. +2
    -0
      src/mconnect/packethandlerinterface.vala
  28. +44
    -0
      src/mconnect/packethandlers-proxy.vala
  29. +33
    -29
      src/mconnect/packethandlers.vala
  30. +52
    -0
      src/mconnect/ping-proxy.vala
  31. +57
    -0
      src/mconnect/ping.vala
  32. +101
    -0
      src/mconnect/property-proxy.vala
  33. +10
    -6
      src/mconnect/telephony.vala
  34. +85
    -0
      src/mconnect/utils.vala
  35. +335
    -0
      src/mconnectctl/main.vala
  36. +22
    -23
      test/mconn-crypt-test.c
  37. +0
    -5089
      vapi/gio-2.0.vapi

+ 7
- 0
.dir-locals.el View File

@ -0,0 +1,7 @@
((c-mode . ((c-file-style . "linux")
(indent-tabs-mode . t)
(show-trailing-whitespace . t)
(c-basic-offset . 4)
(tab-width . 4)
))
)

+ 9
- 20
.travis.yml View File

@ -1,22 +1,11 @@
# default 12.04 is ancient, use trusty instead
dist: trusty
# pretend we're building C
language: c
compiler:
- gcc
services:
- docker
env:
- VALAC=valac-0.32
before_install:
- sudo add-apt-repository -y ppa:vala-team/ppa
- sudo apt-get update -qq
- sudo apt-get install -qq ${VALAC} valac
- sudo apt-get install -qq libgirepository1.0-dev
- sudo apt-get install -qq gir1.2-gee-0.8 libgee-0.8-dev
- sudo apt-get install -qq gir1.2-json-1.0 libjson-glib-dev
- sudo apt-get install -qq gir1.2-notify-0.7 libnotify-dev
- sudo apt-get install -qq gir1.2-atspi-2.0 libatspi2.0-dev
- sudo apt-get install -qq gir1.2-gtk-3.0 libgtk-3-dev
before_script:
- autoreconf -if
matrix:
- DISTRO=fedora
- DISTRO=archlinux
- DISTRO=opensuse
script:
- ./configure && make V=1
- ./extra/travis-build "${DISTRO}"

+ 36
- 2
Makefile.am View File

@ -27,7 +27,8 @@ mconnectdata_DATA = \
mconnect.conf
bin_PROGRAMS = \
mconnect
mconnect \
mconnectctl
noinst_PROGRAMS = \
test-mconn-crypt \
@ -56,16 +57,27 @@ mconnect_SOURCES = \
src/mconnect/discovery.vala \
src/mconnect/packet.vala \
src/mconnect/device.vala \
src/mconnect/discovereddevice.vala \
src/mconnect/device-proxy.vala \
src/mconnect/devicemanager.vala \
src/mconnect/devicemanager-proxy.vala \
src/mconnect/devicechannel.vala \
src/mconnect/core.vala \
src/mconnect/packethandlerinterface.vala \
src/mconnect/packethandlerinterface-proxy.vala \
src/mconnect/packethandlers.vala \
src/mconnect/packethandlers-proxy.vala \
src/mconnect/notification.vala \
src/mconnect/battery.vala \
src/mconnect/battery-proxy.vala \
src/mconnect/telephony.vala \
src/mconnect/mousepad.vala \
src/mconnect/config.vala
src/mconnect/ping.vala \
src/mconnect/ping-proxy.vala \
src/mconnect/config.vala \
src/mconnect/application.vala \
src/mconnect/utils.vala \
src/mconnect/property-proxy.vala
mconnect_LDADD = \
libmconn-crypt.la \
@ -121,6 +133,18 @@ test_mconn_crypt_vala_VALAFLAGS = \
#-------------------------------------------------------------
mconnectctl_SOURCES = \
src/mconnectctl/main.vala
mconnectctl_LDADD = \
$(MCONNECT_LIBS)
mconnectctl_CFLAGS = \
$(MCONNECT_CFLAGS)
#-------------------------------------------------------------
# configure will expand bindir to ${exec_prefix}/bin, we want the
# whole thing, that's why mconnect.desktop is generated here and not
# in configure
@ -139,6 +163,16 @@ git-source-dist:
#-------------------------------------------------------------
run-gdb: gdb-script install
gdb -x gdb-script
.PHONY: run-gdb
gdb-script: gdb-script.in
sed -e 's,[@]bindir[@],${bindir},g' < $< > $@
#-------------------------------------------------------------
GEN_FROM_VALA = $(filter %.vala,$(mconnect_SOURCES))
BUILT_SOURCES = \
mconnect.desktop \


+ 68
- 10
README.md View File

@ -20,17 +20,21 @@ Build dependencies (using package names as found in Fedora):
- gtk3-devel
- at-spi2-core-devel (and at-spi2-atk)
or see `mconnect.spec` in source tree. Once build deps are in place, run:
or see `extra/travis-build` in the source tree for example installation
commands. Once build deps are in place, run:
autoreconf -if
autoreconf -if
./configure --prefix=<your favorite prefix>
make
make install
# or make DESTDIR=<somedir> install if you want to inspect what
# gets installed
# Configuration
**NOTE**: manual configuration file is no longer needed
A sample configuration file is provided in source tree, see
`mconnect.conf`. It will get installed to `${datadir}/mconnect/`
(usually corresponding to `/usr/share/mconnect/`) by default. Once
@ -44,16 +48,70 @@ Android application, only `name` and `type` are used for matching.
# Usage
mconnect comes are 2 separate programs, the daemon - `mconnect` and a D-Bus
client `mconnectctl`.
## The daemon
Start it by running:
mconnect -d
```
$ mconnect -d
```
The daemon starts listens on `0.0.0.0:1714` for incoming UDP packets. Once an
identity packet (a sort of a handshake) is received, a connection to the
sender's address will be made. Known devices are cached in
`~/.cache/mconnect/devices`.
## The client
List discovered devices:
```
$ ./mconnectctl list-devices
Devices:
/org/mconnect/device/0 673ac2db27d2a331 - Motorola Moto G Maciek
```
Accept a device (previously done only through the configuration):
```
$ ./mconnectctl allow-device /org/mconnect/device/0
```
Show device details:
```
$ ./mconnectctl show-device /org/mconnect/device/0
Device
Name: Motorola Moto G Maciek
ID: 673ac2db27d2a331
Address: 192.168.1.103:1716
Type: phone
Allowed: true
Paired: true
Active: true
Connected: true
```
## DBus API
The API is not documented. Use D-Feet or busctl to introspect and invoke methods
manually.
## Gnome Shell
A Gnome Shell extension is available here:
https://github.com/andyholmes/gnome-shell-extension-mconnect or via the GNOME
extensions page: https://extensions.gnome.org/extension/1272/mconnect/
# Operation
# Firewalls
The daemon starts listening on `0.0.0.0:1714` for incoming UDP
packets. Once an identity packet (a sort of a handshake) is received,
a connection at the sender's address will be made only if the device
is listed as `allowed` in `mconnect.conf` (see the sample config).
Should the device be whitelisted in configuration, pairing will happen
automatically.
It may be required to either temporarily disable the firewall or open up UDP
port 1714.
An example service definition for [firewalld](http://www.firewalld.org/) is
provided in `extra/firewalld/mconnect.xml` directory. The file needs to be
copied into `/etc/firewalld/services`.

+ 6
- 0
extra/firewalld/mconnect.xml View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<service>
<short>mconnect</short>
<description>mconnect service configuration</description>
<port port="1714" protocol="udp"/>
</service>

+ 142
- 0
extra/travis-build View File

@ -0,0 +1,142 @@
#!/bin/bash -xe
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# AUTHORS
# Maciek Borzecki <maciek.borzecki (at] gmail.com>
# Travis build script. The script reexecs itself inside the container (setting
# IN_CONTAINER=1). The proceeds to install build dependencies and runs through
# the whole build process. Source tree is bind-mounted at /mnt and the container
# has its workdir set to /mnt
#
# NOTE: it is assumed that the script is run while at the top of source tree
# (i.e. $PWD is your checked out tree, this crucial for properly mounting the
# source code into the container).
deps_fedora() {
dnf install --best -y --refresh \
automake \
autoconf \
libtool \
pkgconfig \
gcc \
vala \
gobject-introspection-devel \
json-glib-devel \
libgee-devel \
openssl-devel \
libnotify-devel \
at-spi2-core-devel \
gtk3-devel
}
deps_opensuse() {
zypper install -y \
make \
automake \
autoconf \
libtool \
pkgconfig \
gcc \
vala \
gobject-introspection-devel \
json-glib-devel \
libgee-devel \
openssl-devel \
libnotify-devel \
at-spi2-core-devel \
gtk3-devel
}
deps_archlinux() {
pacman -Syu --noconfirm \
base-devel \
autoconf \
automake \
libtool \
pkg-config \
gcc \
vala \
glib2 \
gobject-introspection \
json-glib \
libgee \
openssl \
libnotify \
at-spi2-core \
gtk3
}
install_deps() {
case "$1" in
fedora)
deps_fedora
;;
opensuse)
deps_opensuse
;;
archlinux)
deps_archlinux
;;
*)
echo "unsupported distro $1"
exit 1
esac
}
build() {
autoreconf -if
# TODO: out of source builds
./configure
make
make install
}
build_in_container() {
install_deps $1
build
}
spin_container() {
case "$1" in
fedora)
DOCKER_IMG=fedora
;;
archlinux)
DOCKER_IMG=base/archlinux
;;
opensuse)
DOCKER_IMG=opensuse:tumbleweed
;;
*)
echo "unsupported distro $1"
exit 1
esac
# run a container, mount sources at /mnt, st
docker run --rm \
-v $PWD:/mnt \
-w /mnt \
-e IN_CONTAINER=1 \
$DOCKER_IMG \
/mnt/extra/travis-build "$@"
}
if [ "$IN_CONTAINER" = "1" ]; then
build_in_container "$@"
else
spin_container "$@"
fi

+ 4
- 0
gdb-script.in View File

@ -0,0 +1,4 @@
set environment G_MESSAGES_DEBUG = all
set environment G_DEBUG = fatal-criticals
file @bindir@/mconnect
run

+ 140
- 122
src/crypt/mconn-crypt.c View File

@ -1,5 +1,3 @@
/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@ -7,7 +5,7 @@
*
* This program 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
* 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
@ -25,7 +23,7 @@
/* encrypted data padding */
#define MCONN_CRYPT_RSA_PADDING RSA_PKCS1_PADDING
typedef struct _MconnCryptPrivate MconnCryptPrivate;
typedef struct _MconnCryptPrivate MconnCryptPrivate;
/**
* MconnCrypt:
@ -35,13 +33,13 @@ typedef struct _MconnCryptPrivate MconnCryptPrivate;
struct _MconnCrypt
{
GObject parent;
MconnCryptPrivate *priv;
GObject parent;
MconnCryptPrivate *priv;
};
struct _MconnCryptPrivate
{
RSA *key; /* RSA key wrapper */
RSA *key; /* RSA key wrapper */
};
static void mconn_crypt_dispose (GObject *object);
@ -55,108 +53,108 @@ G_DEFINE_TYPE_WITH_PRIVATE (MconnCrypt, mconn_crypt, G_TYPE_OBJECT);
static void
mconn_crypt_class_init (MconnCryptClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *)klass;
GObjectClass *gobject_class = (GObjectClass *)klass;
gobject_class->dispose = mconn_crypt_dispose;
gobject_class->finalize = mconn_crypt_finalize;
gobject_class->dispose = mconn_crypt_dispose;
gobject_class->finalize = mconn_crypt_finalize;
}
static void
mconn_crypt_init (MconnCrypt *self)
{
g_debug("mconn-crypt: new instance");
self->priv = mconn_crypt_get_instance_private(self);
g_debug("mconn-crypt: new instance");
self->priv = mconn_crypt_get_instance_private(self);
}
static void
mconn_crypt_dispose (GObject *object)
{
MconnCrypt *self = (MconnCrypt *)object;
MconnCrypt *self = (MconnCrypt *)object;
if (self->priv->key != NULL)
{
RSA_free(self->priv->key);
self->priv->key = NULL;
}
if (self->priv->key != NULL)
{
RSA_free(self->priv->key);
self->priv->key = NULL;
}
G_OBJECT_CLASS (mconn_crypt_parent_class)->dispose (object);
G_OBJECT_CLASS (mconn_crypt_parent_class)->dispose (object);
}
static void
mconn_crypt_finalize (GObject *object)
{
MconnCrypt *self = (MconnCrypt *)object;
MconnCrypt *self = (MconnCrypt *)object;
g_signal_handlers_destroy (object);
G_OBJECT_CLASS (mconn_crypt_parent_class)->finalize (object);
g_signal_handlers_destroy (object);
G_OBJECT_CLASS (mconn_crypt_parent_class)->finalize (object);
}
MconnCrypt *mconn_crypt_new_for_key_path(const char *path)
{
g_debug("mconn-crypt: new crypt for key %s", path);
g_debug("mconn-crypt: new crypt for key %s", path);
MconnCrypt *self = g_object_new(MCONN_TYPE_CRYPT, NULL);
MconnCrypt *self = g_object_new(MCONN_TYPE_CRYPT, NULL);
if (g_file_test(path, G_FILE_TEST_EXISTS) == FALSE)
__mconn_generate_key_at_path(path);
if (g_file_test(path, G_FILE_TEST_EXISTS) == FALSE)
__mconn_generate_key_at_path(path);
if (__mconn_load_key(self->priv, path) == FALSE)
{
mconn_crypt_unref(self);
return NULL;
}
if (__mconn_load_key(self->priv, path) == FALSE)
{
mconn_crypt_unref(self);
return NULL;
}
return self;
return self;
}
MconnCrypt * mconn_crypt_ref(MconnCrypt *self)
{
g_assert(IS_MCONN_CRYPT(self));
return MCONN_CRYPT(g_object_ref(self));
g_assert(IS_MCONN_CRYPT(self));
return MCONN_CRYPT(g_object_ref(self));
}
void mconn_crypt_unref(MconnCrypt *self)
{
if (self != NULL)
{
g_assert(IS_MCONN_CRYPT(self));
g_object_unref(self);
}
if (self != NULL)
{
g_assert(IS_MCONN_CRYPT(self));
g_object_unref(self);
}
}
GByteArray * mconn_crypt_decrypt(MconnCrypt *self, GBytes *data, GError **err)
{
g_assert(IS_MCONN_CRYPT(self));
g_assert(self->priv->key);
g_assert(IS_MCONN_CRYPT(self));
g_assert(self->priv->key);
g_debug("decrypt: %zu bytes of data", g_bytes_get_size(data));
/* g_debug("decrypt: %zu bytes of data", g_bytes_get_size(data)); */
g_assert_cmpint(g_bytes_get_size(data), ==, RSA_size(self->priv->key));
g_assert_cmpint(g_bytes_get_size(data), ==, RSA_size(self->priv->key));
/* decrypted data is less than RSA_size() long */
gsize out_buf_size = RSA_size(self->priv->key);
GByteArray *out_data = g_byte_array_sized_new(out_buf_size);
/* decrypted data is less than RSA_size() long */
gsize out_buf_size = RSA_size(self->priv->key);
GByteArray *out_data = g_byte_array_sized_new(out_buf_size);
int dec_size;
dec_size = RSA_private_decrypt(g_bytes_get_size(data),
g_bytes_get_data(data, NULL),
(unsigned char *)out_data->data,
self->priv->key,
MCONN_CRYPT_RSA_PADDING);
g_debug("decrypted size: %d", dec_size);
g_assert(dec_size != -1);
int dec_size;
dec_size = RSA_private_decrypt(g_bytes_get_size(data),
g_bytes_get_data(data, NULL),
(unsigned char *)out_data->data,
self->priv->key,
MCONN_CRYPT_RSA_PADDING);
/* g_debug("decrypted size: %d", dec_size); */
g_assert(dec_size != -1);
g_byte_array_set_size(out_data, dec_size);
g_byte_array_set_size(out_data, dec_size);
return out_data;
return out_data;
}
gchar *mconn_crypt_get_public_key_pem(MconnCrypt *self)
{
g_assert(IS_MCONN_CRYPT(self));
g_assert(self->priv);
g_assert(self->priv->key);
return __mconn_get_public_key_as_pem(self->priv);
g_assert(IS_MCONN_CRYPT(self));
g_assert(self->priv);
g_assert(self->priv->key);
return __mconn_get_public_key_as_pem(self->priv);
}
/**
@ -164,89 +162,109 @@ gchar *mconn_crypt_get_public_key_pem(MconnCrypt *self)
*/
static gchar *__mconn_get_public_key_as_pem(MconnCryptPrivate *priv)
{
gchar *pubkey = NULL;
gchar *pubkey = NULL;
/* memory IO */
BIO *bm = BIO_new(BIO_s_mem());
/* memory IO */
BIO *bm = BIO_new(BIO_s_mem());
/* generate PEM */
/* PEM_write_bio_RSAPublicKey(bm, priv->key); */
PEM_write_bio_RSA_PUBKEY(bm, priv->key);
/* generate PEM */
/* PEM_write_bio_RSAPublicKey(bm, priv->key); */
PEM_write_bio_RSA_PUBKEY(bm, priv->key);
/* get PEM as text */
char *oss_pubkey = NULL;
long data = BIO_get_mem_data(bm, &oss_pubkey);
g_debug("mconn-crypt: public key length: %ld", data);
g_assert(data != 0);
g_assert(oss_pubkey != NULL);
/* get PEM as text */
char *oss_pubkey = NULL;
long data = BIO_get_mem_data(bm, &oss_pubkey);
g_debug("mconn-crypt: public key length: %ld", data);
g_assert(data != 0);
g_assert(oss_pubkey != NULL);
/* dup the key as buffer goes away with BIO */
pubkey = g_strndup(oss_pubkey, data);
/* dup the key as buffer goes away with BIO */
pubkey = g_strndup(oss_pubkey, data);
BIO_set_close(bm, BIO_CLOSE);
BIO_free(bm);
BIO_set_close(bm, BIO_CLOSE);
BIO_free(bm);
return pubkey;
return pubkey;
}
static gboolean __mconn_load_key(MconnCryptPrivate *priv, const char *path)
{
if (g_file_test(path, G_FILE_TEST_EXISTS) == FALSE)
{
g_critical("mconn-crypt: key file %s does not exist", path);
return FALSE;
}
if (g_file_test(path, G_FILE_TEST_EXISTS) == FALSE)
{
g_critical("mconn-crypt: key file %s does not exist", path);
return FALSE;
}
g_debug("mconn-crypt: loading key from %s", path);
g_debug("mconn-crypt: loading key from %s", path);
BIO *bf = BIO_new_file(path, "r");
BIO *bf = BIO_new_file(path, "r");
if (bf == NULL)
{
g_critical("mconn-crypt: failed to open file %s", path);
return FALSE;
}
if (bf == NULL)
{
g_critical("mconn-crypt: failed to open file %s", path);
return FALSE;
}
RSA *rsa = NULL;
RSA *rsa = NULL;
rsa = PEM_read_bio_RSAPrivateKey(bf, NULL, NULL, NULL);
rsa = PEM_read_bio_RSAPrivateKey(bf, NULL, NULL, NULL);
BIO_free(bf);
BIO_free(bf);
if (rsa == NULL)
{
g_critical("mconn-crypt: failed to read private key");
return FALSE;
}
if (rsa == NULL)
{
g_critical("mconn-crypt: failed to read private key");
return FALSE;
}
priv->key = rsa;
priv->key = rsa;
return TRUE;
return TRUE;
}
static gboolean __mconn_generate_key_at_path(const char *path)
{
gboolean ret = TRUE;
RSA *rsa = NULL;
BIO *bf = BIO_new_file(path, "w");
if (bf == NULL)
{
g_error("mconn-crypt: failed to open file");
return FALSE;
}
rsa = RSA_generate_key(2048, RSA_3, NULL, NULL);
if (PEM_write_bio_RSAPrivateKey(bf, rsa, NULL, NULL, 0, NULL, NULL) == 0)
{
g_critical("mconn-crypt: failed to private write key to file");
ret = FALSE;
}
RSA_free(rsa);
BIO_free(bf);
return ret;
gboolean ret = FALSE;
RSA *rsa = NULL;
BIO *bf = NULL;
BIGNUM *e = NULL;
int res = 0;
rsa = RSA_new();
g_return_val_if_fail(rsa != NULL, FALSE);
e = BN_new();
if (e == NULL)
{
goto cleanup;
}
BN_set_word(e, RSA_3);
if (RSA_generate_key_ex(rsa, 2048, e, NULL) != 1) {
g_critical("mconn-crypt: failed to generate RSA key");
goto cleanup;
}
bf = BIO_new_file(path, "w");
if (bf == NULL)
{
g_error("mconn-crypt: failed to open file");
goto cleanup;
}
if (PEM_write_bio_RSAPrivateKey(bf, rsa, NULL, NULL, 0, NULL, NULL) == 0)
{
g_critical("mconn-crypt: failed to private write key to file");
goto cleanup;
}
ret = TRUE;
cleanup:
BN_free(e);
RSA_free(rsa);
BIO_free(bf);
return ret;
}

+ 22
- 24
src/crypt/mconn-crypt.h View File

@ -1,5 +1,3 @@
/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@ -7,7 +5,7 @@
*
* This program 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
* 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
@ -25,32 +23,32 @@
G_BEGIN_DECLS
#define MCONN_TYPE_CRYPT \
#define MCONN_TYPE_CRYPT \
(mconn_crypt_get_type())
#define MCONN_CRYPT(obj) \
(G_TYPE_CHECK_INSTANCE_CAST ((obj), \
MCONN_TYPE_CRYPT, \
MconnCrypt))
#define MCONN_CRYPT_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST ((klass), \
MCONN_TYPE_CRYPT, \
MconnCryptClass))
#define IS_MCONN_CRYPT(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
MCONN_TYPE_CRYPT))
#define IS_MCONN_CRYPT_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE ((klass), \
MCONN_TYPE_CRYPT))
#define MCONN_CRYPT_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj), \
MCONN_TYPE_CRYPT, \
MconnCryptClass))
#define MCONN_CRYPT(obj) \
(G_TYPE_CHECK_INSTANCE_CAST ((obj), \
MCONN_TYPE_CRYPT, \
MconnCrypt))
#define MCONN_CRYPT_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST ((klass), \
MCONN_TYPE_CRYPT, \
MconnCryptClass))
#define IS_MCONN_CRYPT(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
MCONN_TYPE_CRYPT))
#define IS_MCONN_CRYPT_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE ((klass), \
MCONN_TYPE_CRYPT))
#define MCONN_CRYPT_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj), \
MCONN_TYPE_CRYPT, \
MconnCryptClass))
typedef struct _MconnCrypt MconnCrypt;
typedef struct _MconnCrypt MconnCrypt;
typedef struct _MconnCryptClass MconnCryptClass;
struct _MconnCryptClass
{
GObjectClass parent_class;
GObjectClass parent_class;
};
GType mconn_crypt_get_type (void) G_GNUC_CONST;


+ 107
- 0
src/mconnect/application.vala View File

@ -0,0 +1,107 @@
/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
namespace Mconn {
public class Application : GLib.Application {
private Core core = null;
private static bool log_debug = false;
private static bool log_debug_verbose = false;
private const GLib.OptionEntry[] options = {
{"debug", 'd', 0, OptionArg.NONE, ref log_debug,
"Show debug output", null},
{"verbose-debug", 0, 0, OptionArg.NONE, ref log_debug_verbose,
"Show verbose debug output", null},
{null}
};
private Discovery discovery = null;
private DeviceManager manager = null;
private DeviceManagerDBusProxy bus_manager = null;
public Application() {
Object(application_id: "org.mconnect");
add_main_option_entries(options);
discovery = new Discovery();
manager = new DeviceManager();
}
protected override void startup() {
debug("startup");
base.startup();
if (log_debug == true)
Environment.set_variable("G_MESSAGES_DEBUG", "all", false);
if (log_debug_verbose == true)
enable_vdebug();
core = Core.instance();
if (core == null)
error("cannot initialize core");
if (core.config.is_debug_on() == true)
Environment.set_variable("G_MESSAGES_DEBUG", "all", false);
Notify.init("mconnect");
discovery.device_found.connect((disc, discdev) => {
manager.handle_discovered_device(discdev);
});
try {
discovery.listen();
} catch (Error e) {
message("failed to setup device listener: %s", e.message);
}
}
protected override void activate() {
debug("activate");
// reload devices from cache
manager.load_cache();
hold();
}
public override bool dbus_register(DBusConnection conn,
string object_path) throws Error {
this.bus_manager = new DeviceManagerDBusProxy.with_manager(conn,
this.manager);
this.bus_manager.publish();
base.dbus_register(conn, object_path);
debug("dbus register, path %s", object_path);
return true;
}
public override void dbus_unregister(DBusConnection conn,
string object_path) {
base.dbus_unregister(conn, object_path);
debug("dbus unregister, path %s", object_path);
}
}
}

+ 89
- 0
src/mconnect/battery-proxy.vala View File

@ -0,0 +1,89 @@
/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
[DBus (name = "org.mconnect.Device.Battery")]
class BatteryHandlerProxy : Object, PacketHandlerInterfaceProxy {
private Device device = null;
private BatteryHandler battery_handler = null;
private uint register_id = 0;
private ulong notify_id = 0;
private DBusPropertyNotifier prop_notifier = null;
public uint level { get; private set; default = 0; }
public bool charging { get; private set; default = false; }
public BatteryHandlerProxy.for_device_handler(Device dev,
PacketHandlerInterface iface) {
this.device = dev;
this.battery_handler = (BatteryHandler) iface;
this.battery_handler.battery.connect(this.battery_change);
}
private void battery_change(Device dev, uint level, bool charging) {
if (this.device != dev)
return;
this.level = level;
this.charging = charging;
}
[DBus (visible = false)]
public void bus_register(DBusConnection conn, string path) throws IOError {
if (this.register_id == 0)
this.register_id = conn.register_object(path, this);
this.prop_notifier = new DBusPropertyNotifier(conn,
"org.mconnect.Device.Battery",
path);
this.notify.connect(this.send_property_change);
}
[DBus (visible = false)]
public void bus_unregister(DBusConnection conn) throws IOError {
if (this.register_id != 0)
conn.unregister_object(this.register_id);
this.register_id = 0;
this.notify.disconnect(this.send_property_change);
this.notify_id = 0;
}
private void send_property_change(ParamSpec p) {
assert(this.prop_notifier != null);
Variant v = null;
if (p.name == "level") {
v = this.level;
}
if (p.name == "charging") {
v = this.charging;
}
if (v == null)
return;
this.prop_notifier.queue_property_change(p.name, v);
}
}

+ 17
- 11
src/mconnect/battery.vala View File

@ -20,7 +20,7 @@
class BatteryHandler : Object, PacketHandlerInterface {
private const string BATTERY = "kdeconnect.battery";
public const string BATTERY = "kdeconnect.battery";
public string get_pkt_type() {
return BATTERY;
@ -36,22 +36,28 @@ class BatteryHandler : Object, PacketHandlerInterface {
public void use_device(Device dev) {
debug("use device %s for battery status updates", dev.to_string());
dev.message.connect((d, pkt) => {
debug("message signal");
if (pkt.pkt_type == BATTERY) {
debug("is battery packet");
this.message(pkt);
}
});
dev.message.connect(this.message);
}
public void message(Packet pkt) {
public void release_device(Device dev) {
debug("release device %s", dev.to_string());
dev.message.disconnect(this.message);
}
public void message(Device dev, Packet pkt) {
if (pkt.pkt_type != BATTERY) {
return;
}
debug("got battery packet");
int64 level = pkt.body.get_int_member("currentCharge");
bool charging = pkt.body.get_boolean_member("isCharging");
GLib.message("battery level: %u %s", (uint) level,
(charging == true) ? "charging" : "");
debug("battery level: %u %s", (uint) level,
(charging == true) ? "charging" : "");
battery(dev, (uint)level, charging);
}
public signal void battery(Device dev, uint level, bool charging);
}

+ 1
- 1
src/mconnect/config.vala View File

@ -19,7 +19,7 @@
*/
public class Config : Object {
public static const string FILE = "mconnect.conf";
public const string FILE = "mconnect.conf";
private KeyFile _kf = null;


+ 3
- 3
src/mconnect/core.vala View File

@ -21,7 +21,7 @@ using Mconn;
class Core : Object {
public static const string APP_NAME = "mconnect";
public const string APP_NAME = "mconnect";
public Crypt crypt { get; private set; default = null; }
@ -49,8 +49,8 @@ class Core : Object {
core.crypt = crypt;
core.handlers = handlers;
info("supported interfaces: %s", string.joinv(", ",
handlers.interfaces));
info("supported interfaces: %s",
string.joinv(", ", handlers.interfaces));
Core._instance = core;
}


+ 244
- 0
src/mconnect/device-proxy.vala View File

@ -0,0 +1,244 @@
/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
using Gee;
/**
* General device wrapper.
*/
[DBus (name = "org.mconnect.Device")]
class DeviceDBusProxy : Object {
public string id {
get { return device.device_id; }
private set {}
default = "";
}
public string name {
get { return device.device_name; }
private set {}
default = "";
}
public string device_type {
get { return device.device_type; }
private set {}
default = "";
}
public uint protocol_version {
get { return device.protocol_version; }
private set {}
default = 5;
}
public string address { get; private set; default = ""; }
public bool is_paired {
get { return device.is_paired; }
private set {}
default = false;
}
public bool allowed {
get { return device.allowed; }
private set {}
default = false;
}
public bool is_active {
get { return device.is_active; }
private set {}
default = false;
}
public bool is_connected { get; private set; default = false; }
public string[] incoming_capabilities {
get;
private set;
}
public string[] outgoing_capabilities {
get;
private set;
}
private HashMap<string,PacketHandlerInterfaceProxy> handlers;
private uint register_id = 0;
private DBusPropertyNotifier prop_notifier = null;
[DBus (visible = false)]
public ObjectPath object_path = null;
[DBus (visible = false)]
public Device device {get; private set; default = null; }
public DeviceDBusProxy.for_device_with_path(Device device, ObjectPath path) {
this.device = device;
this.object_path = path;
this.handlers = new HashMap<string, PacketHandlerInterfaceProxy>();
this.update_address();
this.update_capabilities();
this.device.notify.connect(this.param_changed);
this.device.connected.connect(() => {
this.is_connected = true;
});
this.device.disconnected.connect(() => {
this.is_connected = false;
});
this.notify.connect(this.update_properties);
}
private void update_capabilities() {
string[] caps = {};
foreach (var cap in device.incoming_capabilities) {
caps += cap;
}
this.incoming_capabilities = caps;
caps = {};
foreach (var cap in device.outgoing_capabilities) {
caps += cap;
}
this.outgoing_capabilities = caps;
}
private void update_address() {
this.address = "%s:%u".printf(device.host.to_string(),
device.tcp_port);
}
private void update_properties(ParamSpec param) {
debug("param %s changed", param.name);
string name = param.name;
Variant v = null;
switch (param.name) {
case "address":
v = this.address;
break;
case "id":
v = this.id;
break;
case "name":
v = this.name;
break;
case "device-type":
name = "DeviceType";
v = this.device_type;
break;
case "potocol-version":
name = "ProtocolVersion";
v = this.protocol_version;
break;
case "is-paired":
name = "IsPaired";
v = this.is_paired;
break;
case "allowed":
v = this.allowed;
break;
case "is-active":
name = "IsActive";
v = this.is_active;
break;
case "is-connected":
name = "IsConnected";
v = this.is_connected;
break;
}
if (v == null)
return;
this.prop_notifier.queue_property_change(name, v);
}
private void param_changed(ParamSpec param) {
debug("parameter %s changed", param.name);
switch (param.name) {
case "host":
case "tcp-port":
this.update_address();
break;
case "allowed":
this.allowed = device.allowed;
break;
case "is-active":
this.is_active = device.is_active;
break;
case "is-paired":
this.is_paired = device.is_paired;
break;
case "incoming-capabilities":
case "outgoing-capabilities":
this.update_capabilities();
break;
}
}
[DBus (visible = false)]
public bool has_handler(string cap) {
return this.handlers.has_key(cap);
}
[DBus (visible = false)]
public void bus_register(DBusConnection conn) {
try {
this.register_id = conn.register_object(this.object_path, this);
this.prop_notifier = new DBusPropertyNotifier(conn,
"org.mconnect.Device",
this.object_path);
} catch (IOError err) {
warning("failed to register DBus object for device %s under path %s",
this.device.to_string(), this.object_path.to_string());
}
}
[DBus (visible = false)]
public void bus_unregister(DBusConnection conn) {
if (this.register_id != 0) {
conn.unregister_object(this.register_id);
}
this.register_id = 0;
this.prop_notifier = null;
}
[DBus (visible = false)]
public void bus_register_handler(DBusConnection conn,
string cap,
PacketHandlerInterfaceProxy handler) {
handler.bus_register(conn, this.object_path);
this.handlers.@set(cap, handler);
}
[DBus (visible = false)]
public void bus_unregister_handler(DBusConnection conn,
string cap) {
PacketHandlerInterfaceProxy handler;
this.handlers.@unset(cap, out handler);
if (handler != null) {
handler.bus_unregister(conn);
}
}
}

+ 294
- 65
src/mconnect/device.vala View File

@ -18,15 +18,33 @@
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
using Gee;
/**
* General device wrapper.
*/
class Device : Object {
public const uint PAIR_TIMEOUT = 30;
public signal void paired(bool pair);
public signal void connected();
public signal void disconnected();
public signal void message(Packet pkt);
/**
* capability_added:
* @cap: device capability, eg. kdeconnect.notification
*
* Device capability was added
*/
public signal void capability_added(string cap);
/**
* capability_removed:
* @cap: device capability, eg. kdeconnect.notification
*
* Device capability was removed
*/
public signal void capability_removed(string cap);
public string device_id { get; private set; default = ""; }
public string device_name { get; private set; default = ""; }
@ -35,14 +53,37 @@ class Device : Object {
public uint tcp_port {get; private set; default = 1714; }
public InetAddress host { get; private set; default = null; }
public bool is_paired { get; private set; default = false; }
public bool allowed {get; set; default = false; }
public bool is_active { get; private set; default = false; }
public ArrayList<string> outgoing_capabilities {
get;
private set;
default = null;
}
public ArrayList<string> incoming_capabilities {
get;
private set;
default = null;
}
private HashSet<string> _capabilities = null;
public string public_key {get; private set; default = ""; }
// set to true if pair request was sent
private bool _pair_in_progress = false;
private uint _pair_timeout_source = 0;
private DeviceChannel _channel = null;
private Device() {
// registered packet handlers
private HashMap<string, PacketHandlerInterface> _handlers;
private Device() {
incoming_capabilities = new ArrayList<string>();
outgoing_capabilities = new ArrayList<string>();
_capabilities = new HashSet<string>();
_handlers = new HashMap<string, PacketHandlerInterface>();
}
/**
@ -51,19 +92,21 @@ class Device : Object {
* @param pkt identity packet
* @param host source host that the packet came from
*/
public Device.from_identity(Packet pkt, InetAddress host) {
debug("got packet: %s", pkt.to_string());
var body = pkt.body;
this.host = host;
this.device_name = body.get_string_member("deviceName");
this.device_id = body.get_string_member("deviceId");
this.device_type = body.get_string_member("deviceType");
this.protocol_version = (int) body.get_int_member("protocolVersion");
this.tcp_port = (uint) body.get_int_member("tcpPort");
debug("added new device: %s", this.to_string());
public Device.from_discovered_device(DiscoveredDevice disc) {
this();
this.host = disc.host;
this.device_name = disc.device_name;
this.device_id = disc.device_id;
this.device_type = disc.device_type;
this.protocol_version = disc.protocol_version;
this.tcp_port = disc.tcp_port;
this.outgoing_capabilities = new ArrayList<string>.wrap(
disc.outgoing_capabilities);
this.incoming_capabilities = new ArrayList<string>.wrap(
disc.incoming_capabilities);
debug("new device: %s", this.to_string());
}
/**
@ -85,6 +128,15 @@ class Device : Object {
dev.tcp_port = (uint) cache.get_integer(name, "tcpPort");
var last_ip_str = cache.get_string(name, "lastIPAddress");
debug("last known address: %s:%u", last_ip_str, dev.tcp_port);
dev.allowed = cache.get_boolean(name, "allowed");
dev.is_paired = cache.get_boolean(name, "paired");
dev.public_key = cache.get_string(name, "public_key");
dev.outgoing_capabilities = new ArrayList<string>.wrap(
cache.get_string_list(name,
"outgoing_capabilities"));
dev.incoming_capabilities = new ArrayList<string>.wrap(
cache.get_string_list(name,
"incoming_capabilities"));
var host = new InetAddress.from_string(last_ip_str);
if (host == null) {
@ -93,6 +145,7 @@ class Device : Object {
return null;
}
dev.host = host;
return dev;
}
catch (KeyFileError e) {
@ -109,12 +162,15 @@ class Device : Object {
* Generates a unique string for this device
*/
public string to_unique_string() {
return this.to_string().replace(" ", "-");
return make_unique_device_string(this.device_id,
this.device_name,
this.device_type,
this.protocol_version);
}
public string to_string() {
return "%s-%s-%s-%u".printf(this.device_id, this.device_name,
this.device_type, this.protocol_version);
return make_device_string(this.device_id, this.device_name,
this.device_type, this.protocol_version);
}
/**
@ -130,6 +186,13 @@ class Device : Object {
cache.set_integer(name, "protocolVersion", (int) this.protocol_version);
cache.set_integer(name, "tcpPort", (int) this.tcp_port);
cache.set_string(name, "lastIPAddress", this.host.to_string());
cache.set_boolean(name, "allowed", this.allowed);
cache.set_boolean(name, "paired", this.is_paired);
cache.set_string(name, "public_key", this.public_key);
cache.set_string_list(name, "outgoing_capabilities",
array_list_to_list(this.outgoing_capabilities));
cache.set_string_list(name, "incoming_capabilities",
array_list_to_list(this.incoming_capabilities));
}
private async void greet() {
@ -140,7 +203,7 @@ class Device : Object {
Environment.get_host_name(),
core.handlers.interfaces,
core.handlers.interfaces));
this.pair_if_needed();
this.maybe_pair();
}
/**
@ -158,15 +221,42 @@ class Device : Object {
string pubkey = core.crypt.get_public_key_pem();
debug("public key: %s", pubkey);
if (expect_response == true)
if (expect_response == true) {
_pair_in_progress = true;
// pairing timeout
_pair_timeout_source = Timeout.add_seconds(PAIR_TIMEOUT,
this.pair_timeout);
}
// send request
yield _channel.send(Packet.new_pair(pubkey));
}
}
public void pair_if_needed() {
if (is_paired == false && _pair_in_progress == false)
this.pair.begin();
private bool pair_timeout() {
warning("pair request timeout");
_pair_timeout_source = 0;
// handle failed pairing
handle_pair(false, "");
// remove timeout source
return false;
}
/**
* maybe_pair:
*
* Trigger pairing or call handle_pair() if already paired.
*/
public void maybe_pair() {
if (is_paired == false) {
if (_pair_in_progress == false)
this.pair.begin();
} else {
// we are already paired
handle_pair(true, this.public_key);
}
}
/**
@ -176,7 +266,9 @@ class Device : Object {
* successfuly opening a connection.
*/
public void activate() {
assert(_channel == null);
if (_channel != null) {
debug("device %s already active", this.to_string());
}
var core = Core.instance();
_channel = new DeviceChannel(this.host, this.tcp_port,
@ -190,6 +282,8 @@ class Device : Object {
_channel.open.begin((c, res) => {
this.channel_openend(_channel.open.end(res));
});
this.is_active = true;
}
/**
@ -198,35 +292,8 @@ class Device : Object {
* Deactivate device
*/
public void deactivate() {
if (_channel != null)
_channel.close.begin((c) => {
channel_closed_cleanup();
});
}
/**
* activate_from_device:
*
* Try to activate using a newly discovered device. If device is
* already active, compare the host address to see if it
* changed. If so, close the current connection and activate with
* new address.
*
* @param dev device
*/
public void activate_from_device(Device dev) {
if (host == null) {
host = dev.host;
tcp_port = dev.tcp_port;
activate();
} else if (dev.host.to_string() != host.to_string()) {
deactivate();
host = dev.host;
tcp_port = dev.tcp_port;
activate();
} else {
// same host, assuming no activation needed
debug("device %s already active", dev.to_string());
if (_channel != null) {
close_and_cleanup();
}
}
@ -238,6 +305,9 @@ class Device : Object {
*/
private void channel_openend(bool result) {
debug("channel openend: %s", result.to_string());
connected();
if (result == true) {
greet.begin();
} else {
@ -247,7 +317,7 @@ class Device : Object {
}
private void packet_received(Packet pkt) {
debug("got packet");
vdebug("got packet");
if (pkt.pkt_type == Packet.PAIR) {
// pairing
handle_pair_packet(pkt);
@ -255,13 +325,13 @@ class Device : Object {
// we sent a pair request, but got another packet,
// supposedly meaning we're alredy paired since the device
// is sending us data
if (_pair_in_progress == true) {
_pair_in_progress = false;
// just to be clear, send paired signal
paired(true);
if (this.is_paired == false) {
warning("not paired and still got a packet, " +
"assuming device is paired",
Packet.PAIR);
handle_pair(true, "");
}
debug("signal packet");
// emit signal
message(pkt);
}
@ -278,28 +348,65 @@ class Device : Object {
assert(pkt.pkt_type == Packet.PAIR);
bool pair = pkt.body.get_boolean_member("pair");
string public_key = "";
if (pair) {
public_key = pkt.body.get_string_member("publicKey");
}
handle_pair(pair, public_key);
}
/**
* handle_pair:
* @pair: pairing status
* @public_key: device public key
*
* Update device pair status.
*/
private void handle_pair(bool pair, string public_key) {
if (this._pair_timeout_source != 0) {
Source.remove(_pair_timeout_source);
this._pair_timeout_source = 0;
}
debug("pair in progress: %s is paired: %s pair: %s",
_pair_in_progress.to_string(), this.is_paired.to_string(),
pair.to_string());
if (_pair_in_progress == true) {
// response to host initiated pairing
if (pair == true) {
debug("device is paired, pairing complete");
this.is_paired = true;
} else {
critical("pairing rejected by device");
warning("pairing rejected by device");
this.is_paired = false;
}
// pair completed
_pair_in_progress = false;
} else {
debug("unsolicited pair change from device");
debug("unsolicited pair change from device, pair status: %s",
pair.to_string());
if (pair == false) {
// unpair from device
this.is_paired = false;
} else {
// pair request from device
// split brain, pair was not initiated by us, but we were called
// with information that we are paired, assume we are paired and
// send a pair packet, but not expecting a response this time
this.pair.begin(false);
this.is_paired = true;
}
}
if (pair) {
// update public key
this.public_key = public_key;
} else {
this.public_key = "";
}
// emit signal
paired(is_paired);
}
@ -312,9 +419,12 @@ class Device : Object {
private void handle_disconnect() {
// channel got disconnected
debug("channel disconnected");
_channel.close.begin((c) => {
channel_closed_cleanup();
});
close_and_cleanup();
}
private void close_and_cleanup() {
_channel.close();
channel_closed_cleanup();
}
/**
@ -323,9 +433,128 @@ class Device : Object {
* Single cleanup point after channel has been closed
*/
private void channel_closed_cleanup() {
debug("close cleanup");
_channel = null;
_host = null;
this.is_active = false;
// emit disconnected
disconnected();
}
/**
* register_capability_handler:
* @cap: capability, eg. kdeconnect.notification
* @h: packet handler
*
* Keep track of capability handler @h that supports capability @cap.
* Register oneself with capability handler.
*/
public void register_capability_handler(string cap,
PacketHandlerInterface h) {
assert(this.has_capability_handler(cap) == false);
this._handlers.@set(cap, h);
// make handler connect to device
h.use_device(this);
}
/**
* has_capability_handler:
* @cap: capability, eg. kdeconnect.notification
*
* Returns true if there is a handler of capability @cap registed for this
* device.
*/
public bool has_capability_handler(string cap) {
return this._handlers.has_key(cap);
}
/**
* unregister_capability_handler:
* @cap: capability, eg. kdeconnect.notification
*
* Unregisters a handler for capability @cap.
*/
private void unregister_capability_handler(string cap) {
PacketHandlerInterface handler;
this._handlers.unset(cap, out handler);
if (handler != null) {
// make handler release the device
handler.release_device(this);
}
}
/**
* merge_capabilities:
* @added[out]: capabilities that were added
* @removed[out]: capabilities that were removed
*
* Merge and update existing `outgoing_capabilities` and
* `incoming_capabilities`. Returns lists of added and removed capabilities.
*/
private void merge_capabilities(out HashSet<string> added,
out HashSet<string> removed) {
var caps = new HashSet<string>();
caps.add_all(this.outgoing_capabilities);
caps.add_all(this.incoming_capabilities);
added = new HashSet<string>();
added.add_all(caps);
// TODO: simplify capability names, eg kdeconnect.telephony.request ->
// kdeconnect.telephony
added.remove_all(this._capabilities);
removed = new HashSet<string>();
removed.add_all(this._capabilities);
removed.remove_all(caps);
this._capabilities = caps;
}
/**
* update_from_device:
* @other_dev: other device
*
* Update information/state of this device using data from @other_dev. This
* may happen in case when a discovery packet was received, or a device got
* connected. In such case, a `this` device (which was likely created from
* cached data) needs to be updated.
*
* As a side effect, updating capabilities will emit @capability_added
* and @capability_removed signals.
*/
public void update_from_device(Device other_dev) {
this.outgoing_capabilities = other_dev.outgoing_capabilities;
this.incoming_capabilities = other_dev.incoming_capabilities;
HashSet<string> added;
HashSet<string> removed;
this.merge_capabilities(out added, out removed);
foreach (var c in added) {
debug("added: %s", c);
capability_added(c);
}
foreach (var c in removed) {
debug("removed: %s", c);
capability_removed(c);
// remove capability handlers
this.unregister_capability_handler(c);
}
if (this.host != null && this.host.to_string() != other_dev.host.to_string()) {
debug("host address changed from %s to %s",
this.host.to_string(), other_dev.host.to_string());
// deactivate first
this.deactivate();
host = other_dev.host;
tcp_port = other_dev.tcp_port;
}
}
}

+ 18
- 18
src/mconnect/devicechannel.vala View File

@ -59,7 +59,7 @@ class DeviceChannel : Object {
_conn = yield client.connect_async(_isa);
} catch (Error e) {
//
critical("failed to connect to %s:%u: %s",
warning("failed to connect to %s:%u: %s",
_isa.address.to_string(), _isa.port,
e.message);
// emit disconnected
@ -108,7 +108,7 @@ class DeviceChannel : Object {
// enable keepalive
sock.set_keepalive(true);
// prep source for monitoring events
var source = sock.create_socket_source(IOCondition.IN);
var source = sock.create_source(IOCondition.IN);
source.set_callback((src, cond) => {
return this._io_ready(cond);
});
@ -118,7 +118,7 @@ class DeviceChannel : Object {
return true;
}
public async void close() {
public void close() {
debug("closing connection");
if (_srcid > 0) {
@ -130,19 +130,19 @@ class DeviceChannel : Object {
if (_din != null)
_din.close();
} catch (Error e) {
critical("failed to close data input: %s", e.message);
warning("failed to close data input: %s", e.message);
}
try {
if (_dout != null)
_dout.close();
} catch (Error e) {
critical("failed to close data output: %s", e.message);
warning("failed to close data output: %s", e.message);
}
try {
if (_conn != null)
_conn.close();
} catch (Error e) {
critical("failed to close connection: %s", e.message);
warning("failed to close connection: %s", e.message);
}
_din = null;
_dout = null;
@ -162,7 +162,7 @@ class DeviceChannel : Object {
try {
_dout.put_string(to_send);
} catch (IOError e) {
critical("failed to send message: %s", e.message);
warning("failed to send message: %s", e.message);
// TODO disconnect?
}
}
@ -183,7 +183,7 @@ class DeviceChannel : Object {
// expecting \n
_din.read_byte();
} catch (IOError ie) {
debug("I/O error: %s", ie.message);
warning("I/O error: %s", ie.message);
}
if (data == null) {
@ -191,11 +191,11 @@ class DeviceChannel : Object {
return false;
}
debug("received line: %s", data);
vdebug("received line: %s", data);
Packet pkt = Packet.new_from_data(data);
if (pkt == null) {
critical("failed to build packet from data");
warning("failed to build packet from data");
// data was received, hence connection is still alive
return true;
}
@ -217,7 +217,7 @@ class DeviceChannel : Object {
}
private void handle_packet(Packet pkt) {
debug("handle packet of type: %s", pkt.pkt_type);
// debug("handle packet of type: %s", pkt.pkt_type);
if (pkt.pkt_type == Packet.ENCRYPTED) {
handle_encrypted_packet(pkt);
} else {
@ -233,7 +233,7 @@ class DeviceChannel : Object {
// method.
Json.Array arr = pkt.body.get_array_member("data");
if (arr == null) {
critical("missing data member in encrypted packet");
warning("missing data member in encrypted packet");
return;
}
@ -244,29 +244,29 @@ class DeviceChannel : Object {
if (failed == true)
return;
debug("node data: %s", node.get_string());
vdebug("node data: %s", node.get_string());
// encrypted data is base64 encoded
uchar[] data = Base64.decode(node.get_string());
var dbytes = new Bytes.take(data);
try {
ByteArray decrypted = this._crypt.decrypt(dbytes);
debug("data length: %zu", decrypted.data.length);
vdebug("data length: %zu", decrypted.data.length);
msgbytes.append(decrypted.data);
} catch (Error e) {
critical("decryption failed: %s", e.message);
warning("decryption failed: %s", e.message);
failed = true;
}
});
// data should be complete now
debug("total length of packet data: %zu", msgbytes.len);
vdebug("total length of packet data: %zu", msgbytes.len);
// make sure there is \0 at the end
msgbytes.append({'\0'});
string decrypted_data = ((string)msgbytes.data).dup();
debug("decrypted data: %s", decrypted_data);
vdebug("decrypted data: %s", decrypted_data);
Packet dec_pkt = Packet.new_from_data(decrypted_data);
if (dec_pkt == null) {
critical("failed to parse decrypted packet");
warning("failed to parse decrypted packet");
} else {
packet_received(dec_pkt);
}


+ 170
- 0
src/mconnect/devicemanager-proxy.vala View File

@ -0,0 +1,170 @@
/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
using Gee;
[DBus (name = "org.mconnect.DeviceManager")]
class DeviceManagerDBusProxy : Object
{
private DeviceManager manager;
public signal void device_added(string path);
public signal void device_removed(string path);
private const string DBUS_PATH = "/org/mconnect/manager";
private DBusConnection bus = null;
private HashMap<string, DeviceDBusProxy> devices;
private int device_idx = 0;
public DeviceManagerDBusProxy.with_manager(DBusConnection bus,
DeviceManager manager) {
this.manager = manager;
this.bus = bus;
this.devices = new HashMap<string, DeviceDBusProxy>();
manager.found_new_device.connect((d) => {
this.add_device(d);
});
manager.device_capability_added.connect(this.add_device_capability);
}
[DBus (visible = false)]
public void publish() throws IOError {
assert(this.bus != null);
this.bus.register_object(DBUS_PATH, this);
}
/**
* allow_device:
* @path: device object path
*
* Allow given device
*/
public void allow_device(string path) {
debug("allow device %s", path);
var dev_proxy = this.devices.@get(path);
if (dev_proxy == null) {
warning("no device under path %s", path);
return;
}
this.manager.allow_device(dev_proxy.device);
}
/**
* disallow_device:
* @path: device object path
*
* Disallow given device
*/
public void disallow_device(string path) {
debug("disallow device %s", path);
var dev_proxy = this.devices.@get(path);
if (dev_proxy == null) {
warning("no device under path %s", path);
return;
}
this.manager.disallow_device(dev_proxy.device);
}
/**
* list_devices:
*
* Returns a list of DBus paths of all known devices
*/
public ObjectPath[] list_devices() {
ObjectPath[] devices = {};
foreach (var path in this.devices.keys) {
devices += new ObjectPath(path);
}
return devices;
}
private void add_device(Device dev) {
var path = make_device_path();
var device_proxy = new DeviceDBusProxy.for_device_with_path(dev,
new ObjectPath(path));
this.devices.@set(path, device_proxy);
info("register device %s under path %s",
dev.to_string(), path);
device_proxy.bus_register(this.bus);
device_added(path);
}
private DeviceDBusProxy? find_proxy_for_device(Device dev) {
DeviceDBusProxy dp = null;
foreach (var entry in this.devices.entries) {
if (entry.value.device == dev) {
dp = entry.value;
break;
}
}
return dp;
}
private void add_device_capability(Device dev,
string capability,
PacketHandlerInterface iface) {
DeviceDBusProxy dp = find_proxy_for_device(dev);
if (dp == null) {
warning("no bus proxy for device %s", dev.to_string());
return;
}
if (dp.has_handler(capability)) {
return;
}
info("add capability handler %s for device at path %s",
capability, dp.object_path.to_string());
var h = PacketHandlersProxy.new_device_capability_handler(dev,
capability,
iface);
if (h != null) {
h.bus_register(this.bus, dp.object_path);
}
}
/**
* make_device_path:
*
* return device path string that can be used as ObjectPath
*/
private string make_device_path() {
var path = "/org/mconnect/device/%d".printf(this.device_idx);
// bump device index
this.device_idx++;
return path;
}
}

+ 156
- 44
src/mconnect/devicemanager.vala View File

@ -21,7 +21,12 @@ using Gee;
class DeviceManager : GLib.Object
{
public static const string DEVICES_CACHE_FILE = "devices";
public signal void found_new_device(Device dev);
public signal void device_capability_added(Device dev,
string capability,
PacketHandlerInterface handler);
public const string DEVICES_CACHE_FILE = "devices";
private HashMap<string, Device> devices;
@ -29,10 +34,6 @@ class DeviceManager : GLib.Object
debug("device manager..");
this.devices = new HashMap<string, Device>();
// TODO: check for network connectivity first, possibly pass
// this through the main loop
load_cache();
}
/**
@ -41,7 +42,7 @@ class DeviceManager : GLib.Object
private string get_cache_file() {
var cache_file = Path.build_filename(Core.get_cache_dir(),
DEVICES_CACHE_FILE);
debug("cache file: %s", cache_file);
vdebug("cache file: %s", cache_file);
// make sure that cache dir exists
DirUtils.create_with_parents(Core.get_cache_dir(),
@ -53,11 +54,11 @@ class DeviceManager : GLib.Object
/**
* Load known devices from cache and attempt pairing.
*/
private void load_cache() {
debug("try loading devices from device cache");
public void load_cache() {
var cache_file = get_cache_file();
debug("try loading devices from device cache %s", cache_file);
var kf = new KeyFile();
try {
kf.load_from_file(cache_file, KeyFileFlags.NONE);
@ -68,7 +69,7 @@ class DeviceManager : GLib.Object
var dev = Device.new_from_cache(kf, group);
if (dev != null) {
debug("device %s from cache", dev.to_string());
found_device(dev);
handle_new_device(dev);
}
}
} catch (Error e) {
@ -80,77 +81,188 @@ class DeviceManager : GLib.Object
* Update contents of device cache
*/
private void update_cache() {
debug("update devices cache");
// debug("update devices cache");
if (devices.size == 0)
return;
var kf = new KeyFile();
foreach (Device dev in devices.values) {
foreach (var dev in devices.values) {
dev.to_cache(kf, dev.device_name);
}
try {
debug("saving to cache");
// debug("saving to cache");
FileUtils.set_contents(get_cache_file(),
kf.to_data());
} catch (FileError e) {
debug("failed to save to cache file %s: %s",
get_cache_file(), e.message);
warning("failed to save to cache file %s: %s",
get_cache_file(), e.message);
}
}
public void found_device(Device dev) {
debug("found device: %s", dev.to_string());
public void handle_discovered_device(DiscoveredDevice discovered_dev) {
debug("found device: %s", discovered_dev.to_string());
if (device_allowed(dev) == false) {
message("device %s not on whitelist", dev.to_string());
return;
}
var new_dev = new Device.from_discovered_device(discovered_dev);
handle_new_device(new_dev);
}
public void handle_new_device(Device new_dev) {
var is_new = false;
string unique = new_dev.to_unique_string();
vdebug("device key: %s", unique);
string unique = dev.to_unique_string();
debug("device key: %s", unique);
if (this.devices.has_key(unique) == false) {
debug("adding new device with key: %s", unique);
this.devices.@set(unique, dev);
dev.paired.connect((d, p) => {
device_paired(d, p);
});
this.devices.@set(unique, new_dev);
is_new = true;
dev.disconnected.connect((d) => {
device_disconnected(d);
});
dev.activate();
} else {
debug("activate from device");
var known_dev = this.devices.@get(unique);
known_dev.activate_from_device(dev);
debug("device %s already present", unique);
}
var dev = this.devices.@get(unique);
// notify everyone that a new device appeared
if (is_new) {
// make sure that this happens before we update device data so that
// all subscribeds of found_new_device() signal have a chance to
// setup eveything they need
found_new_device(dev);
}
if (is_new) {
dev.capability_added.connect(this.device_capability_added_cb);
dev.capability_removed.connect(this.device_capability_removed_cb);
}
// update device information
dev.update_from_device(new_dev);
// device in whitelist and added to currently used devices, so
// it's ok to update the device cache
debug("allowed? %s", dev.allowed.to_string());
// check if device is whitelisted in configuration
if (!dev.allowed && device_allowed_in_config(dev)) {
dev.allowed = true;
}
// update devices cache
update_cache();
if (dev.allowed) {
// device is allowed
activate_device(dev);
} else {
warning("skipping device %s activation, device not allowed",
dev.to_string());
}
}
private bool device_allowed(Device dev) {
private void activate_device(Device dev) {
info("activating device %s, active: %s", dev.to_string(),
dev.is_active.to_string());
if (!dev.is_active) {
dev.paired.connect(this.device_paired);
dev.disconnected.connect(this.device_disconnected);
dev.activate();
}
}
/**
* device_allowed_in_config:
* @dev device
*
* Returns true if a matching device is enabled via configuration file.
*/
private bool device_allowed_in_config(Device dev) {
if (dev.allowed)
return true;
var core = Core.instance();
return core.config.is_device_allowed(dev.device_name,
dev.device_type);
var in_config = core.config.is_device_allowed(dev.device_name,
dev.device_type);
return in_config;
}
private void device_paired(Device dev, bool status) {
if (status == true) {
var core = Core.instance();
// register message handlers
core.handlers.use_device(dev);
info("device %s pair status change: %s",
dev.to_string(), status.to_string());
update_cache();
if (status == false) {
// we're no longer interested in paired singnal
dev.paired.disconnect(this.device_paired);
// we're not paired anymore, deactivate if needed
dev.deactivate();
}
}
private void device_capability_added_cb(Device dev, string cap) {
info("capability %s added to device %s", cap, dev.to_string());
if (dev.has_capability_handler(cap)) {
return;
}
var core = Core.instance();
var h = core.handlers.get_capability_handler(cap);
if (h != null) {
dev.register_capability_handler(cap, h);
device_capability_added(dev, cap, h);
} else {
warning("no handler for capability %s", cap);
}
}
private void device_capability_removed_cb(Device dev, string cap) {
info("capability %s removed from device %s", cap, dev.to_string());
}
private void device_disconnected(Device dev) {
debug("device %s got disconnected", dev.to_string());
dev.paired.disconnect(this.device_paired);
dev.disconnected.disconnect(this.device_disconnected);
}
}
/**
* allow_device:
* @path: device object path
*
* Allow given device
*/
public void allow_device(Device dev) {
dev.allowed = true;
// update device cache
update_cache();
// maybe activate if needed
activate_device(dev);
}
/**
* disallow_device:
* @path: device object path
*
* Disallow given device
*/
public void disallow_device(Device dev) {
dev.allowed = false;
// update device cache
update_cache();
}
}

+ 82
- 0
src/mconnect/discovereddevice.vala View File

@ -0,0 +1,82 @@
/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
/**
* Newly discovered device wrapper.
*/
class DiscoveredDevice : Object {
public string device_id { get; private set; default = ""; }
public string device_name { get; private set; default = ""; }
public string device_type { get; private set; default = ""; }
public uint protocol_version {get; private set; default = 5; }
public uint tcp_port {get; private set; default = 1714; }
public InetAddress host { get; private set; default = null; }
public string[] outgoing_capabilities { get; private set; default = null; }
public string[] incoming_capabilities { get; private set; default = null; }
/**
* Constructs DiscoveredDevice based on identity packet.
*
* @param pkt identity packet
* @param host source host that the packet came from
*/
public DiscoveredDevice.from_identity(Packet pkt, InetAddress host) {
debug("got packet: %s", pkt.to_string());
var body = pkt.body;
this.host = host;
this.device_name = body.get_string_member("deviceName");
this.device_id = body.get_string_member("deviceId");
this.device_type = body.get_string_member("deviceType");
this.protocol_version = (int) body.get_int_member("protocolVersion");
this.tcp_port = (uint) body.get_int_member("tcpPort");
var incoming = body.get_array_member("incomingCapabilities");
var outgoing = body.get_array_member("outgoingCapabilities");
this.outgoing_capabilities = new string[outgoing.get_length()];
this.incoming_capabilities = new string[incoming.get_length()];
incoming.foreach_element((a, i, n) => {
this.incoming_capabilities[i] = n.get_string();
});
outgoing.foreach_element((a, i, n) => {
this.outgoing_capabilities[i] = n.get_string();
});
debug("discovered new device: %s", this.to_string());
}
public string to_string() {
return "discovered-%s-%s-%s-%u".printf(this.device_id,
this.device_name,
this.device_type,
this.protocol_version);
}
public string to_unique_string() {
return make_unique_device_string(this.device_id,
this.device_name,
this.device_type,
this.protocol_version);
}
}

+ 8
- 8
src/mconnect/discovery.vala View File

@ -22,10 +22,10 @@ class Discovery : GLib.Object
{
private Socket socket = null;
public signal void device_found(Device dev);
public signal void device_found(DiscoveredDevice dev);
public Discovery() {
}
}
~Discovery() {
debug("cleaning up discovery...");
@ -51,7 +51,7 @@ class Discovery : GLib.Object
throw e;
}
var source = socket.create_socket_source(IOCondition.IN);
var source = socket.create_source(IOCondition.IN);
source.set_callback((s, c) => {
this.incomingPacket();
return true;
@ -60,7 +60,7 @@ class Discovery : GLib.Object
}
private void incomingPacket() {
debug("incoming packet");
vdebug("incoming packet");
uint8 buffer[4096];
SocketAddress sa;
@ -69,14 +69,14 @@ class Discovery : GLib.Object
try {
ssize_t read = this.socket.receive_from(out sa, buffer);
isa = (InetSocketAddress)sa;
debug("got %zd bytes from: %s:%u", read,
vdebug("got %zd bytes from: %s:%u", read,
isa.address.to_string(), isa.port);
} catch (Error e) {
message("failed to receive packet: %s", e.message);
warning("failed to receive packet: %s", e.message);
return;
}
debug("message data: %s", (string)buffer);
vdebug("message data: %s", (string)buffer);
this.parsePacketFromHost((string) buffer, isa.address);
}
@ -91,7 +91,7 @@ class Discovery : GLib.Object
return;
}
var dev = new Device.from_identity(pkt, host);
var dev = new DiscoveredDevice.from_identity(pkt, host);
message("connection from device: \'%s\', responds at: %s:%u",
dev.device_name, host.to_string(), dev.tcp_port);


+ 3
- 45
src/mconnect/main.vala View File

@ -17,54 +17,12 @@
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
private static bool log_debug = false;
private const GLib.OptionEntry[] options = {
{"debug", 'd', 0, OptionArg.NONE, ref log_debug, "Show debug output", null},
{null}
};
public static int main(string[] args)
{
try {
var opt_context = new OptionContext ("mconnect");
opt_context.set_help_enabled (true);
opt_context.add_main_entries (options, null);
opt_context.parse (ref args);
} catch (OptionError e) {
stdout.printf ("error: %s\n", e.message);
stdout.printf ("Run '%s --help' to see a full list of available command line options.\n", args[0]);
return 0;
}
if (log_debug == true)
Environment.set_variable("G_MESSAGES_DEBUG", "all", false);
var app = new Mconn.Application();
// needed for mousepad protocol handler
Gdk.init(ref args);
Notify.init("mconnect");
var core = Core.instance();
if (core == null)
error("cannot initialize core");
if (core.config.is_debug_on() == true)
Environment.set_variable("G_MESSAGES_DEBUG", "all", false);
var loop = new MainLoop();
var discovery = new Discovery();
var manager = new DeviceManager();
discovery.device_found.connect((disc, dev) => {
manager.found_device(dev);
});
try {
discovery.listen();
} catch (Error e) {
message("failed to setup device listener: %s", e.message);
return 1;
}
loop.run();
return 0;
return app.run(args);
}

+ 14
- 10
src/mconnect/mousepad.vala View File

@ -20,7 +20,8 @@
class MousepadHandler : Object, PacketHandlerInterface {
private const string MOUSEPAD = "kdeconnect.mousepad";
public const string MOUSEPAD = "kdeconnect.mousepad.request";
public const string MOUSEPAD_PACKET = "kdeconnect.mousepad";
private Gdk.Display _display;
@ -46,17 +47,20 @@ class MousepadHandler : Object, PacketHandlerInterface {
}
public void use_device(Device dev) {
debug("use device %s for battery status updates", dev.to_string());
dev.message.connect((d, pkt) => {
debug("message signal");
if (pkt.pkt_type == MOUSEPAD) {
debug("mousepad packet");
this.message(pkt);
}
});
debug("use device %s for mouse/keyboard input", dev.to_string());
dev.message.connect(this.message);
}
public void message(Packet pkt) {
public void release_device(Device dev) {
debug("release device %s ", dev.to_string());
dev.message.disconnect(this.message);
}
private void message(Device dev, Packet pkt) {
if (pkt.pkt_type != MOUSEPAD_PACKET) {
return;
}
debug("got mousepad packet");
if (_display == null) {


+ 9
- 6
src/mconnect/notification.vala View File

@ -38,14 +38,17 @@ class NotificationHandler : Object, PacketHandlerInterface {
}
public void use_device(Device dev) {
dev.message.connect((d, pkt) => {
if (pkt.pkt_type == NOTIFICATION) {
this.message(pkt);
}
});
dev.message.connect(this.message);
}
public void release_device(Device dev) {
dev.message.disconnect(this.message);
}
public void message(Packet pkt) {
public void message(Device dev, Packet pkt) {
if (pkt.pkt_type != NOTIFICATION) {
return;
}
debug("got notification packet");
// get application ID


+ 41
- 2
src/mconnect/packet.vala View File

@ -26,6 +26,20 @@ public errordomain PacketError {
class Packet : GLib.Object {
/**
* Payload:
* Wrapper for payload transfer information
*/
public struct Payload {
public uint64 size;
public uint port;
Payload() {
this.size = 0;
this.port = 0;
}
}
public const int PROTOCOL_VERSION = 5;
public const string IDENTITY = "kdeconnect.identity";
@ -35,6 +49,7 @@ class Packet : GLib.Object {
public string pkt_type { get; private set; default = ""; }
public int64 id { get; private set; default = 0; }
public Json.Object body { get; private set; default = null; }
public Payload? payload { get; private set; default = null; }
public Packet(string type, Json.Object body, int64 id = 0) {
this.pkt_type = type;
@ -67,9 +82,33 @@ class Packet : GLib.Object {
int64 id = root_obj.get_int_member("id");
Json.Object body = root_obj.get_object_member("body");
debug("packet type: %s", type);
vdebug("packet type: %s", type);
var pkt = new Packet(type, body, id);
// ignore payload info for encrypted packets
if (type != ENCRYPTED) {
if (root_obj.has_member("payloadSize") &&
root_obj.has_member("payloadTransferInfo")) {
var size = root_obj.get_int_member("payloadSize");
var pti = root_obj.get_object_member("payloadTransferInfo");
int64 port = 0;
if (pti == null) {
warning("no payload transfer info?");
} else {
port = (int) pti.get_int_member("port");
}
if (size != 0 && port != 0) {
pkt.payload = {(uint64) size, (uint) port};
}
}
}
return new Packet(type, body, id);
return pkt;
} catch (Error e) {
message("failed to parse message: \'%s\', error: %s",
data, e.message);


+ 27
- 0
src/mconnect/packethandlerinterface-proxy.vala View File

@ -0,0 +1,27 @@
/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
/**
* PacketHandlerInterfaceProxy: interface of DBus exported packet handler
*/
interface PacketHandlerInterfaceProxy : Object {
public abstract void bus_register(DBusConnection conn, string path) throws IOError;
public abstract void bus_unregister(DBusConnection conn) throws IOError;
}

+ 2
- 0
src/mconnect/packethandlerinterface.vala View File

@ -24,4 +24,6 @@ interface PacketHandlerInterface : Object {
public abstract void use_device(Device dev);
public abstract void release_device(Device dev);
}

+ 44
- 0
src/mconnect/packethandlers-proxy.vala View File

@ -0,0 +1,44 @@
/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
class PacketHandlersProxy : Object {
public static PacketHandlerInterfaceProxy? new_device_capability_handler(
Device dev,
string cap,
PacketHandlerInterface iface) {
switch (iface.get_pkt_type()) {
case BatteryHandler.BATTERY: {
return new BatteryHandlerProxy.for_device_handler(dev, iface);
break;
}
case PingHandler.PING: {
return new PingHandlerProxy.for_device_handler(dev, iface);
break;
}
default:
warning("cannot register bus handler for %s",
iface.get_pkt_type());
break;
}
return null;
}
}

+ 33
- 29
src/mconnect/packethandlers.vala View File

@ -17,56 +17,60 @@
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
using Gee;
class PacketHandlers : Object {
private List<PacketHandlerInterface> _handlers = null;
private HashMap<string, PacketHandlerInterface> _handlers;
public List<PacketHandlerInterface> handlers {
get {
return _handlers;
}
private set {
_handlers = handlers.copy();
}
public string[] interfaces {
owned get { return _handlers.keys.to_array(); }
private set {}
}
public string[] interfaces { get; private set; default = null; }
public PacketHandlers() {
_handlers = load_handlers();
string [] ifaces;
list_handlers(out ifaces);
interfaces = ifaces;
}
private static List<PacketHandlerInterface> load_handlers() {
List<PacketHandlerInterface> hnd = new List<PacketHandlerInterface>();
private static HashMap<string, PacketHandlerInterface> load_handlers() {
HashMap<string, PacketHandlerInterface> hnd =
new HashMap<string, PacketHandlerInterface>();
var notification = NotificationHandler.instance();
var battery = BatteryHandler.instance();
var telephony = TelephonyHandler.instance();
var mousepad = MousepadHandler.instance();
var ping = PingHandler.instance();
hnd.append(notification);
hnd.append(battery);
hnd.append(telephony);
hnd.append(mousepad);
hnd.@set(notification.get_pkt_type(), notification);
hnd.@set(battery.get_pkt_type(), battery);
hnd.@set(telephony.get_pkt_type(), telephony);
hnd.@set(mousepad.get_pkt_type(), mousepad);
hnd.@set(ping.get_pkt_type(), ping);
return hnd;
}
public void list_handlers(out string[] interfaces) {
interfaces = new string[_handlers.length()];
// string[] interfaces = new string[_handlers.length()];
for (int i = 0; i < _handlers.length(); i++) {
interfaces[i] = _handlers.nth_data(i).get_pkt_type();
}
// return interfaces;
/**
* SupportedCapabilityFunc:
* @capability: capability name
* @handler: packet handler
*
* User provided callback called when enabling @capability handled
* by @handler for a particular device.
*/
public delegate void SupportedCapabilityFunc(string capability,
PacketHandlerInterface handler);
public PacketHandlerInterface? get_capability_handler(string cap) {
// all handlers are singletones for now
var h = this._handlers.@get(cap);
return h;
}
public void use_device(Device dev) {
_handlers.foreach((h) => {
h.use_device(dev);
});
public static string to_capability(string pkttype) {
if (pkttype.has_suffix(".request"))
return pkttype.replace(".request", "");
return pkttype;
}
}

+ 52
- 0
src/mconnect/ping-proxy.vala View File

@ -0,0 +1,52 @@
/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
[DBus (name = "org.mconnect.Device.Ping")]
class PingHandlerProxy : Object, PacketHandlerInterfaceProxy {
private Device device = null;
private PingHandler ping_handler = null;
public PingHandlerProxy.for_device_handler(Device dev,
PacketHandlerInterface iface) {
this.device = dev;
this.ping_handler = (PingHandler) iface;
this.ping_handler.ping.connect(this.ping_cb);
}
[DBus (visible = false)]
public void bus_register(DBusConnection conn, string path) throws IOError {
conn.register_object(path, this);
}
[DBus (visible = false)]
public void bus_unregister(DBusConnection conn) throws IOError {
//conn.unregister_object(this);
}
private void ping_cb(Device dev) {
if (this.device != dev)
return;
ping();
}
public signal void ping();
}

+ 57
- 0
src/mconnect/ping.vala View File

@ -0,0 +1,57 @@
/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
class PingHandler : Object, PacketHandlerInterface {
public const string PING = "kdeconnect.ping";
public string get_pkt_type() {
return PING;
}
private PingHandler() {
}
public static PingHandler instance() {
return new PingHandler();
}
public void use_device(Device dev) {
debug("use device %s for ping", dev.to_string());
dev.message.connect(this.message);
}
public void release_device(Device dev) {
debug("release device %s", dev.to_string());
dev.message.disconnect(this.message);
}
public void message(Device dev, Packet pkt) {
if (pkt.pkt_type != PING) {
return;
}
GLib.message("ping from device %s", dev.to_string());
ping(dev);
}
public signal void ping(Device dev);
}

+ 101
- 0
src/mconnect/property-proxy.vala View File

@ -0,0 +1,101 @@
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
/**
* DBusPropertyNotifier:
*
* Helper class for pushing out
* org.freedesktop.DBus.Properties.PropertiesChanged signals.
*/
class DBusPropertyNotifier : Object {
private DBusConnection conn = null;
private string iface = "";
private string path = "";
private VariantBuilder builder = null;
private uint timeout_src = 0;
public const uint TIMEOUT = 300;
public DBusPropertyNotifier(DBusConnection conn,
string iface,
string path) {
this.conn = conn;
this.iface = iface;
this.path = path;
}
/**
* queue_property_change:
*
* @name: property name (will be automatically capitalized if needed)
* @val: Variant holding property value
*
* This method will queue up property notifications for sending. By default
* it waits @TIMEOUT ms before sending the actual signal.
*/
public void queue_property_change(string name, Variant val) {
if (this.builder == null) {
this.builder = new VariantBuilder(VariantType.ARRAY);
}
string nm = name;
if (name.get_char(0).islower()) {
nm = name.get_char(0).toupper().to_string() + name.substring(1);
}
this.builder.add("{sv}", nm, val);
if (this.timeout_src == 0) {
this.timeout_src = Timeout.add(300,
this.send_property_change);
}
}
/**
* send_property_change:
*
* Send out actual PropertiesChanged signals
*/
private bool send_property_change() {
this.timeout_src = 0;
if (this.builder == null)
return false;;
try {
var invalid_builder = new VariantBuilder(new VariantType ("as"));
this.conn.emit_signal(null,
this.path,
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
new Variant ("(sa{sv}as)",
this.iface,
builder,
invalid_builder)
);
} catch (Error e) {
warning("%s\n", e.message);
}
this.builder = null;
return false;
}
}

+ 10
- 6
src/mconnect/telephony.vala View File

@ -36,14 +36,18 @@ class TelephonyHandler : Object, PacketHandlerInterface {
}
public void use_device(Device dev) {
dev.message.connect((d, pkt) => {
if (pkt.pkt_type == TELEPHONY) {
this.message(pkt);
}
});
dev.message.connect(this.message);
}
public void release_device(Device dev) {
dev.message.disconnect(this.message);
}
public void message(Packet pkt) {
public void message(Device dev, Packet pkt) {
if (pkt.pkt_type != TELEPHONY) {
return;
}
debug("got telephony packet");
if (pkt.body.has_member("phoneNumber") == false ||


+ 85
- 0
src/mconnect/utils.vala View File

@ -0,0 +1,85 @@
/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
/**
* @array_list_to_list:
* @al: Gee.ArrayList<T>
*
* Convert Gee.ArrayList<T> to T[]
*/
T[] array_list_to_list<T>(Gee.ArrayList<T> al) {
T[] out_list = new T[al.size];
int i = 0;
foreach(var v in al) {
out_list[i] = v;
i++;
}
return out_list;
}
namespace DebugLog{
public bool Verbose = false;
}
void enable_vdebug() {
DebugLog.Verbose = true;
}
/**
* vdebug:
* @format: format string
*
* Same as debug() but looks at verbose debug flag
*/
void vdebug(string format, ...) {
if (DebugLog.Verbose == true) {
var l = va_list();
logv(null, LogLevelFlags.LEVEL_DEBUG, format, l);
}
}
/**
* make_unique_device_string:
* @id: device ID
* @name: device name
* @type: device type
* @pv: protocol version
*
* Generate device string that can be used as map index
*/
string make_unique_device_string(string id, string name,
string type, uint pv) {
return make_device_string(id, name, type, pv).replace(" ", "-");
}
/**
* make_device_string:
* @id: device ID
* @name: device name
* @type: device type
* @pv: protocol version
*
* Generate device string
*/
string make_device_string(string id, string name,
string type, uint pv) {
return "%s-%s-%s-%u".printf(id, name, type, pv);
}

+ 335
- 0
src/mconnectctl/main.vala View File

@ -0,0 +1,335 @@
/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
namespace Mconnect {
[DBus (name = "org.mconnect.DeviceManager")]
public interface DeviceManagerIface : Object {
public const string OBJECT_PATH = "/org/mconnect/manager";
public abstract ObjectPath[] ListDevices() throws IOError;
public abstract void AllowDevice(string path) throws IOError;
}
[DBus (name = "org.mconnect.Device")]
public interface DeviceIface : Object {
public abstract string id { owned get;}
public abstract string name { owned get;}
public abstract string device_type { owned get;}
public abstract uint protocol_version { owned get;}
public abstract string address { owned get;}
public abstract bool is_paired { owned get;}
public abstract bool allowed { owned get;}
public abstract bool is_active { owned get;}
public abstract bool is_connected { owned get;}
public abstract string[] outgoing_capabilities { owned get;}
public abstract string[] incoming_capabilities { owned get;}
}
public class Client {
private static bool log_debug = false;
private static bool verbose = false;
// some hints for valac about the array holding remaining args
[CCode (array_length = false, array_null_terminated = true)]
private static string[] remaining;
private BusType bus_type = BusType.SESSION;
private const OptionEntry[] options = {
{"debug", 'd', 0, OptionArg.NONE, ref log_debug,
"Show debug output", null},
{"verbose", 'v', 0, OptionArg.NONE, ref verbose,
"Be verbose", null},
// there's no Vala const for G_OPTION_REMAINING (which is a #define
// for "")
{"", 0, 0, OptionArg.STRING_ARRAY, ref remaining, null,
"[COMMAND ..]"},
{null}
};
/**
* Command:
*
* command line 'command' wrapper
*/
private struct Command {
string command; // textual command, ex. list, show, etc.
int arg_count; // number of required parameters, not including
// command
unowned CommandFunc clbk; // callback
Command(string command, int arg_count, CommandFunc clbk) {
this.command = command;
this.arg_count = arg_count;
this.clbk = clbk;
}
}
// command callback
private delegate int CommandFunc(string[] args);
public static int main(string[] args)
{
try {
var opt_context = new OptionContext();
opt_context.set_description(
"""Available commands:
list-devices List devices
allow-device <path> Allow device
show-device <path> Show device details
"""
);
opt_context.set_help_enabled(true);
opt_context.add_main_entries(options, null);
opt_context.parse(ref args);
} catch (OptionError e) {
stdout.printf("error: %s\n", e.message);
stdout.printf("Run '%s --help' to see a full " +
"list of available command line options.\n",
args[0]);
return 1;
}
if (log_debug == true)
Environment.set_variable("G_MESSAGES_DEBUG", "all", false);
var cl = new Client();
Command[] commands = {
Command("list-devices", 0, cl.cmd_list_devices),
Command("allow-device", 1, cl.cmd_allow_device),
Command("show-device", 1, cl.cmd_show_device),
};
handle_command(remaining, commands);
return 0;
}
/**
* handle_command:
* @args: remaining command line arguments
* @commands: supported commands array
*
* @return exit status of command or -1 on error
*/
private static int handle_command(string[] args, Command[] commands) {
// extract command and it's arguments if any
string command = "list-devices";
if (args.length > 0)
command = remaining[0];
debug("command is: %s", command);
string[] command_args = {};
if (args.length > 1)
command_args = args[1:args.length];
foreach (var cmden in commands) {
if (cmden.command == command) {
debug("found match for %s, args expect: %zd, have: %zd",
command, cmden.arg_count, command_args.length);
if (command_args.length != cmden.arg_count) {
stderr.printf("Incorrect number of arguments " +
"for command %s, see --help\n",
command);
return -1;
}
debug("running callback");
return cmden.clbk(command_args);
}
}
stderr.printf("Incorrect command, see --help\n");
return -1;
}
private int cmd_list_devices(string[] args) {
return checked_dbus_call(() => {
var manager = get_manager();
debug("list devices");
var devs = manager.ListDevices();
print_paths(devs, "Devices",
(path) => {
try {
var dp = get_device(path);
return "%s - %s".printf(dp.id, dp.name);
} catch (IOError e) {
warning("error occurred: %s", e.message);
return "(error)";
}
});
return 0;
});
}
private int cmd_allow_device(string[] args) {
return checked_dbus_call(() => {
var dp = args[0];
var manager = get_manager();
debug("allow device device %s", dp);
manager.AllowDevice(new ObjectPath(dp));
return 0;
});
}
private void print_sorted_caps(string[] caps, string format) {
qsort_with_data<string>(caps, sizeof(string),
(a, b) => GLib.strcmp(a, b));
foreach (var cap in caps) {
stdout.printf(format, cap);
}
}
private int cmd_show_device(string[] args) {
return checked_dbus_call(() => {
var dp = get_device(new ObjectPath(args[0]));
stdout.printf("Device\n" +
" Name: %s\n" +
" ID: %s\n" +
" Address: %s\n" +
" Type: %s\n" +
" Allowed: %s\n" +
" Paired: %s\n" +
" Active: %s\n" +
" Connected: %s\n",
dp.name,
dp.id,
dp.address,
dp.device_type,
dp.allowed.to_string(),
dp.is_paired.to_string(),
dp.is_active.to_string(),
dp.is_connected.to_string());
if (verbose) {
stdout.printf(" Capabilities (out):\n");
print_sorted_caps(dp.outgoing_capabilities, " %s\n");
stdout.printf(" Capabilities (in):\n");
print_sorted_caps(dp.incoming_capabilities, " %s\n");
}
return 0;
});
}
private delegate int CheckDBusCallFunc() throws Error;
/**
* checked_dbus_call:
* @clbk: function to wrap
*
* Catch any DBus errors and return appropriate status
*/
private static int checked_dbus_call(CheckDBusCallFunc clbk) {
try {
return clbk();
} catch (IOError e) {
warning("communication returned an error: %s", e.message);
return -1;
} catch (DBusError e) {
warning("communication with service failed: %s", e.message);
} catch (Error e) {
warning("error: %s", e.message);
}
return 0;
}
/**
* get_mconnect_obj_proxy:
* @path: DBus object path
*
* Obtain an interface to a DBus object avaialble at
* Mconnect service under @path.
*
* @return null or interface
*/
private T? get_mconnect_obj_proxy<T>(ObjectPath path) throws IOError {
T proxy_out = null;
try {
proxy_out = Bus.get_proxy_sync(bus_type,
"org.mconnect",
path);
} catch (IOError e) {
warning("failed to obtain proxy to mconnect service: %s",
e.message);
throw e;
}
return proxy_out;
}
/**
* get_manager:
*
* Obtain DBus interface to Device Manager
*
* @return interface or null
*/
private DeviceManagerIface? get_manager() throws IOError {
return get_mconnect_obj_proxy(
new ObjectPath(DeviceManagerIface.OBJECT_PATH));
}
/**
* get_device:
* @path device object path
*
* Obtain DBus interface to Device
*
* @return interface or null
*/
private DeviceIface? get_device(ObjectPath path) throws IOError {
return get_mconnect_obj_proxy(path);
}
/**
* print_paths:
* @objs: object paths
* @header: header for printing,
* @desc_clbk: callback for producing a meaningful description
*
* Print a list of object paths, possibly adding a description
*/
private static void print_paths(ObjectPath[] objs, string header,
GetDescFunc desc_clbk) {
if (objs.length == 0)
stdout.printf("No objects were found\n");
else {
stdout.printf(header + ":\n");
foreach (var o in objs) {
string desc = null;
if (desc_clbk != null) {
debug("calling description callback for obj: %s",
o.to_string());
desc = desc_clbk(o);
}
stdout.printf(" %s", o.to_string());
if (desc != null)
stdout.printf(" %s", desc);
stdout.printf("\n");
}
}
}
private delegate string GetDescFunc(ObjectPath obj_path);
}
}

+ 22
- 23
test/mconn-crypt-test.c View File

@ -1,5 +1,3 @@
/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@ -7,7 +5,7 @@
*
* This program 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
* 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
@ -18,41 +16,42 @@
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
#include <glib.h>
#include <glib/gstdio.h>
#include "mconn-crypt.h"
static void test_simple(void)
{
g_remove("/tmp/test.key");
/* we made sure that file is removed */
g_assert_false(g_file_test("/tmp/test.key", G_FILE_TEST_EXISTS));
g_remove("/tmp/test.key");
/* we made sure that file is removed */
g_assert_false(g_file_test("/tmp/test.key", G_FILE_TEST_EXISTS));
MconnCrypt *cr = mconn_crypt_new_for_key_path("/tmp/test.key");
g_assert_nonnull(cr);
g_assert_true(g_file_test("/tmp/test.key", G_FILE_TEST_EXISTS));
MconnCrypt *cr = mconn_crypt_new_for_key_path("/tmp/test.key");
g_assert_nonnull(cr);
g_assert_true(g_file_test("/tmp/test.key", G_FILE_TEST_EXISTS));
gchar *pubkey1 = mconn_crypt_get_public_key_pem(cr);
gchar *pubkey1 = mconn_crypt_get_public_key_pem(cr);
mconn_crypt_unref(cr);
/* file should still exit */
g_assert_true(g_file_test("/tmp/test.key", G_FILE_TEST_EXISTS));
mconn_crypt_unref(cr);
/* file should still exit */
g_assert_true(g_file_test("/tmp/test.key", G_FILE_TEST_EXISTS));
cr = mconn_crypt_new_for_key_path("/tmp/test.key");
/* key should have been loaded */
g_assert_nonnull(cr);
g_assert_true(g_file_test("/tmp/test.key", G_FILE_TEST_EXISTS));
cr = mconn_crypt_new_for_key_path("/tmp/test.key");
/* key should have been loaded */
g_assert_nonnull(cr);
g_assert_true(g_file_test("/tmp/test.key", G_FILE_TEST_EXISTS));
gchar *pubkey2 = mconn_crypt_get_public_key_pem(cr);
gchar *pubkey2 = mconn_crypt_get_public_key_pem(cr);
mconn_crypt_unref(cr);
mconn_crypt_unref(cr);
g_assert_cmpstr(pubkey1, ==, pubkey2);
g_assert_cmpstr(pubkey1, ==, pubkey2);
}
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
g_test_init(&argc, &argv, NULL);
g_test_add_func("/mconn-crypt/init", test_simple);
g_test_add_func("/mconn-crypt/init", test_simple);
return g_test_run();
return g_test_run();
}

+ 0
- 5089
vapi/gio-2.0.vapi
File diff suppressed because it is too large
View File


Loading…
Cancel
Save