├── debian ├── compat ├── source │ └── format ├── libkano-dev.install ├── libkano.links ├── libparson-dev.install ├── libparson.links ├── libkano-python-dev.install ├── libkano-c-logging-dev.install ├── libkano-networking-dev.install ├── libkano-python.links ├── libkano-c-logging.links ├── libkano-networking.links ├── kano-toolset-i18n-orig.install ├── libkano.install ├── libparson.install ├── libkano-dbg.install ├── libparson-dbg.install ├── libkano-python.install ├── libkano-c-logging.install ├── libkano-python-dbg.install ├── libkano-c-logging-dbg.install ├── libkano-networking-dbg.install ├── libkano-networking.install ├── copyright ├── postrm ├── rules ├── kano-toolset.install └── postinst ├── requirements.txt ├── share ├── kano-launcher │ └── conf │ │ ├── kano-credits │ │ ├── kano-draw │ │ ├── kano-settings │ │ ├── make-music │ │ ├── make-pong │ │ ├── kano-kill-apps │ │ ├── linux-story-gui │ │ ├── make-minecraft │ │ ├── make-snake │ │ └── pcmanfm └── kano-launcher-no-kill-list ├── tests ├── fixtures │ ├── cpuinfo │ │ ├── empty_cpuinfo.dump │ │ ├── empty_cpuinfo.json │ │ ├── garbage_cpuinfo.json │ │ ├── rpi_2_cpuinfo.json │ │ ├── rpi_3_cpuinfo.json │ │ ├── incorrect_cpuinfo.json │ │ ├── garbage_cpuinfo.dump │ │ ├── rpi_2_cpuinfo.dump │ │ ├── rpi_3_cpuinfo.dump │ │ └── incorrect_cpuinfo.dump │ ├── keyboard │ │ ├── no_keyboard.dump │ │ ├── other_keyboard.dump │ │ ├── en_keyboard.dump │ │ └── es_keyboard.dump │ ├── __init__.py │ ├── env │ │ ├── env_root │ │ ├── env_sudo │ │ └── env_user │ ├── system.py │ ├── webapp.py │ ├── cpu.py │ ├── keyboard.py │ └── disk.py ├── __init__.py ├── conftest.py ├── utils │ ├── test_disk.py │ ├── test_system.py │ └── test_user.py ├── test_webapp.py ├── test_rpi_models.py └── test_wifi_countries.py ├── docs ├── source │ ├── readme.rst │ ├── modules.rst │ └── index.rst ├── make.bat └── Makefile ├── icons └── kano-dialog.png ├── media ├── images │ ├── icons.png │ ├── arrow_down.png │ ├── scroll_arrow_up.png │ └── scroll_arrow_down.png └── CSS │ ├── spinner.css │ ├── shutdown.css │ ├── multiline_entry.css │ ├── heading.css │ ├── top_bar.css │ ├── kano_progress.css │ ├── small_orange_button.css │ ├── dialog.css │ ├── colours.css │ ├── kano_combobox.css │ └── kano_button.css ├── requirements-dev.txt ├── etc ├── network │ └── interfaces └── wpa_supplicant │ └── wpa_supplicant.conf ├── libs ├── kano-networking │ ├── pyinterface │ │ ├── __init__.py │ │ └── ifaces.py │ ├── includes │ │ └── kano │ │ │ └── networking │ │ │ └── ifaces.h │ ├── Makefile │ └── src │ │ └── ifaces.c ├── README.md ├── kano-c-logging │ ├── includes │ │ └── kano_c_logging.h │ ├── src │ │ └── kano_c_logging.c │ └── Makefile ├── kano │ ├── includes │ │ └── kano │ │ │ └── kano │ │ │ └── kano_bindings.h │ ├── Makefile │ └── src │ │ ├── kano_network.cpp │ │ └── kano_utils_audio.cpp ├── kano-python │ ├── Makefile │ ├── includes │ │ └── kano │ │ │ └── python │ │ │ ├── python_helpers.h │ │ │ └── python_test_helpers.h │ └── src │ │ └── python_helpers.cpp └── parson │ ├── Makefile │ └── lib │ └── json_helpers.h ├── .coveragerc ├── kano ├── gtk3 │ ├── __init__.py │ ├── cursor.py │ ├── icons.py │ ├── scrolled_window.py │ ├── blur_overlay.py │ ├── application_window.py │ ├── heading.py │ ├── apply_styles.py │ ├── labelled_entries.py │ └── kano_progress.py ├── utils │ ├── lua │ │ ├── __init__.py │ │ └── lua_to_json.lua │ ├── gui.py │ ├── misc.py │ ├── dbus_interface.py │ ├── system.py │ ├── user.py │ ├── disk.py │ ├── http_requests.py │ ├── processes.py │ ├── shell.py │ └── audio.py ├── __init__.py ├── timeout.py ├── paths.py ├── pydebug.py ├── profiling.py ├── xwindow.py ├── logging.py └── decorators.py ├── pytest.ini ├── kano-launcher ├── Makefile ├── README.md └── kano-kill-ns.c ├── Jenkinsfile ├── bin ├── colour_echo ├── is_internet ├── rpi-info ├── json-get ├── kano-led ├── kano-set-system-date ├── kano-progress ├── kano-dialog ├── kano-sentry-startup ├── kano-camera ├── kano-volume ├── colours-cli ├── kano-network-hook ├── kano-test-dhcp ├── kano-window-tool └── typewriter_echo ├── kano-keys-pressed ├── Makefile ├── README.md ├── hid.h └── kano-keys-pressed.cpp ├── dhcpcd-hooks ├── 60-kano-dhcp-debug └── 65-kano-dhcp-hook ├── uinput ├── README.md └── LICENSE ├── po ├── en_QQ.po ├── ja.po ├── Makefile └── es_AR.po ├── internal └── iospeed ├── bash └── logging_py.sh ├── Makefile ├── ifplugd └── kano-ifplugd ├── pythontest.mk ├── js └── backend-api.js └── TRANSLATION.md /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /share/kano-launcher/conf/kano-credits: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /share/kano-launcher/conf/kano-draw: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /share/kano-launcher/conf/kano-settings: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /share/kano-launcher/conf/make-music: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /share/kano-launcher/conf/make-pong: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /share/kano-launcher/conf/kano-kill-apps: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /share/kano-launcher/conf/linux-story-gui: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /share/kano-launcher/conf/make-minecraft: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/cpuinfo/empty_cpuinfo.dump: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /debian/libkano-dev.install: -------------------------------------------------------------------------------- 1 | libs/kano/includes/* usr/include/ 2 | -------------------------------------------------------------------------------- /debian/libkano.links: -------------------------------------------------------------------------------- 1 | usr/lib/libkano.so.1 usr/lib/libkano.so 2 | -------------------------------------------------------------------------------- /debian/libparson-dev.install: -------------------------------------------------------------------------------- 1 | libs/parson/lib/*.h usr/include/parson/ 2 | -------------------------------------------------------------------------------- /debian/libparson.links: -------------------------------------------------------------------------------- 1 | usr/lib/libparson.so.1 usr/lib/libparson.so 2 | -------------------------------------------------------------------------------- /debian/libkano-python-dev.install: -------------------------------------------------------------------------------- 1 | libs/kano-python/includes/* usr/include/ 2 | -------------------------------------------------------------------------------- /docs/source/readme.rst: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | 4 | .. include:: ../../README.rst 5 | -------------------------------------------------------------------------------- /debian/libkano-c-logging-dev.install: -------------------------------------------------------------------------------- 1 | libs/kano-c-logging/includes/* usr/include/ 2 | -------------------------------------------------------------------------------- /debian/libkano-networking-dev.install: -------------------------------------------------------------------------------- 1 | libs/kano-networking/includes/* usr/include/ 2 | -------------------------------------------------------------------------------- /debian/libkano-python.links: -------------------------------------------------------------------------------- 1 | usr/lib/libkano_python.so.1 usr/lib/libkano_python.so 2 | -------------------------------------------------------------------------------- /tests/fixtures/cpuinfo/empty_cpuinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "serial": "", 3 | "revision": "" 4 | } 5 | -------------------------------------------------------------------------------- /debian/libkano-c-logging.links: -------------------------------------------------------------------------------- 1 | usr/lib/libkano_c_logging.so.1 usr/lib/libkano_c_logging.so 2 | -------------------------------------------------------------------------------- /debian/libkano-networking.links: -------------------------------------------------------------------------------- 1 | usr/lib/libkano_networking.so.1 usr/lib/libkano_networking.so 2 | -------------------------------------------------------------------------------- /tests/fixtures/cpuinfo/garbage_cpuinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "serial": "", 3 | "revision": "" 4 | } 5 | -------------------------------------------------------------------------------- /debian/kano-toolset-i18n-orig.install: -------------------------------------------------------------------------------- 1 | po/messages.pot /mnt/translations/translations/kano-toolset/ 2 | -------------------------------------------------------------------------------- /debian/libkano.install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dh-exec 2 | libs/kano/release/libkano.so => usr/lib/libkano.so.1 3 | -------------------------------------------------------------------------------- /icons/kano-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KanoComputing/kano-toolset/HEAD/icons/kano-dialog.png -------------------------------------------------------------------------------- /media/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KanoComputing/kano-toolset/HEAD/media/images/icons.png -------------------------------------------------------------------------------- /debian/libparson.install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dh-exec 2 | libs/parson/release/libparson.so => usr/lib/libparson.so.1 3 | -------------------------------------------------------------------------------- /debian/libkano-dbg.install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dh-exec 2 | libs/kano/debug/libkano.so => usr/lib/debug/usr/lib/libkano.so.1 3 | -------------------------------------------------------------------------------- /media/images/arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KanoComputing/kano-toolset/HEAD/media/images/arrow_down.png -------------------------------------------------------------------------------- /share/kano-launcher/conf/make-snake: -------------------------------------------------------------------------------- 1 | match_only_preset 2 | extra_cmd: kano-window-tool -t "Make Snake" -dyes -f 3 | -------------------------------------------------------------------------------- /tests/fixtures/cpuinfo/rpi_2_cpuinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "serial": "00000000023913DE", 3 | "revision": "1a01041" 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/cpuinfo/rpi_3_cpuinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "serial": "000000008692C11F", 3 | "revision": "a22082" 4 | } 5 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | behave 2 | coverage-badge 3 | mock 4 | pyfakefs==3.3.0 5 | pytest 6 | pytest-cov 7 | pytest-flake8 8 | -------------------------------------------------------------------------------- /debian/libparson-dbg.install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dh-exec 2 | libs/parson/debug/libparson.so => usr/lib/debug/usr/lib/libparson.so.1 3 | -------------------------------------------------------------------------------- /media/images/scroll_arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KanoComputing/kano-toolset/HEAD/media/images/scroll_arrow_up.png -------------------------------------------------------------------------------- /tests/fixtures/cpuinfo/incorrect_cpuinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "serial": "INVALID_SERIAL", 3 | "revision": "invalid_revision" 4 | } 5 | -------------------------------------------------------------------------------- /debian/libkano-python.install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dh-exec 2 | libs/kano-python/release/libkano_python.so => usr/lib/libkano_python.so.1 3 | -------------------------------------------------------------------------------- /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | kano-toolset 2 | ================ 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | kano 8 | tests 9 | -------------------------------------------------------------------------------- /media/images/scroll_arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KanoComputing/kano-toolset/HEAD/media/images/scroll_arrow_down.png -------------------------------------------------------------------------------- /debian/libkano-c-logging.install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dh-exec 2 | libs/kano-c-logging/release/libkano_c_logging.so => usr/lib/libkano_c_logging.so.1 3 | -------------------------------------------------------------------------------- /debian/libkano-python-dbg.install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dh-exec 2 | libs/kano-python/debug/libkano_python.so => usr/lib/debug/usr/lib/libkano_python.so.1 3 | -------------------------------------------------------------------------------- /debian/libkano-c-logging-dbg.install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dh-exec 2 | libs/kano-c-logging/debug/libkano_c_logging.so => usr/lib/debug/usr/lib/libkano_c_logging.so.1 3 | -------------------------------------------------------------------------------- /debian/libkano-networking-dbg.install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dh-exec 2 | libs/kano-networking/debug/libkano_networking.so => usr/lib/debug/usr/lib/libkano_networking.so.1 3 | -------------------------------------------------------------------------------- /etc/network/interfaces: -------------------------------------------------------------------------------- 1 | # Minimal configuration file to init networking.service 2 | # Ensures that the loopback network interface lo is up on init 3 | auto lo 4 | iface lo inet loopback 5 | -------------------------------------------------------------------------------- /share/kano-launcher/conf/pcmanfm: -------------------------------------------------------------------------------- 1 | no_kill 2 | match_only_preset 3 | extra_cmd: if [ -e /usr/bin/kano-profile-cli ]; then kano-profile-cli increment_app_state_variable pcmanfm starts 1 ; fi 4 | 5 | -------------------------------------------------------------------------------- /etc/wpa_supplicant/wpa_supplicant.conf: -------------------------------------------------------------------------------- 1 | # Minimal configuration file for the wpa supplicant 2 | # It ensures correct initialisation on service startup 3 | ctrl_interface=/run/wpa_supplicant 4 | update_config=1 5 | -------------------------------------------------------------------------------- /debian/libkano-networking.install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dh-exec 2 | libs/kano-networking/release/libkano_networking.so => usr/lib/libkano_networking.so.1 3 | libs/kano-networking/pyinterface/* usr/lib/python2.7/dist-packages/kano_networking/ 4 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # __init__.py 3 | # 4 | # Copyright (C) 2016 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | # 7 | 8 | __author__ = 'Kano Computing Ltd.' 9 | __email__ = 'dev@kano.me' 10 | -------------------------------------------------------------------------------- /libs/kano-networking/pyinterface/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py 2 | # 3 | # Copyright (C) 2016 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | 7 | __author__ = 'Kano Computing Ltd.' 8 | __email__ = 'dev@kano.me' 9 | -------------------------------------------------------------------------------- /tests/fixtures/cpuinfo/garbage_cpuinfo.dump: -------------------------------------------------------------------------------- 1 | dkfjaldfkjdf'adksjf kadvnoadkfddkfjaf 2 | dfkadjf\dsajfkadsjfladsjf 3 | adsfkjdsfkjdsafoasmv 4 | dsockvnckxnc09fdifnx kvbmf 5 | vjcxkvjxz]kvk 6 | pfdfcv45czv5c4v5cd4vcv\ 7 | czxv 8 | c5vc4xv65c4v5c4v 9 | ccjh 10 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = . 4 | omit = 5 | tests/* 6 | features/* 7 | data_file = reports/coverage/coverage 8 | 9 | 10 | [html] 11 | directory = reports/coverage/html 12 | 13 | [xml] 14 | output = reports/coverage/coverage.xml 15 | 16 | -------------------------------------------------------------------------------- /tests/fixtures/keyboard/no_keyboard.dump: -------------------------------------------------------------------------------- 1 | Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter 2 | Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. 3 | Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub 4 | -------------------------------------------------------------------------------- /media/CSS/spinner.css: -------------------------------------------------------------------------------- 1 | /** 2 | * spinner.css 3 | * 4 | * Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | * 7 | * Spinner styling 8 | */ 9 | 10 | 11 | spinner { 12 | color: #ffffff; 13 | } 14 | -------------------------------------------------------------------------------- /tests/fixtures/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py 2 | # 3 | # Copyright (C) 2017 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Python package definition. 7 | 8 | 9 | __author__ = 'Kano Computing Ltd.' 10 | __email__ = 'dev@kano.me' 11 | -------------------------------------------------------------------------------- /kano/gtk3/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # __init__.py 4 | # 5 | # Copyright (C) 2014 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | 9 | __author__ = 'Kano Computing Ltd.' 10 | __email__ = 'dev@kano.me' 11 | -------------------------------------------------------------------------------- /kano/utils/lua/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py 2 | # 3 | # Copyright (C) 2014-2016 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Init for the kano utils lua module 7 | 8 | 9 | __author__ = 'Kano Computing Ltd.' 10 | __email__ = 'dev@kano.me' 11 | -------------------------------------------------------------------------------- /share/kano-launcher-no-kill-list: -------------------------------------------------------------------------------- 1 | kano-launcher 2 | dbus-daemon 3 | dbus-launch 4 | kdesk 5 | lxsession 6 | openbox 7 | ssh-agent 8 | lxpanel 9 | menu-cached 10 | python /usr/bin/kano-feedback-widget 11 | python /usr/bin/kano-tracker-ctl 12 | /usr/bin/xbindkeys 13 | x11vnc 14 | lxterminal 15 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | gtk: mark a test as requiring Gtk installed. 4 | lua: mark a test as requiring a Lua interpreter. 5 | flake8-ignore = 6 | E402 # Module level import not at top of file 7 | E501 # Line Too Long 8 | uinput/* ALL 9 | docs/* ALL 10 | features/* ALL 11 | tests/* ALL 12 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Contact: Kano developers 3 | 4 | Files: * 5 | Copyright: 2013,2014,2015 Kano Computing Ltd. 6 | License: GPL-2+ 7 | 8 | Files: libs/parson/lib/* 9 | Copyright: 2012-2016 Krzysztof Gabis (kgabis@gmail.com) 10 | License: MIT 11 | -------------------------------------------------------------------------------- /media/CSS/shutdown.css: -------------------------------------------------------------------------------- 1 | /** 2 | * shutdown.css 3 | * 4 | * Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | * 7 | * Styling for the shutdown 8 | */ 9 | 10 | 11 | .shutdown_title { 12 | color: #222222; 13 | font-family: Bariol Bold; 14 | font-size: 16pt; 15 | padding-top: 10px; 16 | } 17 | -------------------------------------------------------------------------------- /debian/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # postrm 4 | # 5 | # Copyright (C) 2014 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | 9 | case "$1" in 10 | remove|upgrade) 11 | # Remove config file 12 | rm /etc/sudoers.d/kano-toolset_conf 13 | 14 | ;; 15 | esac 16 | 17 | #DEBHELPER# 18 | 19 | exit 0 20 | -------------------------------------------------------------------------------- /kano-launcher/Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: kano-launcher kano-kill-ns 3 | 4 | LIBS=-L../libs/kano-c-logging/release/ -lkano_c_logging 5 | INCLUDES=-I../libs/kano-c-logging/includes 6 | kano-launcher: 7 | gcc -std=c99 -D_BSD_SOURCE -gdwarf-2 $(INCLUDES) kano-launcher.c $(LIBS) -o kano-launcher 8 | 9 | kano-kill-ns: 10 | gcc -std=c99 -D_BSD_SOURCE -gdwarf-2 $(INCLUDES) kano-kill-ns.c $(LIBS) -o kano-kill-ns 11 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | @Library('kanolib') 4 | import build_deb_pkg 5 | import python_test_env 6 | 7 | 8 | def repo_name = 'kano-toolset' 9 | 10 | 11 | stage ('Test') { 12 | python_test_env(['kano-i18n']) { python_path_var -> 13 | } 14 | } 15 | 16 | stage ('Build') { 17 | autobuild_repo_pkg "$repo_name" 18 | } 19 | 20 | stage ('Docs') { 21 | build_docs "$repo_name" 22 | } 23 | -------------------------------------------------------------------------------- /bin/colour_echo: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # typewriter_echo 4 | # 5 | # Copyright (C) 2014 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # Print a formatted string 9 | # "my {{idstring}}" > where id is a number from 0-9 which represents a preset 10 | # The preset list is in colours-cli 11 | 12 | string=$(python /usr/bin/colours-cli "$1") 13 | echo $string 14 | -------------------------------------------------------------------------------- /kano-keys-pressed/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # kano-keys-pressed 3 | # 4 | # A tool to detect key modifers being pressed (Shift, Ctrl, Alt, ...) 5 | # 6 | 7 | all: kano-keys-pressed 8 | 9 | kano-keys-pressed: kano-keys-pressed.o hid.o 10 | g++ -g kano-keys-pressed.o hid.o -o kano-keys-pressed 11 | 12 | hid.o: hid.cpp 13 | g++ -c -g hid.cpp 14 | 15 | kano-keys-pressed.o: kano-keys-pressed.cpp 16 | g++ -c kano-keys-pressed.cpp 17 | -------------------------------------------------------------------------------- /libs/README.md: -------------------------------------------------------------------------------- 1 | # Libs 2 | 3 | 4 | ## libkano-networking 5 | 6 | Module to provide accelerated access to networking functions. 7 | 8 | 9 | ## libkano-python 10 | 11 | Helpers for binding Python with C++. 12 | 13 | 14 | ## libparson 15 | 16 | Shared library version of the great JSON library by Krzysztof Gabis. 17 | 18 | 19 | ## libkano 20 | 21 | C++ bindings for the `kano` Python module. This requires `libkano-python`. 22 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | cd po && make messages.pot 5 | cd po && make 6 | dh $@ 7 | 8 | .PHONY: override_dh_strip 9 | override_dh_strip: 10 | dh_strip -plibkano-networking --dbg-package=libkano-networking-dbg 11 | dh_strip -plibkano-python --dbg-package=libkano-python-dbg 12 | dh_strip -plibparson --dbg-package=libparson-dbg 13 | dh_strip -plibkano-c-logging --dbg-package=libkano-c-logging-dbg 14 | 15 | override_dh_auto_test: 16 | -------------------------------------------------------------------------------- /tests/fixtures/keyboard/other_keyboard.dump: -------------------------------------------------------------------------------- 1 | Bus 001 Device 006: ID 148f:5370 Ralink Technology, Corp. RT5370 Wireless Adapter 2 | Bus 001 Device 005: ID 045e:07b9 Microsoft Corp. 3 | Bus 001 Device 004: ID 046d:c52b Logitech, Inc. Unifying Receiver 4 | Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter 5 | Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. 6 | Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub 7 | -------------------------------------------------------------------------------- /tests/fixtures/keyboard/en_keyboard.dump: -------------------------------------------------------------------------------- 1 | Bus 001 Device 007: ID 1997:2433 2 | Bus 001 Device 006: ID 148f:5370 Ralink Technology, Corp. RT5370 Wireless Adapter 3 | Bus 001 Device 005: ID 045e:07b9 Microsoft Corp. 4 | Bus 001 Device 004: ID 046d:c52b Logitech, Inc. Unifying Receiver 5 | Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter 6 | Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. 7 | Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub 8 | -------------------------------------------------------------------------------- /tests/fixtures/keyboard/es_keyboard.dump: -------------------------------------------------------------------------------- 1 | Bus 001 Device 007: ID 1997:2434 2 | Bus 001 Device 006: ID 148f:5370 Ralink Technology, Corp. RT5370 Wireless Adapter 3 | Bus 001 Device 005: ID 045e:07b9 Microsoft Corp. 4 | Bus 001 Device 004: ID 046d:c52b Logitech, Inc. Unifying Receiver 5 | Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter 6 | Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. 7 | Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub 8 | -------------------------------------------------------------------------------- /tests/fixtures/env/env_root: -------------------------------------------------------------------------------- 1 | XDG_VTNR=2 2 | XDG_SESSION_ID=c6 3 | SHELL=/bin/bash 4 | TERM=linux 5 | HUSHLOGIN=FALSE 6 | USER=root 7 | MAIL=/var/mail/root 8 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/sbin::/usr/sbin:/opt/vc/bin/:/opt/vc/sbin/ 9 | PWD=/root 10 | LANG=en_US.UTF-8 11 | NODE_PATH=/usr/lib/nodejs:/usr/lib/node_modules:/usr/share/javascript 12 | SHLVL=1 13 | XDG_SEAT=seat0 14 | HOME=/root 15 | LOGNAME=root 16 | LC_CTYPE=en_US.UTF-8 17 | XDG_RUNTIME_DIR=/run/user/0 18 | _=/usr/bin/env 19 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # 2 | # conftest.py 3 | # 4 | # Copyright (C) 2017 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | # 7 | # The pytest conftest file 8 | # 9 | # Import fixtures and setup the tests 10 | # 11 | 12 | 13 | from tests.fixtures.webapp import * 14 | from tests.fixtures.users import * 15 | from tests.fixtures.system import * 16 | from tests.fixtures.cpu import * 17 | from tests.fixtures.disk import * 18 | from tests.fixtures.boards import * 19 | from tests.fixtures.keyboard import * 20 | -------------------------------------------------------------------------------- /libs/kano-networking/includes/kano/networking/ifaces.h: -------------------------------------------------------------------------------- 1 | /** 2 | * ifaces.h 3 | * 4 | * Copyright (C) 2016 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | * 7 | * Methods for interracting with interfaces 8 | */ 9 | 10 | #ifndef __KANO_NETWORKING_IFACES__ 11 | #define __KANO_NETWORKING_IFACES__ 12 | 13 | enum ERRORS { 14 | E_NO_INTERFACES = -1 15 | }; 16 | 17 | int select_iface(const char *iface_type, char **iface_name); 18 | int check_iface_type(const char *iface_type); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /dhcpcd-hooks/60-kano-dhcp-debug: -------------------------------------------------------------------------------- 1 | # 2 | # 60-kano-dhcp-debug 3 | # 4 | # Copyright (C) 2017 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | # 7 | # Simple script to diagnose dhcpcd events. 8 | # To enable, edit /etc/systemd/journalctl.conf and set Storage=persistent 9 | # 10 | 11 | ipaddr=`ip addr show wlan0 | grep inet | awk '{print $2}' | sed -s 's/\/.*//')` 12 | logger "kano-dhcp-debug: $reason ifup? $if_up interface? $interface carrier? $ifcarrier wireless? $ifwireless ssid=$ifssid ipaddr=$ipaddr" 13 | -------------------------------------------------------------------------------- /libs/kano-c-logging/includes/kano_c_logging.h: -------------------------------------------------------------------------------- 1 | // kano-log.c 2 | // 3 | // Copyright (C) 2015 Kano Computing Ltd. 4 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 5 | // 6 | // Implementation of logging 7 | 8 | // Currently uses syslog, needs to change to use out log files 9 | 10 | #ifndef KANO_LOG_H 11 | #define KANO_LOG_H 12 | 13 | void kano_log_error(char *msg,...); 14 | void kano_log_warning(char *msg,...); 15 | void kano_log_info(char *msg,...); 16 | void kano_log_debug(char *msg,...); 17 | 18 | #endif // KANO_LOG_H 19 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. kano-toolset documentation master file, created by 2 | sphinx-quickstart on Fri Jan 12 17:50:02 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to kano-toolset's documentation! 7 | ============================================ 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | readme 14 | modules 15 | 16 | 17 | Indices and tables 18 | ================== 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /uinput/README.md: -------------------------------------------------------------------------------- 1 | ## uinput 2 | 3 | This tool is a modification from Robert Thomson PyInputEvent project at: https://github.com/rmt/pyinputevent 4 | It is used as an internal tool to add the capability to simulate keyboard events into the Linux system. 5 | 6 | To send a keyboard event you will first need to know the scancode, then you can do: 7 | 8 | ``` 9 | $ sudo python uinptu 10 | ``` 11 | 12 | You can find most scancodes in the module `scancodes.py`. 13 | 14 | This is an experimental tool which is not reliable enough, hence it is not currently used or packaged into KanoOS. 15 | -------------------------------------------------------------------------------- /media/CSS/multiline_entry.css: -------------------------------------------------------------------------------- 1 | /** 2 | * multiline_entry.css 3 | * 4 | * Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | * 7 | * This controls the styling of the MultilineEntry custom widget. 8 | */ 9 | 10 | 11 | .gray-box { 12 | background: #afacaf; 13 | border-radius: 4px; 14 | } 15 | 16 | .white-box { 17 | background: white; 18 | border-radius: 2px; 19 | } 20 | 21 | .placeholder-text { 22 | font-size: 14px; 23 | color: #7f7c7f; 24 | } 25 | 26 | .entry-text { 27 | font-size: 14px; 28 | color: black; 29 | } 30 | -------------------------------------------------------------------------------- /kano/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # __init__.py 4 | # 5 | # Copyright (C) 2014-2019 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 7 | # 8 | 9 | __author__ = 'Kano Computing Ltd.' 10 | __email__ = 'dev@kano.me' 11 | 12 | import os 13 | import sys 14 | 15 | DIR_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 16 | if DIR_PATH != '/usr': 17 | sys.path.insert(1, DIR_PATH) 18 | LOCALE_PATH = os.path.join(DIR_PATH, 'locale') 19 | else: 20 | LOCALE_PATH = None 21 | 22 | import kano_i18n.init 23 | kano_i18n.init.register_domain('kano-toolset', LOCALE_PATH) 24 | -------------------------------------------------------------------------------- /media/CSS/heading.css: -------------------------------------------------------------------------------- 1 | /** 2 | * heading.css 3 | * 4 | * Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | * 7 | * Heading styling 8 | */ 9 | 10 | 11 | @define-color title_color #323232; 12 | @define-color subheading_color #6e6e6e; 13 | 14 | /* The title of the current level*/ 15 | .title { 16 | color: @title_color; 17 | padding-top:30px; 18 | font-family: Bariol Bold; 19 | font-size: 20px; 20 | } 21 | /* Description of current project */ 22 | .description { 23 | color: @subheading_color; 24 | padding-left: 30px; 25 | padding-right: 30px; 26 | font-family: Bariol; 27 | font-size: 14pt; 28 | } 29 | -------------------------------------------------------------------------------- /po/en_QQ.po: -------------------------------------------------------------------------------- 1 | # 2 | msgid "" 3 | msgstr "" 4 | "Project-Id-Version: kano-toolset\n" 5 | "Report-Msgid-Bugs-To: \n" 6 | "POT-Creation-Date: 2016-08-26 11:23+0000\n" 7 | "PO-Revision-Date: 2016-23-26 11:08+0000\n" 8 | "Last-Translator: autogenerated\n" 9 | "Language-Team: dev@kano.me\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "\n" 13 | "Content-Transfer-Encoding: 8bit\n" 14 | "Language: en\n" 15 | 16 | #: ../kano/gtk3/kano_dialog.py:81 ../kano/gtk3/kano_dialog.py:189 17 | #: ../kano/gtk3/kano_dialog.py:223 ../kano/gtk3/kano_dialog.py:227 18 | msgid "OK" 19 | msgstr "OK" 20 | 21 | #: ../kano/gtk3/kano_dialog.py:191 ../kano/gtk3/kano_dialog.py:194 22 | msgid "CANCEL" 23 | msgstr "CANCEL" 24 | -------------------------------------------------------------------------------- /bin/is_internet: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # is_internet 4 | # 5 | # Copyright (C) 2014-2018 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 7 | # 8 | # Checks internet connectivity by fast pinging TEST_HOST 9 | # Returns 0 if connection is ok, non-zero otherwise. 10 | # The command is silent, no console output. 11 | 12 | 13 | TEST_PING=8.8.8.8 14 | 15 | fping -c 10 -p 30ms -q $TEST_PING > /dev/null 2>&1 16 | rc=$? 17 | if [ $rc -ne 0 ]; then 18 | curl_val=`curl --max-time 5 --silent --output /dev/null --head --write-out "%{http_code}" "http://www.google.com/favicon.ico"` 19 | if [ $curl_val -eq 200 ]; then 20 | rc=0 21 | else 22 | rc=5 23 | fi 24 | fi 25 | 26 | exit $rc 27 | -------------------------------------------------------------------------------- /kano-keys-pressed/README.md: -------------------------------------------------------------------------------- 1 | ## kano-keys-pressed 2 | 3 | This tool is used in KanoOS to detect the keys Ctrl-Alt are being pressed during boot, 4 | and switch to a *safe mode* display settings. 5 | 6 | Usage is pretty straightforward, run `kano-keys-pressed` while holding the Ctrl-Alt keys down, 7 | and it will return with rc=10, or zero otherwise. 8 | 9 | Two options are available to allow for some delay time in detecting the keys. 10 | 11 | `-r` sets a number of times the key combination is checked on the keyboard. 12 | `-d` sets a delay time in milliseconds to wait between each retry. 13 | 14 | Call `kano-keys-pressed -h` to get a comprehensive help page. Also the `-v` flag will emit 15 | information messages to explain what is going on. 16 | -------------------------------------------------------------------------------- /media/CSS/top_bar.css: -------------------------------------------------------------------------------- 1 | /** 2 | * top_bar.css 3 | * 4 | * Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | * 7 | * Styling for the window handle 8 | */ 9 | 10 | 11 | @define-color top_bar_color #e1e1e1; 12 | 13 | 14 | /* The bar at the top that mimics the window bar */ 15 | .top_bar_container { 16 | background: @top_bar_color; 17 | border-radius: 0px; 18 | } 19 | 20 | .top_bar_button { 21 | background: @top_bar_color; 22 | border-radius: 0px; 23 | } 24 | 25 | .top_bar_button:hover { 26 | background: @top_bar_color; 27 | } 28 | 29 | /* Header in top bar (title of window) */ 30 | .top_bar_title { 31 | color: #222222; 32 | font-family: Bariol Bold; 33 | font-size: 13pt; 34 | } 35 | -------------------------------------------------------------------------------- /kano/utils/gui.py: -------------------------------------------------------------------------------- 1 | # gui.py 2 | # 3 | # Copyright (C) 2014-2016 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Utilities relating to UI 7 | 8 | 9 | import os 10 | import subprocess 11 | 12 | from kano.utils.shell import restore_signals 13 | 14 | 15 | def is_gui(): 16 | return 'DISPLAY' in os.environ 17 | 18 | 19 | def zenity_show_progress(msg, title=None): 20 | if title is None: 21 | title = '' 22 | if is_gui(): 23 | cmd = 'yes | zenity --progress --text="{}" --pulsate --no-cancel ' + \ 24 | '--auto-close --title="{}"' 25 | p = subprocess.Popen( 26 | cmd.format(msg, title), 27 | shell=True, 28 | preexec_fn=restore_signals 29 | ) 30 | return p.pid 31 | -------------------------------------------------------------------------------- /tests/fixtures/system.py: -------------------------------------------------------------------------------- 1 | # 2 | # system.py 3 | # 4 | # Copyright (C) 2017 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | # 7 | # Fixtures for system functions 8 | # 9 | 10 | 11 | import sys 12 | import pytest 13 | 14 | 15 | @pytest.fixture(scope='function') 16 | def fake_sys_exit(monkeypatch): 17 | ''' 18 | Overrides the sys.exit() funciton to prevent termination and to keep track 19 | of whether it was called. 20 | ''' 21 | 22 | class ExitState(object): 23 | quit = False 24 | exit_code = 0 25 | 26 | exit_state = ExitState() 27 | 28 | def fake_quit(exit_code=0): 29 | exit_state.quit = True 30 | exit_state.exit_code = exit_code 31 | 32 | monkeypatch.setattr( 33 | sys, 'exit', fake_quit 34 | ) 35 | 36 | return exit_state 37 | -------------------------------------------------------------------------------- /debian/kano-toolset.install: -------------------------------------------------------------------------------- 1 | bin usr/ 2 | kano usr/lib/python2.7/dist-packages/ 3 | media usr/share/kano/ 4 | 5 | js usr/share/kano-toolset/ 6 | 7 | icons/* usr/share/icons/Kano/66x66/apps/ 8 | 9 | uinput/uinput usr/bin/ 10 | uinput/pyinputevent.py usr/lib/python2.7/dist-packages/ 11 | uinput/scancodes.py usr/lib/python2.7/dist-packages/ 12 | 13 | dhcpcd-hooks/65-kano-dhcp-hook /lib/dhcpcd/dhcpcd-hooks 14 | dhcpcd-hooks/60-kano-dhcp-debug /usr/share/kano-toolset/dhcpcd-hooks 15 | 16 | ifplugd/kano-ifplugd etc/ifplugd/action.d 17 | 18 | bash/logging.sh usr/share/kano-toolset/ 19 | bash/logging_py.sh usr/share/kano-toolset/ 20 | 21 | kano-keys-pressed/kano-keys-pressed /usr/bin 22 | 23 | share/kano-launcher/conf usr/share/kano-toolset/kano-launcher/ 24 | kano-launcher/kano-launcher usr/bin/ 25 | kano-launcher/kano-kill-ns usr/bin/ 26 | 27 | locale/* usr/share/locale 28 | etc/* etc/ 29 | -------------------------------------------------------------------------------- /kano/utils/misc.py: -------------------------------------------------------------------------------- 1 | # misc.py 2 | # 3 | # Copyright (C) 2014-2016 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Miscellaneous utilities 7 | 8 | 9 | import datetime 10 | 11 | from kano.utils.shell import run_cmd 12 | 13 | 14 | def get_date_now(): 15 | return datetime.datetime.utcnow().isoformat() 16 | 17 | 18 | def is_number(string): 19 | try: 20 | float(string) 21 | return True 22 | except (ValueError, TypeError): 23 | return False 24 | 25 | 26 | def uniqify_list(seq): 27 | # Order preserving 28 | seen = set() 29 | return [x for x in seq if x not in seen and not seen.add(x)] 30 | 31 | 32 | def is_installed(program): 33 | ''' 34 | Returns True if "program" is recognized as an executable command in PATH 35 | ''' 36 | _, _, rc = run_cmd('which {}'.format(program)) 37 | return rc == 0 38 | -------------------------------------------------------------------------------- /tests/utils/test_disk.py: -------------------------------------------------------------------------------- 1 | # 2 | # test_system.py 3 | # 4 | # Copyright (C) 2018 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | # 7 | # Tests for the kano.utils.system module 8 | # 9 | 10 | 11 | DF_OUTPUT = ''' 12 | Filesystem 1K-blocks Used Available Use% Mounted on 13 | /dev/root 7399352 4011436 3021788 58% / 14 | '''.lstrip() 15 | DF_FREE_MB = 2950 16 | 17 | 18 | LSBLK_OUTPUT = ''' 19 | 7948206080 20 | 100663808 21 | 7834959872 22 | '''.lstrip() 23 | LSBLK_PARTITION_INFO = [ 24 | 7948206080, 25 | 100663808, 26 | 7834959872, 27 | ] 28 | 29 | 30 | def test_free_space(df): 31 | from kano.utils.disk import get_free_space 32 | assert get_free_space() == DF_FREE_MB 33 | 34 | 35 | 36 | def test_partition_info(lsblk): 37 | from kano.utils.disk import get_partition_info 38 | assert get_partition_info() == LSBLK_PARTITION_INFO 39 | -------------------------------------------------------------------------------- /media/CSS/kano_progress.css: -------------------------------------------------------------------------------- 1 | /** 2 | * kano_progress.css 3 | * 4 | * Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | * 7 | * Progress bar styling 8 | */ 9 | 10 | 11 | * { 12 | -GtkProgressBar-min-horizontal-bar-height: 30; 13 | -GtkProgressBar-min-vertical-bar-width: 60; 14 | } 15 | 16 | progressbar.KanoProgressBar { 17 | border-style: solid; 18 | border-color: #dddddd; 19 | border-width: 3px; 20 | border-radius: 3px; 21 | } 22 | 23 | /* background bar */ 24 | progressbar.KanoProgressBar trough { 25 | background: #ffffff; 26 | } 27 | 28 | /* Moving part*/ 29 | progressbar.KanoProgressBar progress { 30 | background: @kano_green; 31 | border-radius: 3px; 32 | border-color: transparent; 33 | } 34 | 35 | label.KanoProgressBar { 36 | font-family: Bariol Bold; 37 | font-size: 15pt; 38 | color: @active_writing; 39 | } 40 | -------------------------------------------------------------------------------- /libs/kano-networking/pyinterface/ifaces.py: -------------------------------------------------------------------------------- 1 | # ifaces.py 2 | # 3 | # Copyright (C) 2016-2019 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Module to interface with the ifaces module of the libkano_networking library 7 | # 8 | 9 | import os 10 | import ctypes 11 | 12 | NETWORKING_LIB = 'libkano_networking.so' 13 | 14 | try: 15 | LIB = ctypes.CDLL(NETWORKING_LIB) 16 | except OSError: 17 | LIB = ctypes.CDLL(os.path.join(os.path.dirname(__file__), NETWORKING_LIB)) 18 | 19 | 20 | def get_iface(iface_type_str): 21 | iface_type = ctypes.create_string_buffer(iface_type_str) 22 | 23 | str_p = ctypes.c_char_p() 24 | pt = ctypes.pointer(str_p) 25 | 26 | if LIB.select_iface(iface_type, pt) != 0: 27 | return False 28 | 29 | return ctypes.string_at(str_p) 30 | 31 | 32 | def get_wlan_device(): 33 | return get_iface('wlan') 34 | 35 | 36 | def get_eth_device(): 37 | return get_iface('eth') 38 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | set SPHINXPROJ=kano-toolset 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /kano/gtk3/cursor.py: -------------------------------------------------------------------------------- 1 | # 2 | # cursor.py 3 | # 4 | # Copyright (C) 2014-2019 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | # 7 | # Functions that changes the cursor's appearence 8 | # 9 | 10 | from gi import require_version 11 | require_version('Gtk', '3.0') 12 | 13 | from gi.repository import Gdk 14 | 15 | 16 | # win is passed through as an argument 17 | def hand_cursor(button, event): 18 | # Change the cursor to hand 19 | cursor = Gdk.Cursor.new(Gdk.CursorType.HAND1) 20 | button.get_root_window().set_cursor(cursor) 21 | 22 | 23 | def arrow_cursor(button, event): 24 | # Set the cursor to normal Arrow 25 | cursor = Gdk.Cursor.new(Gdk.CursorType.ARROW) 26 | button.get_root_window().set_cursor(cursor) 27 | 28 | 29 | def attach_cursor_events(button): 30 | button.connect('enter-notify-event', hand_cursor) 31 | button.connect('leave-notify-event', arrow_cursor) 32 | button.connect('button-press-event', arrow_cursor) 33 | -------------------------------------------------------------------------------- /media/CSS/small_orange_button.css: -------------------------------------------------------------------------------- 1 | /** 2 | * small_orange_button.css 3 | * 4 | * Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | * 7 | * The main button that updates the changes made in each level 8 | */ 9 | 10 | 11 | .small_orange_button { 12 | background: transparent; 13 | padding: 0px; 14 | } 15 | 16 | .small_orange_button label { 17 | color: @kano_orange; 18 | font-family: Bariol Bold; 19 | font-size: 13px; 20 | } 21 | 22 | .small_orange_button label:disabled { 23 | color: @kano_grey; 24 | background: transparent; 25 | } 26 | 27 | .small_orange_button:hover { 28 | background: transparent; 29 | } 30 | 31 | buttonbox .small_orange_button { 32 | background: transparent; 33 | border-color: transparent; 34 | padding: 0px; 35 | } 36 | 37 | buttonbox .small_orange_button:hover { 38 | background: transparent; 39 | border-color: transparent; 40 | padding: 0px; 41 | } 42 | -------------------------------------------------------------------------------- /internal/iospeed: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # IOSpeed computes disk throughput using the tips exposed 4 | # at elinux SD card comparison chart: 5 | # 6 | # http://elinux.org/RPi_SD_cards#Performance 7 | # 8 | 9 | import os 10 | import sys 11 | 12 | if __name__ == '__main__': 13 | 14 | print 'Disk I/O metrics0' 15 | os.system('uname -a') 16 | 17 | scaling_governor = open('/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor', 'r').read().strip() 18 | if scaling_governor == 'ondemand': 19 | print 'WARNING: cpu scaling_governor:', scaling_governor 20 | 21 | print 'Flushing I/O buffers' 22 | os.system ('sync') 23 | os.system ('sudo /bin/bash -c "echo 1 > /proc/sys/vm/drop_caches"') 24 | 25 | print 'Testing I/O WRITE speed' 26 | os.system('sync; time dd if=/dev/zero of=./test.img bs=500k count=1024; time sync') 27 | 28 | print 'Testing I/O READ speed' 29 | os.system('sync; time dd if=./test.img of=/dev/null bs=500k count=1024') 30 | 31 | sys.exit(0) 32 | -------------------------------------------------------------------------------- /libs/kano-c-logging/src/kano_c_logging.c: -------------------------------------------------------------------------------- 1 | // kano-log.c 2 | // 3 | // Copyright (C) 2015 Kano Computing Ltd. 4 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 5 | // 6 | // Implementation of logging 7 | 8 | // Currently uses syslog, needs to change to use out log files 9 | #include 10 | #include 11 | #include 12 | 13 | void kano_log_error(char *msg,...){ 14 | va_list ap; 15 | va_start(ap,msg); 16 | vsyslog(LOG_USER | LOG_ERR, msg, ap); 17 | va_end(ap); 18 | } 19 | void kano_log_warning(char *msg,...){ 20 | va_list ap; 21 | va_start(ap,msg); 22 | vsyslog(LOG_USER | LOG_WARNING, msg, ap); 23 | va_end(ap); 24 | } 25 | void kano_log_info(char *msg,...){ 26 | va_list ap; 27 | va_start(ap,msg); 28 | vsyslog(LOG_USER | LOG_INFO, msg, ap); 29 | va_end(ap); 30 | } 31 | void kano_log_debug(char *msg,...){ 32 | va_list ap; 33 | va_start(ap,msg); 34 | vsyslog(LOG_USER | LOG_ERR, msg, ap); 35 | va_end(ap); 36 | } 37 | 38 | 39 | -------------------------------------------------------------------------------- /kano/utils/dbus_interface.py: -------------------------------------------------------------------------------- 1 | # dbus_interface.py 2 | # 3 | # Copyright (C) 2016 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # DBus helper functions 7 | # 8 | 9 | import dbus 10 | 11 | 12 | def get_all_properties(bus_proxy, interface): 13 | properties_iface = dbus.Interface( 14 | bus_proxy, 'org.freedesktop.DBus.Properties' 15 | ) 16 | 17 | return { 18 | unicode(key): unicode(value) 19 | for key, value in properties_iface.GetAll(interface).iteritems() 20 | } 21 | 22 | 23 | def get_property(bus_proxy, interface, prop): 24 | properties_iface = dbus.Interface( 25 | bus_proxy, 'org.freedesktop.DBus.Properties' 26 | ) 27 | 28 | return unicode(properties_iface.Get(interface, prop)) 29 | 30 | 31 | def set_property(bus_proxy, interface, prop, val): 32 | properties_iface = dbus.Interface( 33 | bus_proxy, 'org.freedesktop.DBus.Properties' 34 | ) 35 | 36 | return properties_iface.Set(interface, prop, val) 37 | 38 | 39 | def dbus_to_bool(val): 40 | return val == u'1' 41 | -------------------------------------------------------------------------------- /bash/logging_py.sh: -------------------------------------------------------------------------------- 1 | # logging_py.sh 2 | # 3 | # Copyright (C) 2014, 2015 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 5 | # 6 | # Bash wrapper for kano.logging, so you can easily log events from your Bash scripts. 7 | # 8 | # Usage: 9 | # 10 | # . /usr/share/kano-toolset/logging_py.sh 11 | # 12 | # set_app_name "make-minecraft" 13 | # 14 | # logger_write "Error! Error!" 15 | # 16 | 17 | APP_NAME="$0" 18 | 19 | logger_set_app_name() 20 | { 21 | export APP_NAME="$1" 22 | } 23 | 24 | logger_write() 25 | { 26 | __msg="$1" 27 | __level=$2 28 | 29 | __kwargs="" 30 | if [ -n "$__level" ]; then 31 | __kwargs="$__kwargs, level=\"$__level\"" 32 | fi 33 | 34 | python <, 2016. 6 | # 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: kano-toolset\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2016-07-15 22:30+0900\n" 12 | "PO-Revision-Date: 2016-07-15 22:30+0900\n" 13 | "Last-Translator: Choppin Antoine \n" 14 | "Language-Team: Japanese\n" 15 | "Language: ja\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=1; plural=0;\n" 20 | 21 | #: ../bin/kano-shutdown:69 22 | msgid "Do you want to Logout?" 23 | msgstr "ログアウトしますか?" 24 | 25 | #: ../bin/kano-shutdown:73 26 | msgid "Shutdown" 27 | msgstr "シャットダウン" 28 | 29 | #: ../bin/kano-shutdown:76 30 | msgid "Reboot" 31 | msgstr "再起動" 32 | 33 | #: ../bin/kano-shutdown:79 34 | msgid "Logout" 35 | msgstr "ログアウト" 36 | 37 | #: ../bin/kano-shutdown:82 38 | msgid "Cancel" 39 | msgstr "取り消し" 40 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = kano-toolset 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | 12 | .PHONY: all clean help Makefile 13 | 14 | # Put it first so that "make" without argument is like "make help". 15 | help: 16 | $(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 17 | 18 | all: 19 | # Generate the reST files from which the documentation will be built. 20 | sphinx-apidoc -o source/ ../ 21 | $(MAKE) html 22 | 23 | # Override the clean target to get rid of all generated .rst files. 24 | clean: 25 | -find source -name "*.rst" -type f \ 26 | -not -name "index.rst" -not -name "modules.rst" -not -name "readme.rst" \ 27 | -delete 28 | -$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 29 | 30 | # Catch-all target: route all unknown targets to Sphinx using the new 31 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 32 | %: Makefile 33 | $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Placeholder makefile so "debuild" can be gently persuaded to work 3 | # 4 | 5 | REPO:= kano-toolset 6 | 7 | 8 | .PHONY: clean docs kano-keys-pressed kano-launcher kano-logging kano kano-networking kano-python parson check test 9 | 10 | all: kano-keys-pressed kano-launcher kano kano-networking kano-python parson 11 | 12 | clean: 13 | cd docs && make clean 14 | 15 | docs: 16 | cd docs && make all 17 | 18 | kano-keys-pressed: 19 | cd kano-keys-pressed && make 20 | 21 | kano-launcher: kano-c-logging 22 | cd kano-launcher && make 23 | 24 | kano-c-logging: 25 | cd libs/kano-c-logging && make 26 | cd libs/kano-c-logging && make debug 27 | 28 | 29 | kano: kano-python 30 | cd libs/kano && LOCAL_BUILD=1 make 31 | cd libs/kano && LOCAL_BUILD=1 make debug 32 | 33 | kano-networking: 34 | cd libs/kano-networking && make 35 | cd libs/kano-networking && make debug 36 | 37 | kano-python: 38 | cd libs/kano-python && make 39 | cd libs/kano-python && make debug 40 | 41 | parson: 42 | cd libs/parson && make 43 | cd libs/parson && make debug 44 | 45 | # 46 | # Add test targets 47 | # 48 | include pythontest.mk 49 | check: pythontest 50 | test: check 51 | -------------------------------------------------------------------------------- /ifplugd/kano-ifplugd: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # kano-ifplugd 4 | # 5 | # Copyright (C) 2015 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # This script is called by the ifplugd deamone when network interfaces 9 | # transition from UP and DOWN (connected to disconnected). 10 | # 11 | # For ethernet devices it is detected when you plug / unplug the network cable. 12 | # On wireless devices the (de)association from the access point. 13 | # 14 | # The UP event is controlled by kano-network-hook script, because it is called 15 | # after the DHCP client has setup the correct configuration. 16 | # 17 | 18 | # file that external applications can monitor for internet availability triggers 19 | monitor_file="/var/opt/internet_monitor" 20 | interface="$1" 21 | 22 | if [ "$2" == "down" ]; then 23 | 24 | # We are testing internet connectivity now for if we are in multihomed mode (eth and wlan) 25 | is_internet 26 | if [ "$?" != "0" ]; then 27 | echo -e "internet: down\n" > $monitor_file 28 | fi 29 | 30 | # Clear associated IP addresses 31 | ip addr flush dev $interface 32 | fi 33 | 34 | exit 0 35 | -------------------------------------------------------------------------------- /media/CSS/dialog.css: -------------------------------------------------------------------------------- 1 | /** 2 | * dialog.css 3 | * 4 | * Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | * 7 | * This controls the dialog style 8 | */ 9 | 10 | dialog { 11 | background: #e7e7e7; 12 | } 13 | 14 | .white { 15 | color: #ffffff; 16 | background: #ffffff; 17 | } 18 | 19 | .grey { 20 | background: #e5e5e5; 21 | } 22 | 23 | entry { 24 | border-color: @active_border; 25 | color: @active_writing; 26 | border-style: solid; 27 | border-width: 2px; 28 | border-radius:3px; 29 | padding: 10px; 30 | font-family: Bariol; 31 | font-size: 13pt; 32 | } 33 | 34 | 35 | checkbutton, 36 | radiobutton { 37 | border-style: solid; 38 | border-color: @active_border; 39 | border-width: 2px; 40 | font-family: Bariol; 41 | font-size: 13pt; 42 | background: #ffffff; 43 | } 44 | 45 | /** 46 | * Do we want this? 47 | */ 48 | checkbutton:active, 49 | radiobutton:active { 50 | color: @kano_green; 51 | } 52 | 53 | checkbutton:hover { 54 | background: transparent; 55 | } 56 | 57 | radiobutton 58 | label { 59 | color: @active_writing; 60 | } 61 | -------------------------------------------------------------------------------- /bin/rpi-info: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # rpi-info 4 | # 5 | # Copyright (C) 2013 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # Displays a report of hardware details of a RaspberryPI unit. 9 | # Code parts taken from: http://elinux.org/RPI_vcgencmd_usage 10 | # 11 | 12 | echo "Core CPU components clock frequencies:" 13 | for src in arm core h264 isp v3d uart pwm emmc pixel vec hdmi dpi ; do 14 | echo -e "$src:\t$(vcgencmd measure_clock $src)" ; \ 15 | done 16 | 17 | echo 18 | echo "RAM components supplied voltages:" 19 | for id in core sdram_c sdram_i sdram_p ; do 20 | echo -e "$id:\t$(vcgencmd measure_volts $id)" ; 21 | done 22 | 23 | echo 24 | echo "CPU Temperature:" 25 | vcgencmd measure_temp 26 | 27 | echo 28 | echo "CPU Enabled CODECs:" 29 | for codec in H264 MPG2 WVC1 MPG4 MJPG WMV9 ; do 30 | echo -e "$codec:\t$(vcgencmd codec_enabled $codec)" ; 31 | done 32 | 33 | echo 34 | echo "System configurations settings:" 35 | vcgencmd get_config int 36 | 37 | echo 38 | echo "RAM/GPU Memory split ratio:" 39 | vcgencmd get_mem arm && vcgencmd get_mem gpu 40 | 41 | echo 42 | echo "Firmware version:" 43 | vcgencmd version 44 | -------------------------------------------------------------------------------- /kano/utils/system.py: -------------------------------------------------------------------------------- 1 | # system.py 2 | # 3 | # Copyright (C) 2014-2016 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Utilities related to the operating system 7 | 8 | import os 9 | 10 | 11 | def get_debian_version(stamp_file='/etc/debian_version'): 12 | ''' 13 | Returns Debian version number from official stamp file 14 | ''' 15 | try: 16 | with open(stamp_file) as f: 17 | osversion = f.read().strip() 18 | 19 | major, minor = osversion.split('.') 20 | return major, minor 21 | except Exception: 22 | return None, None 23 | 24 | 25 | def is_jessie(): 26 | ''' 27 | Returns True if running Debian Jessie 28 | ''' 29 | major, minor = get_debian_version() 30 | return major == '8' 31 | 32 | 33 | def is_stretch(): 34 | ''' 35 | Returns True if runing Debian Stretch 36 | ''' 37 | major, minor = get_debian_version() 38 | return major == '9' 39 | 40 | 41 | def is_systemd(): 42 | ''' 43 | returns True if we are in a systemd environment - Debian Jessie 44 | ''' 45 | try: 46 | return os.readlink('/sbin/init') == '/lib/systemd/systemd' 47 | except Exception: 48 | return False 49 | -------------------------------------------------------------------------------- /po/Makefile: -------------------------------------------------------------------------------- 1 | APPNAME=kano-toolset 2 | ORG="Kano Computing Ltd." 3 | 4 | MSGLANGS=$(notdir $(wildcard *.po)) 5 | MSGOBJS=$(addprefix ../locale/,$(MSGLANGS:.po=/LC_MESSAGES/$(APPNAME).mo)) 6 | 7 | .PHONY: clean_locales messages 8 | 9 | build: $(MSGOBJS) 10 | 11 | update: $(MSGLANGS) 12 | 13 | clean_locales: 14 | rm -rf ../locale 15 | 16 | clean: clean_locales 17 | rm -f messages.pot 18 | 19 | define generate-pypotfiles 20 | grep "env python" -rl --exclude=*.py --exclude=Makefile .. > PYPOTFILES 21 | find .. -name *.py >> PYPOTFILES 22 | endef 23 | 24 | define run-xgettext 25 | xgettext -f PYPOTFILES -L Python -kN_ --force-po -o messages.pot \ 26 | --package-name=$(APPNAME) --copyright-holder=$(ORG) 27 | endef 28 | 29 | define clean-up 30 | rm PYPOTFILES 31 | endef 32 | 33 | messages: 34 | $(generate-pypotfiles) 35 | $(run-xgettext) 36 | $(clean-up) 37 | 38 | messages.pot: 39 | $(generate-pypotfiles) 40 | $(run-xgettext) 41 | $(clean-up) 42 | 43 | %.po: messages.pot 44 | msgmerge -N -U $*.po messages.pot 45 | touch $*.po 46 | 47 | en_QQ.po: messages.pot 48 | ../../kano-i18n/dev_locale/fake_locale messages.pot en_QQ.po 49 | 50 | ../locale/%/LC_MESSAGES/$(APPNAME).mo: clean_locales 51 | mkdir -p $(dir $@) 52 | msgfmt -c -o $@ $*.po 53 | -------------------------------------------------------------------------------- /media/CSS/colours.css: -------------------------------------------------------------------------------- 1 | /** 2 | * colours.css 3 | * 4 | * Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | * 7 | * This controls the styles 8 | */ 9 | 10 | 11 | /* Define colours */ 12 | 13 | @define-color kano_green #7cc640; 14 | @define-color kano_green_darker #6DB038; 15 | @define-color kano_green_lighter #91DD54; 16 | 17 | @define-color kano_orange #ff842A; 18 | @define-color kano_orange_darker #E7680B; 19 | @define-color kano_orange_lighter #FFA563; 20 | 21 | @define-color kano_red #f37677; 22 | @define-color kano_red_darker #D55555; 23 | @define-color kano_red_lighter #FF9494; 24 | 25 | @define-color kano_blue #65C2E7; 26 | @define-color kano_blue_darker #49A6CD; 27 | @define-color kano_blue_lighter #89DCFE; 28 | 29 | @define-color kano_grey #C0C0C0; 30 | @define-color kano_grey_darker #A5A5A5; 31 | @define-color kano_grey_lighter #D6D6D6; 32 | 33 | @define-color title_color #323232; 34 | @define-color subheading_color #6e6e6e; 35 | @define-color inactive_writing #cdcdcd; 36 | @define-color inactive_border #e1e1e1; 37 | @define-color active_writing #323232; 38 | @define-color active_border #ababab; 39 | @define-color hover_color #eeeeee; 40 | @define-color appgrid_grey #e1e1e1; 41 | -------------------------------------------------------------------------------- /kano/utils/user.py: -------------------------------------------------------------------------------- 1 | # user.py 2 | # 3 | # Copyright (C) 2014-2018 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Utilities related to linux users 7 | 8 | 9 | import os 10 | import sys 11 | import pwd 12 | import getpass 13 | 14 | 15 | def get_user_getpass(): 16 | return getpass.getuser() 17 | 18 | 19 | def get_user(): 20 | return os.environ.get('LOGNAME', '') 21 | 22 | 23 | def get_user_unsudoed(): 24 | if 'SUDO_USER' in os.environ: 25 | return os.environ['SUDO_USER'] 26 | elif 'LOGNAME' in os.environ: 27 | return os.environ['LOGNAME'] 28 | else: 29 | return 'root' 30 | 31 | 32 | def get_home(): 33 | return os.path.expanduser('~') 34 | 35 | 36 | def get_home_by_username(username): 37 | return pwd.getpwnam(username).pw_dir 38 | 39 | 40 | def get_all_home_folders(root=False, skel=False): 41 | home = '/home' 42 | folders = [os.path.join(home, f) for f in os.listdir(home)] 43 | if root: 44 | folders += ['/root'] 45 | if skel: 46 | folders += ['/etc/skel'] 47 | return folders 48 | 49 | 50 | def enforce_root(msg): 51 | if os.getuid() != 0: 52 | sys.stderr.write(msg.encode('utf-8') + "\n") 53 | sys.exit(1) 54 | -------------------------------------------------------------------------------- /kano/timeout.py: -------------------------------------------------------------------------------- 1 | # The timeout decorator 2 | # 3 | # Copyright (C) 2015 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Allows to run functions with a specified timeout easily. 7 | # 8 | # Source: 9 | # http://stackoverflow.com/questions/2281850/timeout-function-if-it-takes-too-long-to-finish 10 | # 11 | # Usage: 12 | # 13 | # @timeout(10) #seconds 14 | # def your_function(a, b): 15 | # ... 16 | # 17 | # The function will be interrupted after the specified timeout 18 | 19 | from functools import wraps 20 | import errno 21 | import os 22 | import signal 23 | 24 | 25 | class TimeoutError(Exception): 26 | pass 27 | 28 | 29 | def timeout(seconds=10, error_message=os.strerror(errno.ETIME)): 30 | def decorator(func): 31 | def _handle_timeout(signum, frame): 32 | raise TimeoutError(error_message) 33 | 34 | def wrapper(*args, **kwargs): 35 | signal.signal(signal.SIGALRM, _handle_timeout) 36 | signal.alarm(seconds) 37 | try: 38 | result = func(*args, **kwargs) 39 | finally: 40 | signal.alarm(0) 41 | return result 42 | 43 | return wraps(func)(wrapper) 44 | 45 | return decorator 46 | -------------------------------------------------------------------------------- /libs/kano-networking/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2016 Kano Computing Ltd. 3 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 4 | # 5 | # Build file for libkano_networking 6 | # 7 | 8 | PLATFORM := $(shell uname) 9 | 10 | ifeq ($(PLATFORM), Linux) 11 | SONAME_FLAG=-soname 12 | SONAME=libkano_networking.so 13 | LD_TYPE=-shared 14 | else ifeq ($(PLATFORM), Darwin) 15 | SONAME_FLAG=-install_name 16 | SONAME=libkano_networking.dylib 17 | LD_TYPE=-dynamiclib 18 | endif 19 | 20 | CFLAGS+=-Wall -Wextra -g -fPIC -c 21 | LDFLAGS+=$(LD_TYPE) -Wl,$(SONAME_FLAG),$(SONAME) 22 | INCLUDES+=-I./includes 23 | OBJS=ifaces.so 24 | 25 | OUTDIR=release 26 | DEBUG_OUTDIR=debug 27 | 28 | all: ensure-dir $(SONAME) 29 | 30 | debug: CFLAGS += -DDEBUG -g 31 | debug: OUTDIR=$(DEBUG_OUTDIR) 32 | debug: $(SONAME) 33 | 34 | ensure-dir: 35 | mkdir -p $(OUTDIR) 36 | mkdir -p $(DEBUG_OUTDIR) 37 | 38 | $(SONAME): $(OBJS) 39 | $(CC) $(LDFLAGS) $(addprefix $(OUTDIR)/, $?) -o $(OUTDIR)/$@ 40 | 41 | %.so: src/%.c 42 | $(CC) $(CFLAGS) $(INCLUDES) $< -o $(OUTDIR)/$@ 43 | 44 | clean-release: 45 | @rm -f $(OUTDIR)/$(OBJS) 46 | @rm -f $(OUTDIR)/$(SONAME) 47 | 48 | clean-debug: 49 | @rm -f $(DEBUG_OUTDIR)/$(OBJS) 50 | @rm -f $(DEBUG_OUTDIR)/$(SONAME) 51 | 52 | clean: clean-release clean-debug 53 | -------------------------------------------------------------------------------- /bin/json-get: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # 4 | # A simple script to retrieve values from JSON files 5 | # 6 | # Copyright (C) 2014 Kano Computing Ltd. 7 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 8 | # 9 | 10 | import os 11 | import sys 12 | import json 13 | 14 | 15 | def usage(): 16 | msg = "Usage: {} json-file-path dot.notation.path\n".format(sys.argv[0]) 17 | sys.stderr.write(msg) 18 | sys.exit() 19 | 20 | if __name__ == "__main__": 21 | if "-h" in sys.argv: 22 | usage() 23 | 24 | if len(sys.argv) != 3 or not os.path.isfile(sys.argv[1]): 25 | usage() 26 | 27 | with open(sys.argv[1], "r") as f: 28 | json_data = json.load(f) 29 | 30 | keys = sys.argv[2].split(".") 31 | 32 | obj = json_data 33 | for key in keys: 34 | if isinstance(obj, dict) and key in obj: 35 | obj = obj[key] 36 | else: 37 | msg = "The '{}' part of '{}' not found\n".format( 38 | key, 39 | sys.argv[2] 40 | ) 41 | sys.stderr.write(msg) 42 | sys.exit() 43 | 44 | if isinstance(obj, dict): 45 | print json.dumps(obj) 46 | else: 47 | print obj 48 | -------------------------------------------------------------------------------- /libs/kano/includes/kano/kano/kano_bindings.h: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * kano_bindings.h 4 | * 5 | * Copyright (C) 2016 Kano Computing Ltd. 6 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 7 | * 8 | * Adds the interface between useful Kano Toolset functions and C++ 9 | * 10 | * kano.* 11 | * 12 | */ 13 | 14 | 15 | #ifndef __KANO_BINDINGS_H__ 16 | #define __KANO_BINDINGS_H__ 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #define KANO_NETWORK "kano.network" 24 | #define KANO_UTILS_AUDIO "kano.utils.audio" 25 | 26 | 27 | namespace kano { 28 | 29 | class network : Binding { 30 | public: 31 | network(); 32 | std::unordered_map get_network_info() const; 33 | }; 34 | 35 | namespace utils { 36 | class audio : Binding { 37 | public: 38 | audio(); 39 | bool play_sound(const std::string audio_file, const bool background=false) const; 40 | long percent_to_millibel(const int percent, const bool raspberry_mod=false) const; 41 | long get_volume() const; 42 | void set_volume(float percent) const; 43 | }; 44 | } 45 | 46 | } 47 | 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /kano/paths.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # paths.py 4 | # 5 | # Copyright (C) 2014-2019 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 7 | # 8 | 9 | ''' 10 | Discovers and exposes the absolute pathnames for common_css_dir and common_images_dir 11 | ''' 12 | 13 | import os 14 | 15 | # setting up directories 16 | dir_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 17 | 18 | # media dir 19 | media_local = os.path.join(dir_path, 'media') 20 | media_usr = '/usr/share/kano/media' 21 | 22 | if os.path.exists(media_local): 23 | common_media_dir = media_local 24 | elif os.path.exists(media_usr): 25 | common_media_dir = media_usr 26 | else: 27 | raise Exception('Neither local nor usr media dir found!') 28 | 29 | common_css_dir = os.path.join(common_media_dir, 'CSS') 30 | common_images_dir = os.path.join(common_media_dir, 'images') 31 | 32 | DNS_FILE = '/etc/resolvconf/resolv.conf.d/base' 33 | DNS_INTERFACES_FILE = '/etc/resolvconf/interface-order' 34 | DNS_INTERFACES_BACKUP_FILE = '/etc/resolvconf/interface-order.backup' 35 | SUPPLICANT_LOGFILE = '/var/log/kano_wpa.log' 36 | SUPPLICANT_CONFIG = '/etc/wpa_supplicant/wpa_supplicant.conf' 37 | INTERNET_UP_FILE = '/var/run/internet_monitor' 38 | KANO_CONNECT_PIDFILE = '/var/run/kano-connect.pid' 39 | -------------------------------------------------------------------------------- /tests/fixtures/cpuinfo/rpi_2_cpuinfo.dump: -------------------------------------------------------------------------------- 1 | processor : 0 2 | model name : ARMv7 Processor rev 5 (v7l) 3 | BogoMIPS : 38.40 4 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm 5 | CPU implementer : 0x41 6 | CPU architecture: 7 7 | CPU variant : 0x0 8 | CPU part : 0xc07 9 | CPU revision : 5 10 | 11 | processor : 1 12 | model name : ARMv7 Processor rev 5 (v7l) 13 | BogoMIPS : 38.40 14 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm 15 | CPU implementer : 0x41 16 | CPU architecture: 7 17 | CPU variant : 0x0 18 | CPU part : 0xc07 19 | CPU revision : 5 20 | 21 | processor : 2 22 | model name : ARMv7 Processor rev 5 (v7l) 23 | BogoMIPS : 38.40 24 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm 25 | CPU implementer : 0x41 26 | CPU architecture: 7 27 | CPU variant : 0x0 28 | CPU part : 0xc07 29 | CPU revision : 5 30 | 31 | processor : 3 32 | model name : ARMv7 Processor rev 5 (v7l) 33 | BogoMIPS : 38.40 34 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm 35 | CPU implementer : 0x41 36 | CPU architecture: 7 37 | CPU variant : 0x0 38 | CPU part : 0xc07 39 | CPU revision : 5 40 | 41 | Hardware : BCM2709 42 | Revision : 1a01041 43 | Serial : 00000000023913de 44 | -------------------------------------------------------------------------------- /libs/kano-python/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2016 Kano Computing Ltd. 3 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 4 | # 5 | # Build file for libkano_python 6 | # 7 | 8 | PLATFORM := $(shell uname) 9 | 10 | ifeq ($(PLATFORM), Linux) 11 | SONAME_FLAG=-soname 12 | SONAME=libkano_python.so 13 | LD_TYPE=-shared 14 | else ifeq ($(PLATFORM), Darwin) 15 | SONAME_FLAG=-install_name 16 | SONAME=libkano_python.dylib 17 | LD_TYPE=-dynamiclib 18 | endif 19 | 20 | CXXFLAGS+=-Wall -Wextra -O3 -fPIC -c -std=c++11 `python-config --cflags` 21 | LDFLAGS+=$(LD_TYPE) -Wl,$(SONAME_FLAG),$(SONAME) `python-config --ldflags` 22 | INCLUDES+=-I./includes 23 | OBJS=python_helpers.so 24 | 25 | OUTDIR=release 26 | DEBUG_OUTDIR=debug 27 | 28 | all: ensure-dir $(SONAME) 29 | 30 | debug: CXXFLAGS += -DDEBUG -g 31 | debug: OUTDIR=$(DEBUG_OUTDIR) 32 | debug: $(SONAME) 33 | 34 | ensure-dir: 35 | mkdir -p $(OUTDIR) 36 | mkdir -p $(DEBUG_OUTDIR) 37 | 38 | $(SONAME): $(OBJS) 39 | $(CXX) $(LDFLAGS) $(addprefix $(OUTDIR)/, $?) -o $(OUTDIR)/$@ 40 | 41 | %.so: src/%.cpp 42 | $(CXX) $(CXXFLAGS) $(INCLUDES) $< -o $(OUTDIR)/$@ 43 | 44 | clean-release: 45 | @rm -f $(OUTDIR)/$(OBJS) 46 | @rm -f $(OUTDIR)/$(SONAME) 47 | 48 | clean-debug: 49 | @rm -f $(DEBUG_OUTDIR)/$(OBJS) 50 | @rm -f $(DEBUG_OUTDIR)/$(SONAME) 51 | 52 | clean: clean-release clean-debug 53 | -------------------------------------------------------------------------------- /kano/utils/disk.py: -------------------------------------------------------------------------------- 1 | # disk.py 2 | # 3 | # Copyright (C) 2014-2016 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Utilities relating to disk manangement 7 | 8 | 9 | from kano.utils.shell import run_cmd 10 | 11 | 12 | def get_free_space(path="/"): 13 | """ 14 | Returns the amount of free space in certain location in MB 15 | 16 | :param path: The location to measure the free space at. 17 | :type path: str 18 | 19 | :return: Number of free megabytes. 20 | :rtype: int 21 | """ 22 | 23 | out, dummy_err, dummy_rv = run_cmd("df {}".format(path)) 24 | 25 | dummy_device, dummy_size, dummy_used, free, dummy_percent, dummy_mp = \ 26 | out.split('\n')[1].split() 27 | 28 | return int(free) / 1024 29 | 30 | 31 | def get_partition_info(): 32 | device = '/dev/mmcblk0' 33 | 34 | try: 35 | cmd = 'lsblk -n -b {} -o SIZE'.format(device) 36 | stdout, dummy_stderr, returncode = run_cmd(cmd) 37 | 38 | if returncode != 0: 39 | from kano.logging import logger 40 | logger.warning("error running lsblk") 41 | 42 | return [] 43 | 44 | lines = stdout.strip().split('\n') 45 | sizes = map(int, lines) 46 | 47 | return sizes 48 | except Exception: 49 | return [] 50 | -------------------------------------------------------------------------------- /libs/parson/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile 3 | # Copyright (C) 2016 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Build file for libparson 7 | # Builds: lib/* 8 | # Copyright (C) 2012-2016 Krzysztof Gabis (kgabis@gmail.com) 9 | # License: MIT 10 | # 11 | 12 | PLATFORM := $(shell uname) 13 | 14 | ifeq ($(PLATFORM), Linux) 15 | SONAME_FLAG=-soname 16 | SONAME=libparson.so 17 | LD_TYPE=-shared 18 | else ifeq ($(PLATFORM), Darwin) 19 | SONAME_FLAG=-install_name 20 | SONAME=libparson.dylib 21 | LD_TYPE=-dynamiclib 22 | endif 23 | 24 | CFLAGS+=-Wall -Wextra -g -fPIC -c 25 | LDFLAGS+=$(LD_TYPE) -Wl,$(SONAME_FLAG),$(SONAME) 26 | INCLUDES+=-I./lib 27 | OBJS=parson.so 28 | 29 | OUTDIR=release 30 | DEBUG_OUTDIR=debug 31 | 32 | all: ensure-dir $(SONAME) 33 | 34 | debug: CFLAGS += -DDEBUG -g 35 | debug: OUTDIR=$(DEBUG_OUTDIR) 36 | debug: $(SONAME) 37 | 38 | ensure-dir: 39 | mkdir -p $(OUTDIR) 40 | mkdir -p $(DEBUG_OUTDIR) 41 | 42 | $(SONAME): $(OBJS) 43 | $(CC) $(LDFLAGS) $(addprefix $(OUTDIR)/, $?) -o $(OUTDIR)/$@ 44 | 45 | %.so: lib/%.c 46 | $(CC) $(CFLAGS) $(INCLUDES) $< -o $(OUTDIR)/$@ 47 | 48 | clean-release: 49 | @rm -f $(OUTDIR)/$(OBJS) 50 | @rm -f $(OUTDIR)/$(SONAME) 51 | 52 | clean-debug: 53 | @rm -f $(DEBUG_OUTDIR)/$(OBJS) 54 | @rm -f $(DEBUG_OUTDIR)/$(SONAME) 55 | 56 | clean: clean-release clean-debug 57 | -------------------------------------------------------------------------------- /libs/kano-c-logging/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile 3 | # Copyright (C) 2018 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Build file for libkano_c_logging 7 | # Builds: lib/* 8 | # Copyright (C) 2012-2016 Krzysztof Gabis (kgabis@gmail.com) 9 | # License: MIT 10 | # 11 | 12 | PLATFORM := $(shell uname) 13 | 14 | ifeq ($(PLATFORM), Linux) 15 | SONAME_FLAG=-soname 16 | SONAME=libkano_c_logging.so 17 | LD_TYPE=-shared 18 | else ifeq ($(PLATFORM), Darwin) 19 | SONAME_FLAG=-install_name 20 | SONAME=libkano_c_logging.dylib 21 | LD_TYPE=-dynamiclib 22 | endif 23 | 24 | CFLAGS+=-Wall -Wextra -g -fPIC -c 25 | LDFLAGS+=$(LD_TYPE) -Wl,$(SONAME_FLAG),$(SONAME) 26 | INCLUDES+=-I./lib 27 | OBJS=kano_c_logging.so 28 | 29 | OUTDIR=release 30 | DEBUG_OUTDIR=debug 31 | 32 | all: ensure-dir $(SONAME) 33 | 34 | debug: CFLAGS += -DDEBUG -g 35 | debug: OUTDIR=$(DEBUG_OUTDIR) 36 | debug: $(SONAME) 37 | 38 | ensure-dir: 39 | mkdir -p $(OUTDIR) 40 | mkdir -p $(DEBUG_OUTDIR) 41 | 42 | $(SONAME): $(OBJS) 43 | $(CC) $(LDFLAGS) $(addprefix $(OUTDIR)/, $?) -o $(OUTDIR)/$@ 44 | 45 | %.so: src/%.c 46 | $(CC) $(CFLAGS) $(INCLUDES) $< -o $(OUTDIR)/$@ 47 | 48 | clean-release: 49 | @rm -f $(OUTDIR)/$(OBJS) 50 | @rm -f $(OUTDIR)/$(SONAME) 51 | 52 | clean-debug: 53 | @rm -f $(DEBUG_OUTDIR)/$(OBJS) 54 | @rm -f $(DEBUG_OUTDIR)/$(SONAME) 55 | 56 | clean: clean-release clean-debug 57 | -------------------------------------------------------------------------------- /bin/kano-led: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # kano-led 4 | # 5 | # Copyright (C) 2015 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # Blink the RaspberryPI board LED at a constant rate 9 | # This tool requires the "leds_gpio" kernel module 10 | # 11 | 12 | import os, time, sys 13 | 14 | def led_blinking(ticks_second=10, duration_secs=5): 15 | 16 | for duration in range(0, duration_secs): 17 | for ticks in range(0, ticks_second): 18 | os.system ('echo 1 >/sys/class/leds/led0/brightness') 19 | time.sleep (0.035) 20 | os.system ('echo 0 >/sys/class/leds/led0/brightness') 21 | time.sleep (0.035) 22 | 23 | if __name__ == '__main__': 24 | 25 | modloaded=False 26 | 27 | # Load the kernel module that allows us to blink the LED, if not there 28 | if not os.path.exists('/sys/class/leds/led0/trigger'): 29 | os.system('modprobe leds_gpio') 30 | modloaded=True 31 | 32 | # acquire the board LED for application use 33 | rc = os.system('echo none >/sys/class/leds/led0/trigger') 34 | if not rc == 0: 35 | sys.exit(1) 36 | 37 | led_blinking() 38 | 39 | # restore LED for system disk IO monitoring and unload module 40 | os.system('echo mmc0 >/sys/class/leds/led0/trigger') 41 | if modloaded: 42 | os.system('rmmod leds_gpio') 43 | 44 | sys.exit(0) 45 | -------------------------------------------------------------------------------- /kano/utils/lua/lua_to_json.lua: -------------------------------------------------------------------------------- 1 | -- lua_to_json.lua 2 | -- 3 | -- Copyright (C) 2016 Kano Computing Ltd. 4 | -- License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | -- 6 | -- Read a lua module and output it as json 7 | 8 | local ToJson = require "to_json" 9 | 10 | if #arg ~= 2 then 11 | io.stderr:write("Usage: lua lua_to_json.lua data.lua data.json\n") 12 | os.exit(1) 13 | end 14 | 15 | local json_nil = {} 16 | local function get_lua_data(filename) 17 | 18 | local c = loadfile(filename) 19 | local success, result = pcall(c) 20 | if success then 21 | success, result = pcall(result, json_nil) 22 | end 23 | return result, success 24 | end 25 | local result, success = get_lua_data(arg[1]) 26 | if not success then 27 | io.stderr:write("Error loading file!\n") 28 | os.exit(1) 29 | else 30 | local json, status = ToJson.dumps(result, json_nil) 31 | if not status then 32 | io.stderr:write("error converting to json\n") 33 | os.exit(1) 34 | end 35 | local jf, estr = io.open(arg[2], "w") 36 | if jf == nil then 37 | io.stderr:write("Error outputting to file".. estr.."\n") 38 | os.exit(1) 39 | end 40 | local enil 41 | enil, estr = jf:write(json) 42 | if enil == nil then 43 | io.stderr:write("Error outputting to file".. estr.."\n") 44 | os.exit(1) 45 | end 46 | jf:close() 47 | end 48 | 49 | -------------------------------------------------------------------------------- /libs/kano/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2016 Kano Computing Ltd. 3 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 4 | # 5 | # Build file for libkano 6 | # 7 | 8 | PLATFORM := $(shell uname) 9 | 10 | ifeq ($(PLATFORM), Linux) 11 | SONAME_FLAG=-soname 12 | SONAME=libkano.so 13 | LD_TYPE=-shared 14 | else ifeq ($(PLATFORM), Darwin) 15 | SONAME_FLAG=-install_name 16 | SONAME=libkano.dylib 17 | LD_TYPE=-dynamiclib 18 | endif 19 | 20 | ifdef LOCAL_BUILD 21 | CXXFLAGS+=-I../kano-python/includes 22 | LDFLAGS+=-L../kano-python/release 23 | endif 24 | 25 | CXXFLAGS+=-Wall -Wextra -O3 -fPIC -c -std=c++11 `python-config --cflags` 26 | LDFLAGS+=$(LD_TYPE) -Wl,$(SONAME_FLAG),$(SONAME) `python-config --ldflags` -lkano_python 27 | INCLUDES+=-I./includes 28 | OBJS=kano_network.so kano_utils_audio.so 29 | 30 | OUTDIR=release 31 | DEBUG_OUTDIR=debug 32 | 33 | all: ensure-dir $(SONAME) 34 | 35 | debug: CXXFLAGS += -DDEBUG -g 36 | debug: OUTDIR=$(DEBUG_OUTDIR) 37 | debug: $(SONAME) 38 | 39 | ensure-dir: 40 | mkdir -p $(OUTDIR) 41 | mkdir -p $(DEBUG_OUTDIR) 42 | 43 | $(SONAME): $(OBJS) 44 | $(CXX) $(LDFLAGS) $(addprefix $(OUTDIR)/, $?) -o $(OUTDIR)/$@ 45 | 46 | %.so: src/%.cpp 47 | $(CXX) $(CXXFLAGS) $(INCLUDES) $< -o $(OUTDIR)/$@ 48 | 49 | clean-release: 50 | @rm -f $(OUTDIR)/$(OBJS) 51 | @rm -f $(OUTDIR)/$(SONAME) 52 | 53 | clean-debug: 54 | @rm -f $(DEBUG_OUTDIR)/$(OBJS) 55 | @rm -f $(DEBUG_OUTDIR)/$(SONAME) 56 | 57 | clean: clean-release clean-debug 58 | 59 | -------------------------------------------------------------------------------- /po/es_AR.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Kano Computing Ltd. 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: kano-toolset\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2017-02-08 12:37+0000\n" 11 | "PO-Revision-Date: 2017-02-12 14:34+0000\n" 12 | "Last-Translator: Seán \n" 13 | "Language-Team: LANGUAGE \n" 14 | "Language: es_AR\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | "X-Generator: Pootle 2.7\n" 20 | "X-POOTLE-MTIME: 1486910089.168233\n" 21 | 22 | # Or desconectarte? 23 | #: ../bin/kano-shutdown:70 24 | msgid "Do you want to Logout?" 25 | msgstr "¿Quieres salir?" 26 | 27 | #: ../bin/kano-shutdown:74 28 | msgid "Shutdown" 29 | msgstr "Apagar" 30 | 31 | #: ../bin/kano-shutdown:77 32 | msgid "Reboot" 33 | msgstr "Reiniciar" 34 | 35 | #: ../bin/kano-shutdown:80 36 | msgid "Logout" 37 | msgstr "Cerrar Sesión" 38 | 39 | #: ../bin/kano-shutdown:83 40 | msgid "Cancel" 41 | msgstr "Cancelar" 42 | 43 | #: ../kano/gtk3/kano_dialog.py:81 ../kano/gtk3/kano_dialog.py:189 44 | #: ../kano/gtk3/kano_dialog.py:223 ../kano/gtk3/kano_dialog.py:227 45 | msgid "OK" 46 | msgstr "ACEPTAR" 47 | 48 | #: ../kano/gtk3/kano_dialog.py:191 ../kano/gtk3/kano_dialog.py:194 49 | msgid "CANCEL" 50 | msgstr "CANCELAR" 51 | -------------------------------------------------------------------------------- /kano-keys-pressed/hid.h: -------------------------------------------------------------------------------- 1 | // 2 | // hid.h - Human Input Device definitions 3 | // 4 | // Copyright (C) 2014, 2015 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | 8 | #include 9 | 10 | // Defaut generous buffer size to read from devices 11 | #define BUFF_SIZE 128 12 | #define GRACE_START_TIME 300 // milliseconds to wait for type-ahead input events 13 | 14 | // Devices to which we are listening for user events 15 | #define chkbd0 "/dev/input/event0" 16 | #define chkbd1 "/dev/input/event1" 17 | #define chkbd2 "/dev/input/event2" 18 | #define chmouse0 "/dev/input/mouse0" 19 | #define chmouse1 "/dev/input/mouse1" 20 | #define chmice "/dev/input/mice" 21 | 22 | // Structure to hold the devices to listen to 23 | typedef struct _HID_STRUCT 24 | { 25 | // File-like handles to user input devices 26 | int fdkbd0, fdkbd1, fdkbd2, fdmouse0, fdmouse1, fdmice; 27 | 28 | } HID_STRUCT; 29 | 30 | typedef HID_STRUCT *HID_HANDLE; 31 | 32 | // APIs to init, terminate, and get events from HID devices 33 | 34 | 35 | #ifdef __cplusplus 36 | extern "C" 37 | #endif 38 | HID_HANDLE hid_init(int flags); 39 | 40 | #ifdef __cplusplus 41 | extern "C" 42 | #endif 43 | bool hid_is_user_idle (HID_HANDLE hhid, int timeout); 44 | 45 | #ifdef __cplusplus 46 | extern "C" 47 | #endif 48 | void hid_terminate(HID_HANDLE hid); 49 | 50 | #ifdef __cplusplus 51 | extern "C" 52 | bool is_hotkey_pressed(HID_HANDLE hid, bool verbose); 53 | #endif 54 | -------------------------------------------------------------------------------- /bin/kano-set-system-date: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # kano-set-system-date 4 | # 5 | # Copyright (C) 2017 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # Sets the Kit's timezone and system date from a remote time server. 9 | # This script is executed from the dhcpcd-hooks when a DHCP lease is obtained 10 | # 11 | 12 | . /usr/share/kano-toolset/logging.sh 13 | 14 | # find the local timezone based on the IP address, then set local system 15 | if [ ! -L /etc/localtime ]; then 16 | tzupdated=`/usr/bin/kano-tzupdate 2>&1` 17 | if [ "$?" -eq 0 ]; then 18 | logger_info "tzupdate SUCCESS: $tzupdated" 19 | logger_info "tzupdate result: $(file /etc/localtime)" 20 | else 21 | IFS=$'\n' 22 | for line in ${tzupdated}; do 23 | logger_error "tzupdate FAIL: $line" 24 | done 25 | fi 26 | else 27 | logger_info "skipping tzupdate" 28 | fi 29 | 30 | 31 | # Set the system time from a network server, retry if it fails. 32 | for dateretries in 1 2 3 4 5 33 | do 34 | time_server=`cat /etc/timeserver.conf` 35 | logger_info "rdate using ntp server: $time_server" 36 | dated=`/usr/bin/rdate -4 -u -v -c -n $time_server 2>&1` 37 | if [ "$?" -eq 0 ]; then 38 | logger_info "rdate SUCCESS: $dated" 39 | break 40 | else 41 | IFS=$'\n' 42 | for line in ${dated}; do 43 | logger_error "rdate FAIL: $line" 44 | done 45 | sleep 1 46 | fi 47 | done 48 | -------------------------------------------------------------------------------- /kano/gtk3/icons.py: -------------------------------------------------------------------------------- 1 | # 2 | # icons.py 3 | # 4 | # Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | # 7 | # Creates pixbufs that we can use to make images from. Uses a strip of icons, 8 | # each 24px by 24px. 9 | # 10 | 11 | from gi import require_version 12 | require_version('Gtk', '3.0') 13 | 14 | from gi.repository import Gtk, GdkPixbuf 15 | from kano.paths import common_images_dir 16 | import os 17 | 18 | # To make an image using the pixbuf icon, use the command below: 19 | # image.set_from_pixbuf(self.pixbuf) 20 | 21 | 22 | def set_from_name(name): 23 | icon_number = 0 24 | if name == "green_arrow": 25 | icon_number = 0 26 | elif name == "pale_right_arrow": 27 | icon_number = 1 28 | elif name == "dark_left_arrow": 29 | icon_number = 2 30 | elif name == "dark_right_arrow": 31 | icon_number = 3 32 | elif name == "pale_left_arrow": 33 | icon_number = 4 34 | elif name == "tick": 35 | icon_number = 5 36 | elif name == "cross": 37 | icon_number = 6 38 | elif name == "dropdown_arrow": 39 | icon_number = 7 40 | # Create main window 41 | filename = os.path.join(common_images_dir, "icons.png") 42 | pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filename, 192, 24) 43 | subpixbuf = pixbuf.new_subpixbuf(24 * icon_number, 0, 24, 24).add_alpha(True, 255, 255, 255) 44 | image = Gtk.Image() 45 | image.set_from_pixbuf(subpixbuf) 46 | return image 47 | -------------------------------------------------------------------------------- /tests/fixtures/cpuinfo/rpi_3_cpuinfo.dump: -------------------------------------------------------------------------------- 1 | processor : 0 2 | model name : ARMv7 Processor rev 4 (v7l) 3 | BogoMIPS : 76.80 4 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 5 | CPU implementer : 0x41 6 | CPU architecture: 7 7 | CPU variant : 0x0 8 | CPU part : 0xd03 9 | CPU revision : 4 10 | 11 | processor : 1 12 | model name : ARMv7 Processor rev 4 (v7l) 13 | BogoMIPS : 76.80 14 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 15 | CPU implementer : 0x41 16 | CPU architecture: 7 17 | CPU variant : 0x0 18 | CPU part : 0xd03 19 | CPU revision : 4 20 | 21 | processor : 2 22 | model name : ARMv7 Processor rev 4 (v7l) 23 | BogoMIPS : 76.80 24 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 25 | CPU implementer : 0x41 26 | CPU architecture: 7 27 | CPU variant : 0x0 28 | CPU part : 0xd03 29 | CPU revision : 4 30 | 31 | processor : 3 32 | model name : ARMv7 Processor rev 4 (v7l) 33 | BogoMIPS : 76.80 34 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 35 | CPU implementer : 0x41 36 | CPU architecture: 7 37 | CPU variant : 0x0 38 | CPU part : 0xd03 39 | CPU revision : 4 40 | 41 | Hardware : BCM2709 42 | Revision : a22082 43 | Serial : 000000008692c11f 44 | -------------------------------------------------------------------------------- /tests/fixtures/cpuinfo/incorrect_cpuinfo.dump: -------------------------------------------------------------------------------- 1 | processor : 0 2 | model name : ARMv7 Processor rev 4 (v7l) 3 | BogoMIPS : 76.80 4 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 5 | CPU implementer : 0x41 6 | CPU architecture: 7 7 | CPU variant : 0x0 8 | CPU part : 0xd03 9 | CPU revision : 4 10 | 11 | processor : 1 12 | model name : ARMv7 Processor rev 4 (v7l) 13 | BogoMIPS : 76.80 14 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 15 | CPU implementer : 0x41 16 | CPU architecture: 7 17 | CPU variant : 0x0 18 | CPU part : 0xd03 19 | CPU revision : 4 20 | 21 | processor : 2 22 | model name : ARMv7 Processor rev 4 (v7l) 23 | BogoMIPS : 76.80 24 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 25 | CPU implementer : 0x41 26 | CPU architecture: 7 27 | CPU variant : 0x0 28 | CPU part : 0xd03 29 | CPU revision : 4 30 | 31 | processor : 3 32 | model name : ARMv7 Processor rev 4 (v7l) 33 | BogoMIPS : 76.80 34 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 35 | CPU implementer : 0x41 36 | CPU architecture: 7 37 | CPU variant : 0x0 38 | CPU part : 0xd03 39 | CPU revision : 4 40 | 41 | Hardware : BCM2709 42 | Revision : invalid_revision 43 | Serial : invalid_serial 44 | -------------------------------------------------------------------------------- /kano/gtk3/scrolled_window.py: -------------------------------------------------------------------------------- 1 | # 2 | # kano_scrolled_window.py 3 | # 4 | # Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | # 7 | # Create a scrolled window with custom scrollbars 8 | # 9 | 10 | from gi import require_version 11 | require_version('Gtk', '3.0') 12 | 13 | from gi.repository import Gtk 14 | from kano.paths import common_css_dir 15 | from kano.gtk3.apply_styles import apply_styling_to_widget, apply_styling_to_screen, apply_colours_to_widget 16 | import os 17 | 18 | 19 | class ScrolledWindow(Gtk.ScrolledWindow): 20 | NORMAL_CSS_PATH = os.path.join(common_css_dir, 'scrollbar.css') 21 | WIDE_CSS_PATH = os.path.join(common_css_dir, 'scrollbar-wide.css') 22 | 23 | def __init__(self, hexpand=None, vexpand=None, wide_scrollbar=False): 24 | Gtk.ScrolledWindow.__init__(self, hexpand=hexpand, vexpand=vexpand) 25 | self.set_overlay_scrolling(False) 26 | 27 | @staticmethod 28 | def apply_styling_to_screen(wide=False): 29 | if wide: 30 | apply_styling_to_screen(ScrolledWindow.WIDE_CSS_PATH) 31 | else: 32 | apply_styling_to_screen(ScrolledWindow.NORMAL_CSS_PATH) 33 | 34 | def apply_styling_to_widget(self, wide=False): 35 | for bar in [self.get_vscrollbar(), self.get_hscrollbar()]: 36 | apply_colours_to_widget(bar) 37 | if wide: 38 | apply_styling_to_widget(bar, self.WIDE_CSS_PATH) 39 | else: 40 | apply_styling_to_widget(bar, self.NORMAL_CSS_PATH) 41 | -------------------------------------------------------------------------------- /kano/utils/http_requests.py: -------------------------------------------------------------------------------- 1 | # http_requests.py 2 | # 3 | # Copyright (C) 2014-2016 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Utilities relating to http requests 7 | 8 | 9 | try: 10 | from kano_settings.system.proxy import get_requests_proxies 11 | proxies = get_requests_proxies() 12 | except Exception: 13 | proxies = None 14 | 15 | 16 | def download_url(url, file_path): 17 | import requests 18 | try: 19 | with open(file_path, 'wb') as handle: 20 | request = requests.get(url, stream=True, proxies=proxies) 21 | if not request.ok: 22 | return False, request.text 23 | for block in request.iter_content(1024): 24 | if not block: 25 | break 26 | handle.write(block) 27 | return True, None 28 | except Exception as e: 29 | return False, str(e) 30 | 31 | 32 | def requests_get_json(url, params=None): 33 | import requests 34 | try: 35 | r = requests.get(url, params=params, proxies=proxies) 36 | if r.ok: 37 | return r.ok, None, r.json() 38 | else: 39 | return r.ok, r.text, None 40 | except Exception as e: 41 | return False, str(e), None 42 | 43 | 44 | def debug_requests(): 45 | import httplib 46 | 47 | old_send = httplib.HTTPConnection.send 48 | 49 | def new_send(self, data): 50 | print data 51 | return old_send(self, data) 52 | httplib.HTTPConnection.send = new_send 53 | -------------------------------------------------------------------------------- /bin/kano-progress: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # kano-progress 4 | # 5 | # Copyright (C) 2014 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # usage: kano-progress title=title description=desc buttons=abc:orange:1,bcd:red:2 9 | # 10 | 11 | import os 12 | import sys 13 | import time 14 | 15 | 16 | if __name__ == '__main__' and __package__ is None: 17 | dir_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 18 | if dir_path != '/usr': 19 | sys.path.insert(1, dir_path) 20 | 21 | from kano.gtk3.kano_progress import KanoProgressBar 22 | 23 | 24 | def parse_items(args): 25 | global radio_returnvalue 26 | 27 | pulse = False 28 | title = "Loading" 29 | rate = 0.01 30 | timeout = 2 31 | 32 | for arg in args: 33 | split = arg.split('=') 34 | 35 | if split[0] == "pulse": 36 | pulse = True 37 | if split[0] == "title": 38 | title = split[1] 39 | if split[0] == "rate": 40 | rate = float(split[1]) 41 | if split[0] == "timeout": 42 | timeout = int(split[1]) 43 | 44 | return pulse, title, rate, timeout 45 | 46 | 47 | def main(): 48 | text = sys.argv[1:] 49 | pulse, title, rate, timeout = parse_items(text) 50 | pbar = KanoProgressBar(pulse, title, rate) 51 | pbar.show_all() 52 | # For now, only use timeout if the progress bar pulses 53 | if pulse: 54 | pbar.set_work_function(time.sleep, timeout) 55 | pbar.run() 56 | 57 | 58 | if __name__ == '__main__': 59 | main() 60 | -------------------------------------------------------------------------------- /bin/kano-dialog: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # kano-dialog 4 | # 5 | # Copyright (C) 2014 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # 9 | # usage: kano-dialog title=title description=desc buttons=abc:orange:1,bcd:red:2 10 | # 11 | 12 | import os 13 | import sys 14 | 15 | 16 | if __name__ == '__main__' and __package__ is None: 17 | dir_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 18 | if dir_path != '/usr': 19 | sys.path.insert(1, dir_path) 20 | 21 | from kano.gtk3.kano_dialog import parse_items, KanoDialog 22 | from kano.utils import is_number 23 | 24 | 25 | def main(): 26 | text = sys.argv[1:] 27 | title, description, buttons, custom_widget, entry_bool, list_bool, scrolled_text, global_style, hide_from_taskbar = parse_items(text) 28 | kdialog = KanoDialog( 29 | title_text=title, 30 | description_text=description, 31 | button_dict=buttons, 32 | widget=custom_widget, 33 | has_entry=entry_bool, 34 | has_list=list_bool, 35 | scrolled_text=scrolled_text, 36 | global_style=global_style, 37 | hide_from_taskbar=hide_from_taskbar) 38 | response = kdialog.run() 39 | 40 | # Printing out response means we can read the return value in bash via 41 | # var = $(kano-dialog button=ok,return_value:ok) 42 | # echo $var 43 | print response 44 | 45 | if is_number(response): 46 | sys.exit(response) 47 | 48 | if __name__ == '__main__': 49 | if len(sys.argv) == 1: 50 | sys.exit('Nothing to display!') 51 | main() 52 | -------------------------------------------------------------------------------- /kano/pydebug.py: -------------------------------------------------------------------------------- 1 | # kano.logging 2 | # 3 | # Copyright (C) 2015-2019 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 5 | 6 | 7 | # Start the python debugger 8 | def start_term(a, b): 9 | import pdb 10 | pdb.set_trace() 11 | 12 | 13 | # start the python debugger for remote connection on port 444 14 | # needs pip install remote_pdb 15 | def start_remote(a, b): 16 | from remote_pdb import RemotePdb 17 | RemotePdb('0.0.0.0', 4444).set_trace() 18 | 19 | 20 | # start the debugger on a tty 21 | 22 | # for some reason this needs you to do chvt 8 23 | # before you can switch to vt 8 with the keyboard 24 | def start_tty(a, b, tty="/dev/tty8"): 25 | from pdb import Pdb 26 | io = open(tty, "r+b") 27 | Pdb(stdin=io, stdout=io).set_trace() 28 | 29 | 30 | # Start pdb in an rxvt on X 31 | def start_x(a, b): 32 | from pdb import Pdb 33 | import os 34 | from pty import openpty 35 | 36 | (master, slave) = openpty() 37 | cmd = "rxvt -pty-fd {} &".format(master) 38 | os.system(cmd) 39 | io = os.fdopen(slave, "r+b") 40 | Pdb(stdin=io, stdout=io).set_trace() 41 | 42 | 43 | # set a handler on SIGUSR1. NB although all the above shoudln't really 44 | # work as signal handlers, because they call system calls which you are 45 | # not supposed to use in a handler, these are okay because in fact 46 | # python is handling the signal in C then interpreting these 'handlers' 47 | # outside the handler. 48 | 49 | # To use: pydebug.patch_handler(pydebug.start_x) 50 | def patch_handler(handler): 51 | import signal 52 | signal.signal(signal.SIGUSR1, handler) 53 | -------------------------------------------------------------------------------- /kano/utils/processes.py: -------------------------------------------------------------------------------- 1 | # processes.py 2 | # 3 | # Copyright (C) 2014-2016 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Utilities related to process manangement 7 | 8 | 9 | import os 10 | import sys 11 | import signal 12 | 13 | from kano.utils.shell import run_cmd 14 | 15 | 16 | def kill_child_processes(parent_pid): 17 | cmd = "ps -o pid --ppid {} --noheaders".format(parent_pid) 18 | o, _, _ = run_cmd(cmd) 19 | processes = [int(p) for p in o.splitlines()] 20 | for process in processes: 21 | os.kill(process, signal.SIGTERM) 22 | 23 | 24 | def is_running(program): 25 | ''' 26 | Returns True if at least one instance of program name is running. 27 | program will search through the command line, so asking for 28 | "connect" will return True for the process 29 | "wpa_supplicant -c/etc/connect.conf" 30 | ''' 31 | # Search using a regex, to exclude itself (pgrep) from the list 32 | cmd = "pgrep -fc '[{}]{}'".format(program[0], program[1:]) 33 | running = 0 34 | try: 35 | result = os.popen(cmd) 36 | running = int(result.read().strip()) 37 | except Exception: 38 | pass 39 | 40 | return running > 0 41 | 42 | 43 | def get_program_name(): 44 | return os.path.basename(sys.argv[0]) 45 | 46 | 47 | def pkill(clues): 48 | if type(clues) == str: 49 | clues = [clues] 50 | 51 | psx, _, _ = run_cmd("ps x") 52 | for line in psx.split("\n"): 53 | for clue in clues: 54 | if clue in line: 55 | pid = line.split()[0] 56 | run_cmd("kill {}".format(pid)) 57 | -------------------------------------------------------------------------------- /uinput/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Robert Thomson and individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Robert Thomson, nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /bin/kano-sentry-startup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # kano-sentry-startup 4 | # 5 | # Copyright (C) 2017 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # Starts the sentry server depending on Parental control settings. 9 | # This script is executed from the dhcpcd-hook scripts when a DHCP lease is obtained. 10 | # 11 | 12 | . /usr/share/kano-toolset/logging.sh 13 | 14 | SERVER_CONFIG='/usr/bin/start-sentry-server' 15 | 16 | # First check that server config file exists. 17 | if [ -x $SERVER_CONFIG ]; then 18 | 19 | # Run the script that switches on the parental control if the config is set 20 | # Will return 1 if it doesn't launch the server 21 | logger_info "The sentry server script $SERVER_CONFIG exists" 22 | $SERVER_CONFIG 23 | server_rv=$? 24 | logger_info "Run script $SERVER_CONFIG, return value is $server_rv" 25 | parental_control_on=$server_rv 26 | else 27 | # The ultimate parental control was not turned on 28 | logger_info "The sentry server script $SERVER_CONFIG does not exist" 29 | parental_control_on=1 30 | fi 31 | 32 | # If the parental control was not switched on, go through the other options 33 | if [ $parental_control_on -eq 1 ]; then 34 | logger_info "Ultimate parental control is NOT switched on" 35 | if [ -x /sbin/resolvconf ]; then 36 | echo -n "$R" | resolvconf -a "${interface}.udhcpc" 37 | else 38 | echo -n "$R" > "$RESOLV_CONF" 39 | 40 | # Add a fallback DNS server to the end of the list, including a new line 41 | echo "nameserver 8.8.8.8" >> "$RESOLV_CONF" 42 | fi 43 | else 44 | logger_info "Ultimate parental control IS switched on" 45 | fi 46 | -------------------------------------------------------------------------------- /media/CSS/kano_combobox.css: -------------------------------------------------------------------------------- 1 | /** 2 | * kano_combobox.css 3 | * 4 | * Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | * 7 | * Combo-box styling 8 | */ 9 | 10 | 11 | menuitem.KanoComboBox, menuitem.KanoComboBox.up_button, menuitem.KanoComboBox.down_button { 12 | background: #ffffff; 13 | border-width: 2px; 14 | border-color: @active_border; 15 | border-right-style: solid; 16 | border-left-style: solid; 17 | } 18 | 19 | 20 | menuitem.KanoComboBox { 21 | padding-top: 5px; 22 | padding-bottom: 5px; 23 | border-top-style: none; 24 | border-bottom-style: none; 25 | } 26 | 27 | menuitem.KanoComboBox.up_button { 28 | border-top-style: solid; 29 | border-bottom-style: none; 30 | } 31 | 32 | menuitem.KanoComboBox.down_button { 33 | border-top-style: none; 34 | border-bottom-style: solid; 35 | } 36 | 37 | menuitem.KanoComboBox label { 38 | color: @active_writing; 39 | font-family: Bariol; 40 | font-size: 14pt; 41 | } 42 | 43 | menuitem.KanoComboBox:hover { 44 | background: @kano_green; 45 | color: #ffffff; 46 | } 47 | 48 | /* These control the arrow button styling */ 49 | imagemenuitem.KanoComboBox { 50 | padding-top: 10px; 51 | padding-bottom: 10px; 52 | } 53 | 54 | imagemenuitem.KanoComboBox:hover { 55 | background: @kano_green; 56 | } 57 | 58 | button.KanoComboBox { 59 | background: #ffffff; 60 | border-color: @active_border; 61 | border-style: solid; 62 | border-width: 2px; 63 | border-radius:3px; 64 | padding: 9px; 65 | } 66 | 67 | button.KanoComboBox label { 68 | color: @active_writing; 69 | font-family: Bariol; 70 | font-size: 14pt; 71 | } 72 | -------------------------------------------------------------------------------- /libs/kano-python/includes/kano/python/python_helpers.h: -------------------------------------------------------------------------------- 1 | /** 2 | * python_helpers.h 3 | * 4 | * Copyright (C) 2016 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | * 7 | * Provide functions to assist calling Python modules 8 | */ 9 | 10 | 11 | #ifndef __PYTHON_HELPERS_H__ 12 | #define __PYTHON_HELPERS_H__ 13 | 14 | #include 15 | #include 16 | 17 | PyObject *new_pyobject(PyObject *o); 18 | PyObject *new_pyobject(int o); 19 | PyObject *new_pyobject(long o); 20 | PyObject *new_pyobject(double o); 21 | PyObject *new_pyobject(bool o); 22 | PyObject *new_pyobject(std::string o); 23 | PyObject *new_pyobject(char *o); 24 | void fill_tuple(PyObject *tuple, int pos); 25 | 26 | 27 | template 28 | void fill_tuple(PyObject *tuple, int pos, Type arg1, Args... args) 29 | { 30 | PyObject *py_arg = new_pyobject(arg1); 31 | PyTuple_SetItem(tuple, pos, py_arg); 32 | 33 | if (sizeof...(args) > 0) 34 | fill_tuple(tuple, pos + 1, args...); 35 | } 36 | 37 | 38 | /** 39 | * Creates a new tuple packed with PyObject* converted args. 40 | * 41 | * Note: Returns new reference to tuple 42 | */ 43 | template 44 | PyObject *new_tuple(Args... args) 45 | { 46 | PyObject *tuple = PyTuple_New(sizeof...(args)); 47 | fill_tuple(tuple, 0, args...); 48 | 49 | return tuple; 50 | } 51 | 52 | 53 | class Binding { 54 | public: 55 | Binding(const std::string module_str); 56 | ~Binding(); 57 | protected: 58 | PyObject *run_func(const std::string function_name, PyObject *args = NULL) const; 59 | void finalise(); 60 | 61 | const std::string module_name; 62 | PyObject *module; 63 | }; 64 | 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # postinst 4 | # 5 | # Copyright (C) 2014-2016 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | 9 | TMP_FILE=/tmp/kano-toolset_conf 10 | 11 | case "$1" in 12 | configure) 13 | if [ "$2" != "" ]; then 14 | # When separating utils into submodules, the .pyc isn't deleted 15 | if `dpkg --compare-versions $2 lt 2.3.0-2`; then 16 | rm -f /usr/lib/python2.7/dist-packages/kano/utils.pyc 17 | fi 18 | fi 19 | 20 | # Create custom sudoers file 21 | echo 'Defaults env_keep += "KLOG_FORCE_FLUSH NO_AT_BRIDGE"' > $TMP_FILE 22 | echo "%sudo ALL=(root) NOPASSWD: /usr/bin/kano-launcher" >> $TMP_FILE 23 | echo "%sudo ALL=(root) NOPASSWD: /usr/bin/is_internet" >> $TMP_FILE 24 | echo "%sudo ALL=(root) NOPASSWD: /usr/sbin/ifplugstatus" >> $TMP_FILE 25 | 26 | # The owner and group for the sudoers file must both be 0 27 | chown root:root $TMP_FILE 28 | # The file permissions must be set to 0440 29 | chmod 0440 $TMP_FILE 30 | # Move the file to the sudoers directory 31 | mv $TMP_FILE /etc/sudoers.d/ 32 | 33 | # Configure kano.logging 34 | if [ ! -f /etc/kano-logs.conf ]; then 35 | cat >/etc/kano-logs.conf <&2 39 | exit 1 40 | fi 41 | 42 | # Turn camera off 43 | if grep -q "start_x=1" /boot/config.txt; then 44 | echo "Toggling PiCamera off [reboot needed]" 45 | sed -i "s/start_x=1/start_x=0/g" /boot/config.txt 46 | exit 0 47 | # Turn camera on 48 | elif grep -q "start_x=0" /boot/config.txt; then 49 | echo "Toggling PiCamera on [reboot needed]" 50 | sed -i "s/start_x=0/start_x=1/g" /boot/config.txt 51 | exit 0 52 | # Camera setting not detected, add it to config file 53 | else 54 | echo "Toggling PiCamera on [reboot needed]" 55 | echo "# Toggles PiCamera on(1) and off(0)" >> /boot/config.txt 56 | echo "start_x=1" >> /boot/config.txt 57 | exit 0 58 | fi 59 | -------------------------------------------------------------------------------- /tests/fixtures/webapp.py: -------------------------------------------------------------------------------- 1 | # 2 | # webapp.py 3 | # 4 | # Copyright (C) 2017 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | # 7 | # Fixtures for the WebApp browser 8 | # 9 | 10 | 11 | import re 12 | import pytest 13 | 14 | WEB_URI_RE = re.compile(r'^https?://.+$') 15 | SCHEME_URI_RE = re.compile(r'^kano:.+$') 16 | API_URI_RE = re.compile(r'^.*#api:.+$') 17 | 18 | 19 | SAMPLE_URIS = ( 20 | 'http://www.kano.me', 21 | 'kano:share:123456789', 22 | 'http://www.kano.me/something#api:some_func[12345]/args' 23 | ) 24 | 25 | 26 | @pytest.fixture(scope='function') 27 | def webapp(monkeypatch): 28 | ''' 29 | Instance of the WebApp class adapted for use in tests 30 | ''' 31 | 32 | import gtk 33 | from kano.webapp import WebApp 34 | 35 | app = WebApp() 36 | app._index = 'http://www.kano.me' 37 | 38 | monkeypatch.setattr(gtk, 'main', lambda *args: None) 39 | app.run() 40 | 41 | return app 42 | 43 | 44 | @pytest.fixture(scope='function', params=SAMPLE_URIS) 45 | def navigation_action(request): 46 | ''' 47 | Navigation request objects mimicking those passed to the navigation request 48 | handler 49 | ''' 50 | 51 | class FakeAction(object): 52 | def get_original_uri(self): 53 | return request.param 54 | 55 | def is_api_call(self): 56 | uri = self.get_original_uri() 57 | return bool(API_URI_RE.match(uri)) 58 | 59 | def is_scheme_call(self): 60 | uri = self.get_original_uri() 61 | return not self.is_api_call() \ 62 | and bool(SCHEME_URI_RE.match(uri)) 63 | 64 | def is_web_call(self): 65 | uri = self.get_original_uri() 66 | return not self.is_api_call() \ 67 | and not self.is_scheme_call() \ 68 | and bool(WEB_URI_RE.match(uri)) 69 | 70 | return FakeAction() 71 | -------------------------------------------------------------------------------- /bin/kano-volume: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # kano-volume 4 | # 5 | # Copyright (C) 2014,2015 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # This script increases and decreases the analog audio volume output level 9 | # 10 | # TODO: How to increase/decrease volume for HDMI output? 11 | # 12 | 13 | # Check whether omxplayer is running, if not, we don't need to send 14 | # any messages on Dbus 15 | pgrep 'omxplayer' > /dev/null 16 | omx_running="$?" 17 | 18 | if [ "$omx_running" -eq 0 ]; then 19 | USER_NAME="$(id -u -n)" 20 | OMXPLAYER_DBUS_ADDR="/tmp/omxplayerdbus.$USER_NAME" 21 | OMXPLAYER_DBUS_PID="/tmp/omxplayerdbus.$USER_NAME.pid" 22 | 23 | export DBUS_SESSION_BUS_ADDRESS=`cat $OMXPLAYER_DBUS_ADDR` 24 | export DBUS_SESSION_BUS_PID=`cat $OMXPLAYER_DBUS_PID` 25 | fi 26 | 27 | # First parameter is either "up" or "down" 28 | updown=$1 29 | 30 | case $updown in 31 | "up") 32 | amixer set Master 1+ > /dev/null 2>&1 33 | dbus-send --session --type=signal --dest=me.kano.audio /me/kano/audio me.kano.audio.volume.changed 34 | 35 | if [ "$omx_running" -eq 0 ]; then 36 | dbus-send --print-reply=literal --session --dest=org.mpris.MediaPlayer2.omxplayer /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Action int32:18 >/dev/null 37 | fi 38 | ;; 39 | "down") 40 | amixer set Master 1- > /dev/null 2>&1 41 | dbus-send --session --type=signal --dest=me.kano.audio /me/kano/audio me.kano.audio.volume.changed 42 | 43 | if [ "$omx_running" -eq 0 ]; then 44 | dbus-send --print-reply=literal --session --dest=org.mpris.MediaPlayer2.omxplayer /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Action int32:17 >/dev/null 45 | fi 46 | ;; 47 | 48 | *) 49 | echo "Syntax error: kano-volume < up | down >" 50 | exit -1 51 | esac 52 | exit 0 53 | -------------------------------------------------------------------------------- /tests/fixtures/cpu.py: -------------------------------------------------------------------------------- 1 | # 2 | # cpu.py 3 | # 4 | # Copyright (C) 2018 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | # 7 | # Fixtures for fake /proc/cpuinfo files 8 | # 9 | 10 | 11 | import os 12 | import json 13 | import pytest 14 | 15 | from tests.fixtures.boards import BoardData 16 | 17 | 18 | CPUINFO_DIR = os.path.join( 19 | os.path.dirname(os.path.realpath(__file__)), 'cpuinfo' 20 | ) 21 | # TODO: Add configurations for other RPi devices 22 | PLATFORMS = [ 23 | ('rpi_3', BoardData.RPI_3_KEY), 24 | ('rpi_2', BoardData.RPI_2_B_KEY), 25 | ('empty', None), 26 | ('incorrect', None), 27 | ('garbage', None) 28 | ] 29 | 30 | 31 | @pytest.fixture(scope='function', params=PLATFORMS) 32 | def cpu(request, fs): 33 | ''' 34 | Simulates several different platforms, each having a CPU of a different 35 | underlying architectures. It does this by mocking the system outputs which 36 | would be associated with such a CPU, it does not actually emulate the 37 | instruction set. 38 | ''' 39 | 40 | platform, key = request.param 41 | 42 | cpuinfo_json_path = os.path.join( 43 | CPUINFO_DIR, 44 | '{}_cpuinfo.json'.format(platform) 45 | ) 46 | fs.add_real_file(cpuinfo_json_path) 47 | with open(cpuinfo_json_path, 'r') as cpuinfo_json_f: 48 | cpuinfo_json = json.load(cpuinfo_json_f) 49 | 50 | cpuinfo_dump_path = os.path.join( 51 | CPUINFO_DIR, 52 | '{}_cpuinfo.dump'.format(platform) 53 | ) 54 | cpu_file = fs.add_real_file(cpuinfo_dump_path) 55 | fs.CreateLink('/proc/cpuinfo', cpuinfo_dump_path) 56 | 57 | cpu_file.platform = platform 58 | cpu_file.platform_key = key 59 | cpu_file.serial = cpuinfo_json.get('serial', None) 60 | cpu_file.revision = cpuinfo_json.get('revision', None) 61 | 62 | if cpu_file.serial == '': 63 | cpu_file.serial = None 64 | 65 | return cpu_file 66 | -------------------------------------------------------------------------------- /libs/kano-networking/src/ifaces.c: -------------------------------------------------------------------------------- 1 | /** 2 | * ifaces.c 3 | * 4 | * Copyright (C) 2016 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | * 7 | * Methods for interracting with interfaces 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "kano/networking/ifaces.h" 17 | 18 | /** 19 | * Search through the available interfaces and return the first available 20 | * which matches the type provided 21 | * 22 | * The caller is responsible for freeing iface_name 23 | * 24 | * Upon error, iface_name does not need freeing 25 | */ 26 | int select_iface(const char *iface_type, char **iface_name) 27 | { 28 | struct ifaddrs *iface_addr; 29 | 30 | if (getifaddrs(&iface_addr) == -1) 31 | return E_NO_INTERFACES; 32 | 33 | struct ifaddrs *candidate_iface = iface_addr; 34 | bool match = false; 35 | 36 | while (candidate_iface != NULL && !match) { 37 | if (strstr(candidate_iface->ifa_name, iface_type) != NULL) { 38 | match = true; 39 | break; 40 | } 41 | 42 | candidate_iface = candidate_iface->ifa_next; 43 | } 44 | 45 | if (!match) { 46 | freeifaddrs(iface_addr); 47 | return E_NO_INTERFACES; 48 | } 49 | 50 | *iface_name = strdup(candidate_iface->ifa_name); 51 | freeifaddrs(iface_addr); 52 | 53 | return 0; 54 | } 55 | 56 | /** 57 | * Check for interfaces of the provided type and print the interface name 58 | */ 59 | int check_iface_type(const char *iface_type) 60 | { 61 | char *iface; 62 | 63 | if (select_iface(iface_type, &iface) == E_NO_INTERFACES) { 64 | printf("No interfaces for iface type %s\n", iface_type); 65 | return E_NO_INTERFACES; 66 | } 67 | 68 | printf("Found %s iface: %s\n", iface_type, iface); 69 | free(iface); 70 | 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /tests/utils/test_system.py: -------------------------------------------------------------------------------- 1 | # 2 | # test_system.py 3 | # 4 | # Copyright (C) 2017 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | # 7 | # Tests for the kano.utils.system module 8 | # 9 | 10 | import pytest 11 | 12 | 13 | DEBIAN_VERSION_FILE = '/etc/debian_version' 14 | INIT_FILE = '/sbin/init' 15 | SYSTEMD_INIT_FILE = '/lib/systemd/systemd' 16 | 17 | 18 | @pytest.mark.parametrize( 19 | 'version,expected', 20 | [ 21 | ('8.0', True), 22 | ('7.0', False), 23 | ('9.0', False), 24 | ('8.1', True), 25 | ('8.39020', True), 26 | ('88.0', False), 27 | ('', False), 28 | ('not a version', False), 29 | ('not.a.version', False), 30 | ('8.something', True), 31 | ('major.version.84', False), 32 | ] 33 | ) 34 | def test_is_jessie(fs, version, expected): 35 | from kano.utils.system import is_jessie 36 | 37 | fs.CreateFile(DEBIAN_VERSION_FILE, contents=version) 38 | 39 | assert is_jessie() == expected 40 | 41 | 42 | def test_is_jessie_no_file(fs): 43 | from kano.utils.system import is_jessie 44 | 45 | assert not is_jessie() 46 | 47 | 48 | def test_is_systemd(fs): 49 | from kano.utils.system import is_systemd 50 | 51 | fs.CreateFile(SYSTEMD_INIT_FILE) 52 | fs.CreateLink(INIT_FILE, SYSTEMD_INIT_FILE) 53 | 54 | assert is_systemd() 55 | 56 | 57 | def test_is_systemd_no_link(fs): 58 | from kano.utils.system import is_systemd 59 | 60 | fs.CreateFile(INIT_FILE) 61 | 62 | assert not is_systemd() 63 | 64 | 65 | def test_is_systemd_wrong_link(fs): 66 | from kano.utils.system import is_systemd 67 | 68 | fs.CreateFile(SYSTEMD_INIT_FILE) 69 | fs.CreateLink(INIT_FILE, '/some/wrong/file') 70 | 71 | assert not is_systemd() 72 | 73 | 74 | def test_is_systemd_missing_link(fs): 75 | from kano.utils.system import is_systemd 76 | 77 | assert not is_systemd() 78 | -------------------------------------------------------------------------------- /libs/kano/src/kano_network.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * kano_network.cpp 4 | * 5 | * Copyright (C) 2016 Kano Computing Ltd. 6 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 7 | * 8 | * Adds the interface between useful Kano Toolset functions and C++ 9 | * 10 | * kano.network 11 | * 12 | */ 13 | 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | #include "kano/kano/kano_bindings.h" 20 | 21 | 22 | std::unordered_map parse_dict(PyObject *py_dict) 23 | { 24 | std::unordered_map dict; 25 | 26 | PyObject *py_key; 27 | PyObject *py_value; 28 | std::string key; 29 | std::string value; 30 | Py_ssize_t pos = 0; 31 | 32 | while (PyDict_Next(py_dict, &pos, &py_key, &py_value)) { 33 | if (py_key != Py_None && Py_TYPE(py_key) == &PyString_Type) { 34 | key = PyString_AsString(py_key); 35 | } else { 36 | key = ""; 37 | } 38 | 39 | if (py_value != Py_None && Py_TYPE(py_value) == &PyString_Type) { 40 | value = PyString_AsString(py_value); 41 | } else { 42 | value = ""; 43 | } 44 | 45 | dict[key] = value; 46 | } 47 | 48 | return dict; 49 | } 50 | 51 | 52 | kano::network::network(): 53 | Binding(KANO_NETWORK) 54 | { 55 | } 56 | 57 | 58 | std::unordered_map kano::network::get_network_info() const 59 | { 60 | std::unordered_map info; 61 | 62 | PyObject *py_info = this->run_func("network_info"); 63 | 64 | if (py_info == NULL || !PyDict_Check(py_info) || PyDict_Size(py_info) == 0) 65 | return info; 66 | 67 | PyObject *values = PyDict_Values(py_info); 68 | PyObject *iface_details = PyList_GetItem(values, 0); 69 | 70 | info = parse_dict(iface_details); 71 | 72 | Py_CLEAR(py_info); 73 | 74 | return info; 75 | } 76 | -------------------------------------------------------------------------------- /kano-launcher/README.md: -------------------------------------------------------------------------------- 1 | ### Kano launcher 2 | 3 | kano-launcher implements a poor man's container system on kernels where control groups 4 | are not present, but namespaces are. 5 | 6 | ### Usage 7 | kano-launcher 8 | 9 | kano-launcher is basically intended to run (using system(2)). 10 | 11 | If present, config files in ```/usr/share/kano-toolset/kano-launcher/conf/``` 12 | modify its behavior. 13 | 14 | The optional argument `````` helps identify a config file. 15 | 16 | If the name of the config file exactly matches it is chosen. 17 | Otherwise, any config file matching a substring of may match. 18 | 19 | The following lines in the config file are allowed: 20 | 21 | ``` 22 | no_kill 23 | extra_cmd: 24 | match_only_preset 25 | ``` 26 | 27 | * match_only_preset causes a config file to match only if its filename matches the preset, not 28 | in the command. 29 | 30 | If a matching config file is found, the command is 'containerised'. This means it, and 31 | all processes it launches, are subject to being killed when a new kano-launch instance is run. 32 | 'no_kill' prevents a launched app from killing *other* launched apps (but not from itself 33 | being killed). 34 | 35 | if 'extra_cmd' is present, an additional command is run, as specified. 36 | 37 | 38 | ### Internals: 39 | 40 | Internally, if an app is to be containerised it is launched with clone using CLONE_NEWUTS, 41 | and a copy of ```/proc//ns/uts``` is bind-mounted under ```~/.kano-app-containers```. 42 | When an instance of kano-launcher decides to kill the other apps, it scans this directory and 43 | kills any processes using one of the UTS namespaces used there, then removes the bind mount. 44 | 45 | To do so, it uses a separate command kano-kill-ns. This is used as follows: 46 | ``` kano-kill-ns Pid1 Pid2 Pid3 ... ``` 47 | 48 | Which sends the signal to any of the pids listed which are in the UTS namespace 49 | linked to 50 | 51 | -------------------------------------------------------------------------------- /kano/xwindow.py: -------------------------------------------------------------------------------- 1 | # 2 | # xwindow.py 3 | # 4 | # Copyright (C) 2015-2019 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | # 7 | # low level Xlib utilities 8 | # 9 | # Be careful mixing calls to this with Gtk. 10 | 11 | from contextlib import contextmanager 12 | import Xlib.display 13 | from kano.logging import logger 14 | 15 | 16 | def handle_uncaught_errors(err, req): 17 | # req is always None in the default error handler 18 | logger.error("error from Xlib {}".format(err)) 19 | 20 | 21 | @contextmanager 22 | def display(): 23 | ''' 24 | A context manager for display 25 | ''' 26 | d = Xlib.display.Display() 27 | d.set_error_handler(handle_uncaught_errors) 28 | 29 | yield d 30 | 31 | d.close() 32 | 33 | 34 | def find_xwindow_by_id(xid, parent): 35 | ''' 36 | Given a parent Xlib Window, find a window with given xid 37 | returning Xlib window object 38 | ''' 39 | try: 40 | for c in parent.query_tree().children: 41 | if c.id == xid: 42 | return c 43 | r = find_xwindow_by_id(xid, c) 44 | if r is not None: 45 | return r 46 | except Exception: 47 | return None 48 | 49 | 50 | def xid_to_str(xid): 51 | ''' make a strign suitable for passing on command line''' 52 | 53 | return hex(xid).rstrip('L') 54 | 55 | # NB this function opens its own X connection. This is only safe because 56 | # we don't return any objects, only xids. Normally Xlib objects must be used 57 | # in the context of a Display(). 58 | 59 | 60 | def get_child_windows_from_xid(xid): 61 | ''' 62 | Given an X window id, return the xid's of its children 63 | ''' 64 | try: 65 | with display() as d: 66 | root = d.screen().root 67 | xw = find_xwindow_by_id(xid, root) 68 | children = [] 69 | for c in xw.query_tree().children: 70 | children.append(xid_to_str(c.id)) 71 | return children 72 | except Exception: 73 | return [] 74 | -------------------------------------------------------------------------------- /pythontest.mk: -------------------------------------------------------------------------------- 1 | # 2 | # pythontest.mk 3 | # 4 | # Copyright (C) 2018 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | # 7 | # Project-independent python test targets. 8 | # 9 | # Add to main Makefile with: 10 | # 11 | # include pythontest.mk 12 | # 13 | # Ensure to set the REPO variable in the main Makefile: 14 | # 15 | # REPO:= my-project 16 | # 17 | # Creates a `pythontest` target which can either be accessed from main Makefile 18 | # or linked to another target, e.g. 19 | # 20 | # test: pythontest 21 | # 22 | 23 | # Run tests, excluding these tagged items, e.g 24 | # make check OMITTED_TAGS="tag1 tag2" 25 | OMITTED_TAGS = 26 | 27 | empty:= 28 | space:= $(empty) $(empty) 29 | 30 | REPORT_DIR = reports 31 | COVERAGE_REPORT_DIR = $(REPORT_DIR)/coverage 32 | TESTS_REPORT_DIR = $(REPORT_DIR)/tests 33 | 34 | # Elaborate mechanism just to get the correct syntax for the pytest markers param 35 | _FIRST_TAG := $(firstword $(OMITTED_TAGS)) 36 | PYTEST_TAGS_EXPR := $(foreach tag, $(OMITTED_TAGS), $(if $(filter $(tag), $(_FIRST_TAG)),not $(tag),and not $(tag))) 37 | 38 | ifeq ($(PYTEST_TAGS_EXPR), ) 39 | PYTEST_TAGS_FLAG := 40 | else 41 | PYTEST_TAGS_FLAG := -m "$(strip $(PYTEST_TAGS_EXPR))" 42 | endif 43 | BEHAVE_TAGS_FLAG := $(join $(addprefix --tags=-,$(OMITTED_TAGS)), $(space)) 44 | 45 | 46 | # 47 | # Run the tests 48 | # 49 | # Requirements: 50 | # - pytest 51 | # - behave 52 | # - pytest-cov 53 | # - pytest-flake8 54 | # - pytest-tap 55 | # 56 | pythontest: 57 | # Refresh the reports directory 58 | rm -rf $(REPORT_DIR) 59 | mkdir -p $(REPORT_DIR) 60 | mkdir -p $(COVERAGE_REPORT_DIR) 61 | mkdir -p $(TESTS_REPORT_DIR) 62 | # Run the tests 63 | -coverage run --module pytest --flake8 --cache-clear $(PYTEST_TAGS_FLAG) --junitxml=$(TESTS_REPORT_DIR)/pytest_results.xml 64 | -coverage run --append --module behave $(BEHAVE_TAGS_FLAG) --junit --junit-directory=$(TESTS_REPORT_DIR) 65 | # Generate reports 66 | coverage xml 67 | coverage html 68 | coverage-badge -o $(COVERAGE_REPORT_DIR)/$(REPO)-coverage.svg 69 | -------------------------------------------------------------------------------- /libs/kano/src/kano_utils_audio.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * utils_audio.cpp 4 | * 5 | * Copyright (C) 2016 Kano Computing Ltd. 6 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 7 | * 8 | * Adds the interface between useful Kano Toolset functions and C++ 9 | * 10 | * kano.utils.audio 11 | * 12 | */ 13 | 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | #include "kano/kano/kano_bindings.h" 20 | 21 | 22 | kano::utils::audio::audio(): 23 | Binding(KANO_UTILS_AUDIO) 24 | { 25 | } 26 | 27 | 28 | bool kano::utils::audio::play_sound(const std::string audio_file, const bool background) const 29 | { 30 | PyObject *py_success = this->run_func( 31 | "play_sound", 32 | new_tuple(audio_file, background) 33 | ); 34 | 35 | if (py_success == NULL) 36 | return false; 37 | 38 | bool success = PyBool_Check(py_success) 39 | && py_success == Py_True; 40 | 41 | Py_CLEAR(py_success); 42 | 43 | return success; 44 | } 45 | 46 | 47 | long kano::utils::audio::percent_to_millibel(const int percent, const bool raspberry_mod) const 48 | { 49 | PyObject *py_milli = this->run_func( 50 | "percent_to_millibel", 51 | new_tuple(percent, raspberry_mod) 52 | ); 53 | 54 | if (py_milli == NULL) 55 | return 0; 56 | 57 | if (!PyInt_Check(py_milli)) { 58 | Py_CLEAR(py_milli); 59 | return 0; 60 | } 61 | 62 | long milli = PyInt_AsLong(py_milli); 63 | Py_CLEAR(py_milli); 64 | 65 | return milli; 66 | } 67 | 68 | 69 | long kano::utils::audio::get_volume() const 70 | { 71 | PyObject *py_vol = this->run_func("get_volume"); 72 | 73 | if (py_vol == NULL) 74 | return 0; 75 | 76 | if (!PyInt_Check(py_vol)) { 77 | Py_CLEAR(py_vol); 78 | return 0; 79 | } 80 | 81 | long vol = PyInt_AsLong(py_vol); 82 | Py_CLEAR(py_vol); 83 | 84 | return vol; 85 | } 86 | 87 | 88 | void kano::utils::audio::set_volume(float percent) const 89 | { 90 | this->run_func( 91 | "set_volume", 92 | new_tuple(percent) 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /kano/gtk3/application_window.py: -------------------------------------------------------------------------------- 1 | # 2 | # Custom window base class 3 | # 4 | # Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | # 7 | # You can use this as a base for you application's window in case 8 | # you'd like to blur it. 9 | # 10 | 11 | from gi import require_version 12 | require_version('Gtk', '3.0') 13 | 14 | from gi.repository import Gtk, Gdk 15 | 16 | from kano.gtk3.apply_styles import apply_common_to_screen 17 | 18 | 19 | class ApplicationWindow(Gtk.Window): 20 | def __init__(self, title="Application", width=None, height=None): 21 | Gtk.Window.__init__(self, title=title) 22 | 23 | self.set_decorated(False) 24 | self.set_resizable(False) 25 | 26 | screen = Gdk.Screen.get_default() 27 | self._win_width = width 28 | if width <= 1: 29 | self._win_width = int(screen.get_width() * width) 30 | 31 | self._win_height = height 32 | if height <= 1: 33 | self._win_height = int(screen.get_height() * height) 34 | self.set_size_request(self._win_width, self._win_height) 35 | 36 | self.set_position(Gtk.WindowPosition.CENTER) 37 | self.connect('delete-event', Gtk.main_quit) 38 | 39 | apply_common_to_screen() 40 | 41 | self._overlay = Gtk.Overlay() 42 | self.add(self._overlay) 43 | 44 | self._blur = Gtk.EventBox() 45 | self._blur.get_style_context().add_class('blur') 46 | 47 | self._blurred = False 48 | 49 | # TODO: Maybe handle the taskbar here to avoid even more code duplication? 50 | 51 | def blur(self): 52 | if not self._blurred: 53 | self._overlay.add_overlay(self._blur) 54 | self._blur.show() 55 | self._blurred = True 56 | 57 | def unblur(self): 58 | if self._blurred: 59 | self._overlay.remove(self._blur) 60 | self._blurred = False 61 | 62 | def set_main_widget(self, widget): 63 | self._overlay.add(widget) 64 | 65 | def remove_main_widget(self): 66 | for w in self._overlay.get_children(): 67 | self._overlay.remove(w) 68 | -------------------------------------------------------------------------------- /kano-launcher/kano-kill-ns.c: -------------------------------------------------------------------------------- 1 | // kano-kill-ns 2 | // 3 | // Copyright (C) 2015 Kano Computing Ltd. 4 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 5 | // 6 | // kills given pids if they are in the given namespace. 7 | // usage: kano-kill-ns pid1 pid2 pid3... 8 | // 9 | #include 10 | #include 11 | #include 12 | #include 13 | #define __USE_POSIX 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "kano_c_logging.h" 20 | #define PATH_LEN 256 21 | #define LINK_LEN 256 22 | 23 | int main(int argc,char *argv[]) 24 | { 25 | int i; 26 | if(argc<3){ 27 | kano_log_error("kano-kill-ns: too few arguments\n"); 28 | exit(1); 29 | } 30 | // parse first two arguments 31 | int signum=atoi(argv[1]); 32 | int count; 33 | char *end; 34 | ino_t tokill=strtoull(argv[2],&end,10); 35 | if(*end!=0) { 36 | kano_log_error("kano-kill-ns: ns inode not a number\n"); 37 | exit(1); 38 | } 39 | int err; 40 | 41 | // now send signal to each pid 42 | for(i=3;i/ns/uts to see if it is the same as the inode we have 51 | // been given 52 | count=snprintf(path,PATH_LEN,"/proc/%d/ns/uts",pid); 53 | if(count=0 || count==PATH_LEN){ 54 | kano_log_warning("kano-kill-ns: path too long for %s, skipping\n",argv[i]); 55 | continue; 56 | } 57 | struct stat statbuf; 58 | err=stat(path,&statbuf); 59 | if(err){ 60 | kano_log_warning("kano-kill-ns failed to stat %s\n",path); 61 | continue; 62 | } 63 | // if matched, send the signal 64 | if(statbuf.st_ino==tokill){ 65 | kano_log_info("kano-kill-ns: killing %d\n",pid); 66 | int err= kill(pid, signum); 67 | if(err) kano_log_warning("kano-kill-ns failed to kill %d\n",pid); 68 | } 69 | } 70 | return 0; 71 | } 72 | 73 | -------------------------------------------------------------------------------- /tests/fixtures/keyboard.py: -------------------------------------------------------------------------------- 1 | # 2 | # keyboard.py 3 | # 4 | # Copyright (C) 2018 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | # 7 | # Fixtures for fake keyboards 8 | # 9 | 10 | 11 | import imp 12 | import os 13 | import pytest 14 | 15 | 16 | KEYBOARD_LSUSB_DIR = os.path.join( 17 | os.path.dirname(os.path.realpath(__file__)), 'keyboard' 18 | ) 19 | KEYBOARD_LSUSB_OUTPUTS = [ 20 | ('no_keyboard', None), 21 | ('other_keyboard', None), 22 | ('en_keyboard', 'en'), 23 | ('es_keyboard', 'es'), 24 | ] 25 | 26 | 27 | 28 | @pytest.fixture(scope='function', params=KEYBOARD_LSUSB_OUTPUTS) 29 | def keyboard(request, fs, monkeypatch): 30 | ''' 31 | Simulates different keyboards, mainly by their outputs from terminal 32 | commands. 33 | 34 | Note: This fixture auto-reimports the `kano.utils.hardware` module, which we 35 | expect to be the one which requires the patch, however depending on 36 | the module, it may be required to re-import the module being tested 37 | as this fixture patches `kano.utils.run_cmd` and if the tested module 38 | depends on this directly and has already been loaded then the updated 39 | version will not propagate. To re-import use: 40 | 41 | import imp 42 | import module.to.be.tested 43 | imp.reload(module.to.be.tested) 44 | 45 | ''' 46 | 47 | kb_file, version = request.param 48 | 49 | lsusb_output_path = os.path.join( 50 | KEYBOARD_LSUSB_DIR, 51 | '{}.dump'.format(kb_file) 52 | ) 53 | fs.add_real_file(lsusb_output_path) 54 | with open(lsusb_output_path, 'r') as lsusb_output_f: 55 | lsusb_output = lsusb_output_f.read() 56 | 57 | def fake_lsusb_out(cmd): 58 | if cmd.startswith('lsusb'): 59 | return lsusb_output, None, None 60 | else: 61 | raise NotImplementedError( 62 | 'Command run is not lsusb: {}'.format(cmd) 63 | ) 64 | 65 | import kano.utils.shell 66 | monkeypatch.setattr(kano.utils.shell, 'run_cmd', fake_lsusb_out) 67 | imp.reload(kano.utils.hardware) 68 | 69 | return version 70 | -------------------------------------------------------------------------------- /kano-keys-pressed/kano-keys-pressed.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // kano-keys-pressed.cpp 3 | // 4 | // Copyright (C) 2015 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // A tool to detect key modifers being pressed (Shift, Ctrl, Alt, ...) 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "hid.h" 18 | 19 | using namespace std; 20 | 21 | int main(int argc, char *argv[]) 22 | { 23 | HID_HANDLE hhid; 24 | bool is_hotkey=false; 25 | bool verbose=false; 26 | int c=0, retry=5, delay=1000; 27 | 28 | while ((c = getopt(argc, argv, "?hvr:d:")) != EOF) 29 | { 30 | switch (c) 31 | { 32 | case '?': 33 | case 'h': 34 | cout << "kano-keys-pressed [ -? | -h | -v | -r retry_times (default=" << retry << ") "; 35 | cout << "| -d delay (default=" << delay << "ms) ]" << endl; 36 | return 0; 37 | case 'v': 38 | verbose=true; 39 | break; 40 | case 'r': 41 | retry=atoi(optarg); 42 | break; 43 | case 'd': 44 | delay=atoi(optarg); 45 | break; 46 | } 47 | } 48 | 49 | // Ask if any key modifiers are being pressed 50 | do { 51 | // Open, read and close access to the keyboard 52 | // to allow the kernel to refresh any /dev/input mappings 53 | hhid=hid_init(0); 54 | if (hhid) { 55 | is_hotkey = is_hotkey_pressed(hhid, verbose); 56 | hid_terminate(hhid); 57 | } 58 | if (verbose) { 59 | cout << "is hotkey pressed? " << (is_hotkey ? "Yes" : "No") << endl; 60 | } 61 | 62 | if (is_hotkey) { 63 | retry=0; 64 | } 65 | else { 66 | if (--retry > 0) { 67 | usleep(delay * 1000); 68 | } 69 | } 70 | 71 | } while (retry > 0); 72 | 73 | // TODO: Map each key modifier to a value multiplier, 74 | // so we can explain exactly which keys are being pressed 75 | exit((is_hotkey == true) ? 10 : 0); 76 | } 77 | -------------------------------------------------------------------------------- /bin/colours-cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # colours-cli 4 | # 5 | # Copyright (C) 2014 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | 9 | import os 10 | import sys 11 | 12 | if __name__ == '__main__' and __package__ is None: 13 | dir_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 14 | if dir_path != '/usr': 15 | sys.path.insert(1, dir_path) 16 | 17 | import kano.colours as colours 18 | 19 | if __name__ != '__main__': 20 | sys.exit("This is a script, do not import it as a module!") 21 | 22 | if len(sys.argv) == 1: 23 | help = 'Wrong usage, needs an argument\n' 24 | help += 'Possible uses: get_app_data_dir, load_app_state_variable, save_app_state_variable\n' 25 | help += 'increment_app_state_variable, unlock, lock, is_registered, get_avatar, get_xp\n' 26 | help += 'get_level, get_username, has_token, get_stats' 27 | sys.exit(help) 28 | 29 | 30 | def get_preset_from_id(id): 31 | if id == '0': 32 | return "command_prompt" 33 | elif id == '1': 34 | return "keyboard" 35 | elif id == '2': 36 | return "instructions" 37 | elif id == '3': 38 | return "success_text" 39 | elif id == '4': 40 | return "success_icon" 41 | elif id == '5': 42 | return "hint_text" 43 | elif id == '6': 44 | return "hint_icon" 45 | elif id == '7': 46 | return "error_text" 47 | elif id == '8': 48 | return "error_icon" 49 | elif id == '9': 50 | return "code" 51 | else: 52 | return "alert" 53 | 54 | 55 | def parse_string(string): 56 | # First part of the string 57 | pos1 = string.index("{{") 58 | first_part = string[:pos1] 59 | 60 | # Last part of the string 61 | pos2 = string.index("}}") 62 | last_part = string[pos2 + 2:] 63 | 64 | # Preset id 65 | preset_id = string[pos1 + 2] 66 | preset = get_preset_from_id(preset_id) 67 | 68 | # Colour part of the string 69 | colour_part = string[pos1 + 3:pos2] 70 | colour_part = colours.decorate_with_preset(colour_part, preset) 71 | return first_part + colour_part + last_part 72 | 73 | 74 | output = sys.argv[1] 75 | 76 | # If format found then parse the string "my {{1example}}" 77 | while output.find("{{") != -1: 78 | output = parse_string(output) 79 | 80 | print output 81 | -------------------------------------------------------------------------------- /kano/gtk3/heading.py: -------------------------------------------------------------------------------- 1 | # 2 | # kano_dialog.py 3 | # 4 | # Copyright (C) 2014-2019 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | # 7 | # Heading used frequently around kano-settings and kano-login 8 | # 9 | 10 | from gi import require_version 11 | require_version('Gtk', '3.0') 12 | 13 | from gi.repository import Gtk 14 | from kano.paths import common_css_dir 15 | 16 | 17 | class Heading(): 18 | def __init__(self, title, description): 19 | 20 | cssProvider = Gtk.CssProvider() 21 | cssProvider.load_from_path(common_css_dir + "/heading.css") 22 | styleContext = Gtk.StyleContext() 23 | styleContext.add_provider(cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER) 24 | 25 | self.title = Gtk.Label(title) 26 | self.title_style = self.title.get_style_context() 27 | self.title_style.add_provider(cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER) 28 | self.title_style.add_class('title') 29 | 30 | self.container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) 31 | self.container.pack_start(self.title, False, False, 0) 32 | 33 | if description != "": 34 | self.description = Gtk.Label(description) 35 | self.description.set_justify(Gtk.Justification.CENTER) 36 | self.description.set_line_wrap(True) 37 | self.description_style = self.description.get_style_context() 38 | self.description_style.add_provider(cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER) 39 | self.description_style.add_class('description') 40 | 41 | self.container.pack_start(self.description, False, False, 0) 42 | 43 | def set_text(self, title, description): 44 | self.title.set_text(title) 45 | if getattr(self, 'description'): 46 | self.description.set_text(description) 47 | 48 | def get_text(self): 49 | if getattr(self, 'description'): 50 | return [self.title.get_text(), self.description.get_text()] 51 | else: 52 | return [self.title.get_text(), ""] 53 | 54 | def set_margin(self, top_margin, right_margin, bottom_margin, left_margin): 55 | self.container.set_margin_left(left_margin) 56 | self.container.set_margin_right(right_margin) 57 | self.container.set_margin_top(top_margin) 58 | self.container.set_margin_bottom(bottom_margin) 59 | -------------------------------------------------------------------------------- /js/backend-api.js: -------------------------------------------------------------------------------- 1 | // backend-api.js 2 | // 3 | // Copyright (C) 2014 Kano Computing Ltd. 4 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 5 | // 6 | // This Javascript module is used to provide callbacks from the webapp Webkit based browser. 7 | // 8 | // In conjunction with Kano Blocks it allows for receiving special events such as capture 9 | // Download links triggered by the user. 10 | // 11 | 12 | window.backend = { 13 | callbacks: [], 14 | seq: 0, 15 | 16 | call: function() { 17 | if (arguments.length >= 1) { 18 | func = arguments[0]; 19 | args = []; 20 | 21 | if (arguments.length > 1 && 22 | typeof arguments[arguments.length - 1] === 'function') { 23 | 24 | arguments = Array.prototype.slice.call(arguments, 0); 25 | callback = arguments.pop(); 26 | timestamp = (new Date).getTime(); 27 | 28 | this.callbacks.push({ 29 | timestamp: timestamp, 30 | func: func, 31 | callback: callback 32 | }); 33 | func += '[' + timestamp + ']'; 34 | } 35 | 36 | for (var i = 1; i < arguments.length; i += 1) { 37 | args.push(encodeURIComponent(arguments[i].toString())); 38 | } 39 | 40 | } else { 41 | func = 'error'; 42 | args = [encodeURIComponent('Invalid API call - no arguments passed.')]; 43 | } 44 | 45 | var args_string = args.join('/'); 46 | 47 | this.seq++; // add a sequence number to ensure string is different from last one 48 | 49 | window.location.hash = 'api:' + func + '/' + this.seq + '/' + args_string; 50 | }, 51 | 52 | trigger_cb: function(cb_name, timestamp, result) { 53 | if (typeof result === 'string') { 54 | result = decodeURIComponent(result); 55 | } 56 | 57 | for (var i = 0; i < backend.callbacks.length; i += 1) { 58 | callback = backend.callbacks[i]; 59 | if (callback.func === cb_name && 60 | callback.timestamp === timestamp) { 61 | callback.callback(result); 62 | backend.callbacks.splice(i, 1); 63 | return; 64 | } 65 | } 66 | console.log("callback not found!"); 67 | 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /libs/kano-python/includes/kano/python/python_test_helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef __PYTHON_TEST_HELPERS_H__ 2 | #define __PYTHON_TEST_HELPERS_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | std::string remove_character(const std::string &str, char rm_char, std::string replace_str = "") 13 | { 14 | std::string new_str; 15 | 16 | for (std::string::const_iterator it = str.cbegin(); it != str.cend(); ++it) { 17 | if (strncmp(&*it, &rm_char, 1) == 0) { 18 | new_str += replace_str; 19 | } else { 20 | new_str += *it; 21 | } 22 | } 23 | 24 | return new_str; 25 | } 26 | 27 | 28 | void strip(std::string &tuple) 29 | { 30 | std::vector invalid_chars = {'(', ' ', ')', '\n'}; 31 | 32 | for (std::vector::iterator it = invalid_chars.begin(); 33 | it != invalid_chars.end(); ++it) { 34 | tuple = remove_character(tuple, *it); 35 | } 36 | } 37 | 38 | 39 | std::string run_python_cmd(const std::string module, const std::string function, const std::string args = "") 40 | { 41 | 42 | std::string cmd = "python -c '"; 43 | 44 | if (!module.empty()) 45 | cmd += "from " + module + " import " + function + "; "; 46 | 47 | cmd += "print(" + 48 | function + "(" + remove_character(args, '\'', "\"") + ")" 49 | ")'"; 50 | 51 | std::shared_ptr cmd_pipe(popen(cmd.c_str(), "r"), pclose); 52 | 53 | if (!cmd_pipe) 54 | return "ERROR"; 55 | 56 | char buffer[128]; 57 | std::string result = ""; 58 | 59 | while (!feof(cmd_pipe.get())) { 60 | if (fgets(buffer, sizeof(buffer), cmd_pipe.get()) != NULL) 61 | result += buffer; 62 | } 63 | 64 | strip(result); 65 | 66 | return result; 67 | } 68 | 69 | 70 | std::string get_tuple_item(const std::string &tuple, const int idx) 71 | { 72 | size_t pos = 0; 73 | size_t prev_pos = pos; 74 | int i = 0; 75 | 76 | while (i <= idx && pos != std::string::npos) { 77 | prev_pos = pos; 78 | pos = tuple.find(',', pos) + 1; 79 | i++; 80 | } 81 | 82 | if (i < idx) 83 | return ""; 84 | 85 | if (pos == std::string::npos) 86 | pos = tuple.size(); 87 | 88 | if (pos == 0) 89 | pos = tuple.find(','); 90 | 91 | return tuple.substr(prev_pos, pos - (prev_pos + 1)); 92 | } 93 | 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /kano/logging.py: -------------------------------------------------------------------------------- 1 | # kano.logging 2 | # 3 | # Copyright (C) 2014-2019 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | 7 | ''' 8 | This module provides the core Kano Logging functionality 9 | However it is actually a lazy loader for kano._logging 10 | ''' 11 | 12 | import sys 13 | 14 | # The way this module works is that the 'kano.logging' entry in sys.modules 15 | # gets substituted twice: 16 | # First, with the loader class below 17 | # When an attribute on that class is accessed, it gets substituted a second 18 | # time. 19 | 20 | 21 | class lazy_logger: 22 | def __getattr__(self, name): 23 | import kano._logging 24 | return getattr(kano._logging.logger, name) 25 | 26 | 27 | class loader: 28 | ll = lazy_logger 29 | 30 | def __init__(self, old_mod): 31 | self.old_mod = old_mod 32 | 33 | def __getattr__(self, name): 34 | # avoid importing the module just to make a logger object 35 | if name == '__path__': 36 | return getattr(self.old_mod, name) 37 | 38 | if name == 'logger': 39 | return self.ll() 40 | 41 | # load the real module 42 | # at this point we have substituted the module once, 43 | # so we need to import sys again 44 | import sys 45 | import kano._logging # noqa 46 | 47 | lm = sys.modules['kano._logging'] 48 | sys.modules[__name__] = lm 49 | return getattr(lm, name) 50 | 51 | 52 | def log_excepthook(exc_class, exc_value, tb): 53 | # at this point we have substituted the module once, 54 | # so we need to import all modules again 55 | import traceback 56 | import kano.logging # Don't think about this one too hard... 57 | import sys 58 | 59 | tb_txt = ''.join(traceback.format_tb(tb)) 60 | try: 61 | (filename, number, function, line_text) = traceback.extract_tb(tb)[-1] 62 | exc_txt = "{} line {} function {} [{}]".format( 63 | filename, number, function, line_text) 64 | except Exception: 65 | exc_txt = "" 66 | 67 | kano.logging.logger.error( 68 | "Unhandled exception '{}' at {} (see logfile for full trace)".format( 69 | exc_value, exc_txt 70 | ), 71 | traceback=tb_txt, 72 | exc_class=str(exc_class), 73 | exc_value=str(exc_value) 74 | ) 75 | sys.__excepthook__(exc_class, exc_value, tb) 76 | 77 | 78 | sys.excepthook = log_excepthook 79 | 80 | # substitute the lazy loader module 81 | sys.modules[__name__] = loader(sys.modules[__name__]) 82 | -------------------------------------------------------------------------------- /bin/kano-network-hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # kano-network-hook 4 | # 5 | # Copyright (C) 2015-2017 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # This script is called from the dhcpcd-hooks scripts, 9 | # to send the RaspberryPI CPU Serial Number to Kano. 10 | # It runs as the superuser. 11 | # 12 | 13 | import os 14 | import sys 15 | import time 16 | 17 | from kano.logging import logger 18 | from kano.utils import get_cpu_id, get_rpi_model, run_cmd 19 | 20 | def send_cpu_id(): 21 | sent=False 22 | 23 | # This is a loose dependency as we can't make kano-toolset 24 | # depend on profile. 25 | try: 26 | from kano_profile.tracking_events import generate_event 27 | generate_event('ping') 28 | except Exception as e: 29 | logger.error("Error sending the ping event ({})".format(e)) 30 | 31 | # Get the unit CPU Serial number and send it, save a local copy 32 | cpuid_filename = '/etc/cpuid-{}'.format(get_cpu_id()) 33 | if os.path.isfile(cpuid_filename): 34 | # We have already sent the CPU ID 35 | pass 36 | else: 37 | try: 38 | # The code below is the legacy way of tracking cpuids 39 | # TODO: To be removed when the new way is known to be reliable 40 | 41 | # Save a trace of the CPUID so we dont send it over again 42 | # The UTC timestamp is embedded inside the cpuid file as well 43 | utc_time=time.asctime(time.gmtime(time.time())) 44 | with open(cpuid_filename, 'a') as f: 45 | os.utime(cpuid_filename, None) 46 | f.write('RaspberryPI model: {}\n{}\n'.format(get_rpi_model(), utc_time)) 47 | 48 | # Send this file over the network 49 | cmdline='curl --retry 5 --retry-delay 3 -T "{}" ' \ 50 | '--user "cpuname:mko821fgx" http://cpuids.kano.me//cpuserialnos/'.format(cpuid_filename) 51 | _, _,rc = run_cmd(cmdline) 52 | assert (rc==0) 53 | sent=True 54 | except: 55 | if os.path.isfile(cpuid_filename): os.unlink(cpuid_filename) 56 | logger.error('Error sending Kit serial number') 57 | 58 | return sent 59 | 60 | if __name__ == '__main__': 61 | rc = 1 62 | 63 | # A DHCP REBOOT event is an indication that the Internet should be reachable 64 | if len(sys.argv) > 1 and sys.argv[1] == 'REBOOT': 65 | 66 | if send_cpu_id() == True: 67 | logger.info('Kit serial number has been sent') 68 | rc=0 69 | 70 | exit (rc) 71 | -------------------------------------------------------------------------------- /tests/fixtures/env/env_user: -------------------------------------------------------------------------------- 1 | XDG_VTNR=7 2 | XDG_SESSION_ID=c4 3 | SSH_AGENT_PID=1354 4 | XDG_GREETER_DATA_DIR=/var/lib/lightdm/data/username 5 | SAL_USE_VCLPLUGIN=gtk 6 | XDG_MENU_PREFIX=lxde- 7 | TERM=screen-256color 8 | SHELL=/bin/bash 9 | SSH_TTY=/dev/pts/2 10 | SDL_VIDEODRIVER=rpi 11 | USER=username 12 | LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36: 13 | XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0 14 | XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0 15 | SSH_AUTH_SOCK=/tmp/ssh-scIaEqHjXbPi/agent.1278 16 | DESKTOP_SESSION=lightdm-xsession 17 | PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games 18 | PWD=/home/username 19 | LANG=en_US.UTF-8 20 | NODE_PATH=/usr/lib/nodejs:/usr/lib/node_modules:/usr/share/javascript 21 | GDMSESSION=lightdm-xsession 22 | XDG_SEAT=seat0 23 | SHLVL=1 24 | HOME=/home/username 25 | XDG_CONFIG_HOME=/home/username/.config 26 | LOGNAME=username 27 | XDG_DATA_DIRS=/usr/local/share/:/usr/share/:/usr/share/gdm/:/var/lib/menu-xdg/ 28 | DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-bwjR2aKVhW,guid=fa5f34838f8d400d7929ff835a38e72b 29 | LC_CTYPE=en_US.UTF-8 30 | SSH_CONNECTION=10.0.0.111 62304 10.0.30.176 22 31 | DISPLAY=:0 32 | XDG_RUNTIME_DIR=/run/user/1001 33 | XDG_CURRENT_DESKTOP=LXDE 34 | XAUTHORITY=/home/username/.Xauthority 35 | _=/usr/bin/env 36 | -------------------------------------------------------------------------------- /tests/utils/test_user.py: -------------------------------------------------------------------------------- 1 | # 2 | # test_user.py 3 | # 4 | # Copyright (C) 2017 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | # 7 | # Tests for the kano.utils.user module 8 | # 9 | 10 | 11 | def test_get_user_pass(): 12 | pass 13 | 14 | 15 | def test_get_user(fake_user): 16 | from kano.utils.user import get_user 17 | 18 | user = get_user() 19 | 20 | if fake_user.sudo: 21 | assert user == 'root' 22 | else: 23 | assert user == fake_user.username 24 | 25 | 26 | def test_get_user_unsudoed(fake_user): 27 | from kano.utils.user import get_user_unsudoed 28 | 29 | assert get_user_unsudoed() == fake_user.username 30 | 31 | 32 | def test_get_home(fake_user, get_home_dir): 33 | from kano.utils.user import get_home 34 | 35 | assert get_home() == get_home_dir(fake_user.username) 36 | 37 | 38 | def test_get_home_by_username(fake_users, fake_pwd, get_home_dir): 39 | from kano.utils.user import get_home_by_username 40 | 41 | for user in fake_users.users: 42 | assert get_home_by_username(user) == get_home_dir(user) 43 | 44 | 45 | def test_get_all_home_folders(fake_users, get_home_dir): 46 | from kano.utils.user import get_all_home_folders 47 | home_dirs = get_all_home_folders() 48 | assert set(home_dirs) == set([ 49 | get_home_dir(username) for username in fake_users.users 50 | if username != 'root' 51 | ]) 52 | 53 | 54 | def test_get_all_home_folders_with_root(fake_users, get_home_dir): 55 | from kano.utils.user import get_all_home_folders 56 | home_dirs = get_all_home_folders(root=True) 57 | assert set(home_dirs) == set([ 58 | get_home_dir(username) for username in fake_users.users 59 | ]) 60 | 61 | 62 | def test_get_all_home_folders_with_skel(fake_users, get_home_dir): 63 | from kano.utils.user import get_all_home_folders 64 | home_dirs = get_all_home_folders(skel=True) 65 | assert set(home_dirs) == set([ 66 | get_home_dir(username) for username in fake_users.users 67 | if username != 'root' 68 | ] + ['/etc/skel']) 69 | 70 | 71 | def test_get_all_home_folders_with_root_skel(fake_users, get_home_dir): 72 | from kano.utils.user import get_all_home_folders 73 | home_dirs = get_all_home_folders(root=True, skel=True) 74 | assert set(home_dirs) == set([ 75 | get_home_dir(username) for username in fake_users.users 76 | ] + ['/etc/skel']) 77 | 78 | 79 | def test_enforce_root(fake_user, fake_sys_exit): 80 | from kano.utils.user import enforce_root 81 | enforce_root('Must be root') 82 | 83 | expect_quit = fake_user.username != 'root' and not fake_user.sudo 84 | assert fake_sys_exit.quit == expect_quit 85 | -------------------------------------------------------------------------------- /bin/kano-test-dhcp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # kano-test-dhcp 4 | # 5 | # Copyright (C) 2017 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # Simple test to make sure that various setup scripts have run on new DHCP hooks. 9 | # Searches the systemd journal for entries emitted by the network scripts. 10 | # 11 | # To run these tests: 12 | # Enable journalctl adding "Storage=persistent" to /etc/systemd/journalctl.conf 13 | # Reboot the kit 14 | # Run this program to expect 0 failures 15 | # 16 | 17 | ''' 18 | kano-test-dhcp Tests that network setup scripts have run correctly 19 | Usage: 20 | kano-tesst-dhcp 21 | kano-tesst-dhcp [--dump] 22 | 23 | Options: 24 | --dump Just dump journalctl logs 25 | 26 | ''' 27 | 28 | import docopt 29 | import os 30 | import sys 31 | 32 | failures=0 33 | 34 | # Patterns to test on a successful network on boot 35 | test_patterns_boot=[ 36 | 'REBOOT event', 37 | 'launching network up scripts', 38 | 'Started /usr/bin/kano-sentry-startup', 39 | 'Started /usr/bin/kano-network-hook', 40 | 'Started /usr/bin/kano-set-system-date', 41 | 'Ultimate parental control', 42 | 'Started /usr/bin/kano-dashboard-sysupdates', 43 | 'dashboard-sysupdates: updates check starting', 44 | 'Time has been changed', 45 | 'info rdate SUCCESS' 46 | ] 47 | 48 | 49 | def testlog(logs, entry, verbose=False): 50 | ''' 51 | Tests that a text entry is registered in the logs 52 | ''' 53 | global failures 54 | 55 | for line in logs: 56 | if entry in line: 57 | if verbose: 58 | print 'PASS: {}'.format(line), 59 | return True 60 | 61 | failures += 1 62 | print 'FAIL: {}'.format(entry) 63 | 64 | 65 | if __name__ == '__main__': 66 | 67 | args = docopt.docopt(__doc__) 68 | 69 | # Permissions check 70 | if os.getuid() != 0: 71 | print 'You need root permissions' 72 | sys.exit(1) 73 | 74 | # Collect the journal from the current system bootup sequence alone 75 | output=os.popen('journalctl --boot=0 | grep "REBOOT" -A 60').readlines() 76 | if not len(output): 77 | # TODO: Teach "kano-dev" to do it for us 78 | print 'No logs found, did you enable journalctl?' 79 | print 'Set "Storage=persistent" in /etc/systemd/journalctl.conf' 80 | sys.exit(1) 81 | 82 | # Simply dump the logs and exit 83 | if args['--dump']: 84 | for line in output: 85 | print line, 86 | sys.exit(0) 87 | 88 | # Go through the test patterns and give a report 89 | for test in test_patterns_boot: 90 | testlog(output, test, verbose=True) 91 | 92 | print 'failures={}'.format(failures) 93 | sys.exit(failures > 0) 94 | -------------------------------------------------------------------------------- /kano/gtk3/apply_styles.py: -------------------------------------------------------------------------------- 1 | # 2 | # apply_styles.py 3 | # 4 | # Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | # 7 | # This is a function to apply the common styles across a window 8 | # 9 | 10 | import os 11 | import sys 12 | 13 | from gi import require_version 14 | require_version('Gtk', '3.0') 15 | 16 | from gi.repository import Gtk, Gdk 17 | from kano.paths import common_css_dir 18 | 19 | 20 | # Apply the general CSS files to the screen 21 | def apply_common_to_screen(): 22 | apply_colours_to_screen() 23 | apply_base_to_screen() 24 | 25 | 26 | # This applies the colour variable names to the screen 27 | def apply_colours_to_screen(): 28 | apply_styling_to_screen(common_css_dir + "/colours.css", "APPLICATION") 29 | 30 | 31 | # This applies the base styling of the widgets to the screen 32 | def apply_base_to_screen(): 33 | apply_styling_to_screen(common_css_dir + "/widgets.css", "APPLICATION") 34 | 35 | 36 | # Apply the styling from a filename to the screen 37 | def apply_styling_to_screen(css_file, priority="USER"): 38 | css = Gtk.CssProvider() 39 | 40 | if not os.path.exists(css_file): 41 | sys.exit(css_file + ' CSS file missing!') 42 | 43 | css.load_from_path(css_file) 44 | 45 | screen = Gdk.Screen.get_default() 46 | styleContext = Gtk.StyleContext() 47 | 48 | if priority == "FALLBACK": 49 | gtk_priority = Gtk.STYLE_PROVIDER_PRIORITY_FALLBACK 50 | elif priority == "THEME": 51 | gtk_priority = Gtk.STYLE_PROVIDER_PRIORITY_THEME 52 | elif priority == "SETTINGS": 53 | gtk_priority = Gtk.STYLE_PROVIDER_PRIORITY_SETTINGS 54 | elif priority == "APPLICATION": 55 | gtk_priority = Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION 56 | elif priority == "USER": 57 | gtk_priority = Gtk.STYLE_PROVIDER_PRIORITY_USER 58 | 59 | styleContext.add_provider_for_screen(screen, css, gtk_priority) 60 | 61 | 62 | # Apply the styling from a CSS file to a specific widget 63 | def apply_styling_to_widget(widget, path): 64 | 65 | if not os.path.exists(path): 66 | sys.exit('{} CSS file missing!'.format(path)) 67 | 68 | provider = Gtk.CssProvider() 69 | provider.load_from_path(path) 70 | styleContext = widget.get_style_context() 71 | styleContext.add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) 72 | 73 | 74 | # Apply the colour variable names to the widget (useful if you want to refer to kano_green) 75 | def apply_colours_to_widget(widget): 76 | path = os.path.join(common_css_dir, "colours.css") 77 | apply_styling_to_widget(widget, path) 78 | 79 | 80 | # Apply the general styling of all the widgets to the widget (TODO: is this needed?) 81 | def apply_base_to_widget(widget): 82 | path = os.path.join(common_css_dir, "common.css") 83 | apply_styling_to_widget(widget, path) 84 | -------------------------------------------------------------------------------- /kano/gtk3/labelled_entries.py: -------------------------------------------------------------------------------- 1 | # 2 | # labelled_entries.py 3 | # 4 | # Copyright (C) 2014-2019 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | # 7 | # Template for creating a list of labelled entries 8 | # 9 | 10 | from gi import require_version 11 | require_version('Gtk', '3.0') 12 | 13 | from gi.repository import Gtk 14 | 15 | 16 | class LabelledEntries(Gtk.Alignment): 17 | 18 | def __init__(self, entries_info): 19 | Gtk.Alignment.__init__(self) 20 | 21 | self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 22 | self.entries = [] 23 | 24 | # entries_info = [{"heading": "", "subheading": ""}, {"heading": "", "subheading": ""}] 25 | for info in entries_info: 26 | entry = Gtk.Entry() 27 | align = create_labelled_widget(info["heading"], info["subheading"], entry) 28 | self.entries.append(entry) 29 | self.box.pack_start(align, False, False, 5) 30 | 31 | self.add(self.box) 32 | 33 | def get_entries(self): 34 | return self.entries 35 | 36 | def get_entry(self, number): 37 | return self.entries[number] 38 | 39 | def get_entry_text(self): 40 | all_text = [] 41 | 42 | for entry in self.entries: 43 | text = entry.get_text() 44 | all_text.append(text) 45 | 46 | return all_text 47 | 48 | def set_spacing(self, number): 49 | self.box.set_spacing(number) 50 | 51 | 52 | def add_heading(text, widget, bold=False): 53 | 54 | label = Gtk.Label(text) 55 | label_alignment = Gtk.Alignment(xscale=0, xalign=0) 56 | label_alignment.add(label) 57 | 58 | if bold: 59 | label.get_style_context().add_class("bold_label") 60 | else: 61 | label.get_style_context().add_class("desc_label") 62 | 63 | box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 64 | box.pack_start(label_alignment, False, False, 3) 65 | box.pack_start(widget, False, False, 3) 66 | 67 | return box 68 | 69 | 70 | def create_custom_label(heading, description=""): 71 | 72 | heading_label = Gtk.Label(heading) 73 | heading_label.get_style_context().add_class("bold_label") 74 | box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 75 | box.pack_start(heading_label, False, False, 0) 76 | 77 | if description: 78 | description_label = Gtk.Label(description) 79 | description_label.get_style_context().add_class("desc_label") 80 | box.pack_start(description_label, False, False, 0) 81 | 82 | align = Gtk.Alignment(yscale=0, yalign=0.5) 83 | align.add(box) 84 | 85 | return align 86 | 87 | 88 | def create_labelled_widget(heading, description="", widget=None): 89 | 90 | label_box = create_custom_label(heading, description) 91 | box = Gtk.Box() 92 | box.pack_start(label_box, False, False, 5) 93 | box.pack_start(widget, False, False, 5) 94 | 95 | align = Gtk.Alignment(xscale=0, xalign=1) 96 | align.add(box) 97 | 98 | return align 99 | -------------------------------------------------------------------------------- /TRANSLATION.md: -------------------------------------------------------------------------------- 1 | # Translation 2 | 3 | Translation files are found in the `po/` directory. 4 | 5 | ## i18n not released yet 6 | 7 | Kano OS is not fully i18n-aware and locales are not installed for end users, yet. You can translate this application, but as of now, users will still see the default English message strings. 8 | 9 | ## How to add a new translation 10 | 11 | In this example, we're going to add a French translation: 12 | 13 | # install your target locale `fr_FR.utf8` with: 14 | sudo dpkg-reconfigure locales 15 | 16 | cd po/ 17 | # create messages.pot 18 | make messages 19 | 20 | # create fr.po from messages.pot: 21 | msginit -l fr_FR.utf8 22 | 23 | # now use your favourite editor to translate fr.po 24 | 25 | # build locale files: 26 | make 27 | 28 | cd .. 29 | 30 | ## How to make sure your code is i18n-aware 31 | 32 | Add the gettext `_()` macro to all the user-visible message strings in your Python. List the Python source files that contain message strings in `PYPOTFILES`. 33 | 34 | If you added new message strings or made changes to existing ones, do `make messages` to keep the template file up-to-date. 35 | 36 | After that, merge the existing translations with `make update` and ask your translators to update their translations. 37 | 38 | ## gettext explained (in 20 seconds) 39 | 40 | * User-visible strings in the source are marked with a macro of your choice. Usually, it's `_()`. 41 | * `xgettext` extracts these message strings from your sources and puts them into a template file. 42 | * This template file, usually named `messages.pot`, contains all user-visible strings of the project, but no translations. 43 | * Translators use `msginit` to copy the template file into a new *portable object* file for their language (explained above). 44 | * The translations are put into `.po`. It's a plain-text file format, you can use any text editor. 45 | * More convenient, specialized `.po`-editors and web-based tools such as Pootle exist, as well. 46 | * If your template file changes, use `msgmerge` to merge your existing translations with the new template, then re-translate the updated messages. Beware of `msgmerge`'s "fuzzy" matches. 47 | * `msgfmt` converts a `.po` file into a binary *message object* file. 48 | * You don't link these `.mo` files with your application binary. 49 | * The `.mo` files are bundled alongside with your software as part of the distribution package. 50 | * During installation, the `.mo` files are copied into the system's locale directory, usually `/usr/share/locale`. 51 | * On startup, your application will look for the message object file that it needs for the current system locale. 52 | * The locale even allows you to provide region-specific translations, e.g. "colour" for en_UK vs "color" for en_US. 53 | * At runtime, all user-visible strings are being replaced with the translations. 54 | * If no message object was found for the system locale, the original strings will be shown. 55 | 56 | ## To-Do 57 | 58 | Pootle or Transifex integration. 59 | -------------------------------------------------------------------------------- /libs/kano-python/src/python_helpers.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * python_helpers.cpp 3 | * 4 | * Copyright (C) 2016 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | * 7 | * Provide functions to assist calling Python modules 8 | */ 9 | 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "kano/python/python_helpers.h" 16 | 17 | 18 | /** 19 | * For simplicity add this function which does nothing 20 | */ 21 | PyObject *new_pyobject(PyObject *o) 22 | { 23 | return o; 24 | } 25 | 26 | 27 | PyObject *new_pyobject(int o) 28 | { 29 | return new_pyobject(static_cast(o)); 30 | } 31 | 32 | 33 | PyObject *new_pyobject(long o) 34 | { 35 | return PyInt_FromLong(o); 36 | } 37 | 38 | 39 | PyObject *new_pyobject(double o) 40 | { 41 | return PyFloat_FromDouble(o); 42 | } 43 | 44 | 45 | PyObject *new_pyobject(bool o) 46 | { 47 | if (o) { 48 | Py_RETURN_TRUE; 49 | } else { 50 | Py_RETURN_FALSE; 51 | } 52 | } 53 | 54 | 55 | PyObject *new_pyobject(std::string o) 56 | { 57 | return PyString_FromString(o.c_str()); 58 | } 59 | 60 | 61 | PyObject *new_pyobject(char *o) 62 | { 63 | return PyString_FromString(o); 64 | } 65 | 66 | 67 | Binding::Binding(const std::string module_str): 68 | module_name(module_str) 69 | { 70 | if (Py_IsInitialized() == 0) 71 | Py_InitializeEx(0); 72 | 73 | PyGILState_STATE state = PyGILState_Ensure(); 74 | 75 | this->module = PyImport_ImportModule(this->module_name.c_str()); 76 | 77 | if (module == NULL) { 78 | std::cout << "Error: Module couldn't be imported\n"; 79 | } 80 | 81 | PyGILState_Release(state); 82 | } 83 | 84 | 85 | Binding::~Binding() 86 | { 87 | Py_CLEAR(this->module); 88 | } 89 | 90 | 91 | /** 92 | * Run a function in the object's module 93 | * 94 | * Note: Steals the reference to `args` so that (like `PyTuple_SetItem`) it can 95 | * be called as `run_func("function_name", new_tuple(arg1, arg2, ...))` 96 | */ 97 | PyObject *Binding::run_func(const std::string function_name, PyObject *args) const 98 | { 99 | if (this->module == NULL) { 100 | std::cout << "Error: Module couldn't be imported\n"; 101 | return NULL; 102 | } 103 | 104 | PyObject *func = PyObject_GetAttrString( 105 | this->module, function_name.c_str() 106 | ); 107 | 108 | if (func == NULL) { 109 | std::cout << "Error: Function couldn't be found\n"; 110 | return NULL; 111 | } 112 | 113 | if (args == NULL) 114 | args = PyTuple_New(0); 115 | 116 | PyObject *ret = PyObject_CallObject(func, args); 117 | 118 | if (ret == Py_None) { 119 | Py_CLEAR(ret); 120 | } 121 | 122 | Py_CLEAR(func); 123 | Py_CLEAR(args); 124 | 125 | return ret; 126 | } 127 | 128 | 129 | /** 130 | * Deallocate and stop the Python interpreters. 131 | */ 132 | void Binding::finalise() 133 | { 134 | Py_Finalize(); 135 | } 136 | -------------------------------------------------------------------------------- /tests/test_webapp.py: -------------------------------------------------------------------------------- 1 | # 2 | # test_webapp.py 3 | # 4 | # Copyright (C) 2017 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | # 7 | # Tests for the WebApp browser 8 | # 9 | 10 | 11 | import os 12 | import pytest 13 | 14 | 15 | @pytest.mark.gtk 16 | def test_navigation_requests(monkeypatch, webapp, navigation_action): 17 | ''' 18 | Ensures that custom navigation requests to the webapp are parsed correctly. 19 | 20 | Does this by replacing the functions that would be called for the navigation 21 | and ensuring that the correct function is called with the correct arguments 22 | ''' 23 | 24 | assert navigation_action.is_api_call() \ 25 | or navigation_action.is_scheme_call() \ 26 | or navigation_action.is_web_call(), \ 27 | 'The sample request is not valid' 28 | 29 | uri = navigation_action.get_original_uri() 30 | 31 | func_called = { 32 | 'fake_os_system': False, 33 | 'fake_func': False 34 | } 35 | 36 | if navigation_action.is_api_call(): 37 | ''' 38 | The API receives a function name and arguments which it is supposed to 39 | call. Create a fake function to be called to ensure that this is working 40 | correctly. 41 | ''' 42 | 43 | data = webapp._parse_api_call(uri) 44 | func = data[0] 45 | call_args = () 46 | if len(data) >= 3: 47 | call_args = data[2] 48 | 49 | def fake_func(*args, **kwargs): 50 | ''' 51 | Stub function to ensure that the API call does the right thing 52 | ''' 53 | 54 | func_called['fake_func'] = True 55 | 56 | if not navigation_action.is_api_call(): 57 | assert False, 'Function should only be called for API URIs' 58 | 59 | assert args == call_args, 'API function call arguments incorrect' 60 | 61 | monkeypatch.setattr(webapp, func, fake_func, raising=False) 62 | 63 | 64 | def fake_os_system(*args, **kwargs): 65 | ''' 66 | Stub function to ensure that the scheme calls do the right thing 67 | ''' 68 | func_called['fake_os_system'] = True 69 | 70 | if not navigation_action.is_scheme_call(): 71 | assert False, 'Funciton should only be called for scheme URIs' 72 | 73 | assert \ 74 | args[0] == 'systemd-run --user /usr/bin/xdg-open {}'.format(uri), \ 75 | 'Scheme launch command is incorrect' 76 | 77 | 78 | monkeypatch.setattr(os, 'system', fake_os_system) 79 | webapp._nav_req_handler( 80 | view=webapp._view, 81 | frame=None, 82 | request=None, 83 | action=navigation_action, 84 | decision=None, 85 | data=None 86 | ) 87 | 88 | assert \ 89 | func_called['fake_os_system'] == navigation_action.is_scheme_call(), \ 90 | 'Scheme handling function did not run when it should have' 91 | assert func_called['fake_func'] == navigation_action.is_api_call(), \ 92 | 'API handling function did not run when it should have' 93 | -------------------------------------------------------------------------------- /kano/utils/shell.py: -------------------------------------------------------------------------------- 1 | # shell.py 2 | # 3 | # Copyright (C) 2014-2016 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Utilities related to shell and running commands 7 | 8 | 9 | import os 10 | import sys 11 | import signal 12 | import subprocess 13 | 14 | 15 | def restore_signals(): 16 | signals = ('SIGPIPE', 'SIGXFZ', 'SIGXFSZ') 17 | for sig in signals: 18 | if hasattr(signal, sig): 19 | signal.signal(getattr(signal, sig), signal.SIG_DFL) 20 | 21 | 22 | def run_cmd(cmd, localised=False, unsudo=False): 23 | ''' 24 | Executes cmd, returning stdout, stderr, return code 25 | if localised is False, LC_ALL will be set to "C" 26 | ''' 27 | env = os.environ.copy() 28 | if not localised: 29 | env['LC_ALL'] = 'C' 30 | 31 | if unsudo and \ 32 | 'SUDO_USER' in os.environ and \ 33 | os.environ['SUDO_USER'] != 'root': 34 | cmd = "sudo -u {} bash -c '{}' ".format(os.environ['SUDO_USER'], cmd) 35 | 36 | process = subprocess.Popen(cmd, shell=True, env=env, 37 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, 38 | preexec_fn=restore_signals) 39 | 40 | stdout, stderr = process.communicate() 41 | returncode = process.returncode 42 | return stdout, stderr, returncode 43 | 44 | 45 | def run_cmd_log(cmd, localised=False, unsudo=False): 46 | ''' 47 | Wrapper against run_cmd but Kano Logging executuion and return code 48 | ''' 49 | 50 | from kano.logging import logger 51 | 52 | out, err, rv = run_cmd(cmd, localised, unsudo) 53 | logger.info("Command: {}".format(cmd)) 54 | 55 | if len(out.strip()) > 0: 56 | logger.info(out) 57 | 58 | if len(err.strip()) > 0: 59 | logger.error(err) 60 | 61 | logger.info("Return value: {}".format(rv)) 62 | 63 | return out, err, rv 64 | 65 | 66 | def run_bg(cmd, localised=False, unsudo=False): 67 | ''' 68 | Starts cmd program in the background 69 | ''' 70 | env = os.environ.copy() 71 | if not localised: 72 | env['LC_ALL'] = 'C' 73 | 74 | if unsudo and \ 75 | 'SUDO_USER' in os.environ and \ 76 | os.environ['SUDO_USER'] != 'root': 77 | cmd = "sudo -u {} bash -c '{}' ".format(os.environ['SUDO_USER'], cmd) 78 | 79 | s = subprocess.Popen(cmd, shell=True, env=env) 80 | return s 81 | 82 | 83 | def run_term_on_error(cmd, localised=False): 84 | o, e, rc = run_cmd(cmd, localised) 85 | if e: 86 | sys.exit( 87 | '\nCommand:\n{}\n\nterminated with error:\n{}' 88 | .format(cmd, e.strip()) 89 | ) 90 | return o, e, rc 91 | 92 | 93 | def run_print_output_error(cmd, localised=False): 94 | o, e, rc = run_cmd(cmd, localised) 95 | if o or e: 96 | print '\ncommand: {}'.format(cmd) 97 | if o: 98 | print 'output:\n{}'.format(o.strip()) 99 | if e: 100 | print '\nerror:\n{}'.format(e.strip()) 101 | return o, e, rc 102 | -------------------------------------------------------------------------------- /tests/fixtures/disk.py: -------------------------------------------------------------------------------- 1 | # 2 | # disk.py 3 | # 4 | # Copyright (C) 2018 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | # 7 | # Fixtures for fake disk utils 8 | # 9 | 10 | 11 | import imp 12 | import pytest 13 | 14 | 15 | DF_OUTPUT = ''' 16 | Filesystem 1K-blocks Used Available Use% Mounted on 17 | /dev/root 7399352 4011436 3021788 58% / 18 | '''.lstrip() 19 | DF_FREE_MB = 2950 20 | 21 | 22 | LSBLK_OUTPUT = ''' 23 | 7948206080 24 | 100663808 25 | 7834959872 26 | '''.lstrip() 27 | LSBLK_PARTITION_INFO = [ 28 | 7948206080, 29 | 100663808, 30 | 7834959872, 31 | ] 32 | 33 | 34 | @pytest.fixture(scope='function') 35 | def df(monkeypatch): 36 | ''' 37 | Mocks the output from `df`. 38 | 39 | Note: This fixture auto-reimports the `kano.utils.disk` module, which we 40 | expect to be the one which requires the patch, however depending on 41 | the module, it may be required to re-import the module being tested 42 | as this fixture patches `kano.utils.run_cmd` and if the tested module 43 | depends on this directly and has already been loaded then the updated 44 | version will not propagate. To re-import use: 45 | 46 | import imp 47 | import module.to.be.tested 48 | imp.reload(module.to.be.tested) 49 | 50 | ''' 51 | 52 | import kano.utils.shell 53 | 54 | def mock_run_cmd(cmd): 55 | if cmd.startswith('df'): 56 | return DF_OUTPUT, '', 0 57 | else: 58 | raise NotImplementedError( 59 | 'Command run is not df: {}'.format(cmd) 60 | ) 61 | 62 | patch = monkeypatch.setattr(kano.utils.shell, 'run_cmd', mock_run_cmd) 63 | import kano.utils.disk 64 | imp.reload(kano.utils.disk) 65 | return patch 66 | 67 | 68 | 69 | @pytest.fixture(scope='function') 70 | def lsblk(monkeypatch): 71 | ''' 72 | Mocks the output from `lsblk`. 73 | 74 | Note: This fixture auto-reimports the `kano.utils.disk` module, which we 75 | expect to be the one which requires the patch, however depending on 76 | the module, it may be required to re-import the module being tested 77 | as this fixture patches `kano.utils.run_cmd` and if the tested module 78 | depends on this directly and has already been loaded then the updated 79 | version will not propagate. To re-import use: 80 | 81 | import imp 82 | import module.to.be.tested 83 | imp.reload(module.to.be.tested) 84 | 85 | ''' 86 | 87 | import kano.utils.shell 88 | 89 | def mock_run_cmd(cmd): 90 | if cmd.startswith('lsblk'): 91 | return LSBLK_OUTPUT, '', 0 92 | else: 93 | raise NotImplementedError( 94 | 'Command run is not lsblk: {}'.format(cmd) 95 | ) 96 | 97 | patch = monkeypatch.setattr(kano.utils.shell, 'run_cmd', mock_run_cmd) 98 | import kano.utils.disk 99 | imp.reload(kano.utils.disk) 100 | 101 | return patch 102 | -------------------------------------------------------------------------------- /kano/utils/audio.py: -------------------------------------------------------------------------------- 1 | # audio.py 2 | # 3 | # Copyright (C) 2014-2016 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Utilities relating to audio 7 | 8 | 9 | import os 10 | 11 | from kano.utils.shell import run_cmd, run_bg, run_cmd_log 12 | 13 | 14 | def play_sound(audio_file, background=False, delay=0): 15 | from kano.logging import logger 16 | 17 | # Check if file exists 18 | if not os.path.isfile(audio_file): 19 | logger.error('audio file not found: {}'.format(audio_file)) 20 | return False 21 | 22 | _, extension = os.path.splitext(audio_file) 23 | 24 | if extension in ['.wav', '.voc', '.raw', '.au']: 25 | cmd = 'aplay -q {}'.format(audio_file) 26 | else: 27 | volume_percent = get_volume() 28 | volume_str = '--vol {}'.format( 29 | percent_to_millibel(volume_percent, raspberry_mod=True)) 30 | 31 | # Set the audio output between HDMI or Jack. Default is HDMI since it's the 32 | # safest route given the PiHat lib getting destabilised if Jack is used. 33 | audio_out = 'hdmi' 34 | try: 35 | from kano_settings.system.audio import is_HDMI 36 | if not is_HDMI(): 37 | audio_out = 'local' 38 | except Exception: 39 | pass 40 | 41 | cmd = 'omxplayer -o {audio_out} {volume} {link}'.format( 42 | audio_out=audio_out, 43 | volume=volume_str, 44 | link=audio_file 45 | ) 46 | 47 | logger.debug('cmd: {}'.format(cmd)) 48 | 49 | # Delay the sound playback if specified 50 | if delay: 51 | cmd = '/bin/sleep {} ; {}'.format(delay, cmd) 52 | 53 | if background: 54 | run_bg(cmd) 55 | rc = 0 56 | else: 57 | dummy, dummy, rc = run_cmd_log(cmd) 58 | 59 | return rc == 0 60 | 61 | 62 | def percent_to_millibel(percent, raspberry_mod=False): 63 | if not raspberry_mod: 64 | from math import log10 65 | 66 | multiplier = 2.5 67 | 68 | percent *= multiplier 69 | percent = min(percent, 100. * multiplier) 70 | percent = max(percent, 0.000001) 71 | 72 | millibel = 1000 * log10(percent / 100.) 73 | 74 | else: 75 | # special case for mute 76 | if percent == 0: 77 | return -11000 78 | 79 | min_allowed = -4000 80 | max_allowed = 400 81 | percent = percent / 100. 82 | millibel = min_allowed + (max_allowed - min_allowed) * percent 83 | 84 | return int(millibel) 85 | 86 | 87 | def get_volume(): 88 | from kano.logging import logger 89 | 90 | percent = 100 91 | 92 | cmd = "amixer | head -n 6 | grep -Po '(\d{1,3})(?=%)'" # noqa 93 | output, _, _ = run_cmd(cmd) 94 | 95 | try: 96 | percent = int(output.strip()) 97 | except Exception: 98 | msg = 'amixer format bad for percent, output: {}'.format(output) 99 | logger.error(msg) 100 | pass 101 | 102 | return percent 103 | 104 | 105 | def set_volume(percent): 106 | cmd = 'amixer set Master {}%'.format(percent) 107 | run_cmd(cmd) 108 | -------------------------------------------------------------------------------- /bin/kano-window-tool: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # kano-window-tool 4 | # 5 | # Copyright (C) 2013, 2014 Kano Computing Ltd. 6 | # License: GNU General Public License v2 http://www.gnu.org/licenses/gpl-2.0.txt 7 | # 8 | # A tool used to manipulate window properties 9 | # Call kano-window-tool --help for detailed options 10 | # 11 | 12 | import sys 13 | import re 14 | 15 | from argparse import ArgumentParser 16 | from kano.window import gdk_window_settings, find_window 17 | 18 | BOTTOM_BAR_HEIGHT = 39 19 | 20 | 21 | def parse_args(): 22 | app_desc = "A tool for manipulating window parameters and properties." 23 | parser = ArgumentParser(add_help=False, description=app_desc) 24 | 25 | parser.add_argument("--help", action="help", 26 | help="show this message and exit") 27 | 28 | id_group = parser.add_mutually_exclusive_group(required=True) 29 | id_group.add_argument("-p", "--pid", 30 | help="process id as an identifier of the window") 31 | id_group.add_argument("-t", "--title", 32 | help="window title as an identifier of the window") 33 | id_group.add_argument("-i", "--id", 34 | help="wm id as an identifier of the window") 35 | 36 | parser.add_argument("-w", "--width", type=float, metavar="W", 37 | help="set specific width") 38 | parser.add_argument("-h", "--height", type=float, metavar="H", 39 | help="set specific height") 40 | parser.add_argument("-x", "--x-pos", metavar="X", type=float, 41 | help="set X position of the window") 42 | parser.add_argument("-y", "--y-pos", metavar="Y", type=float, 43 | help="set Y position of the window") 44 | 45 | mode_group = parser.add_mutually_exclusive_group() 46 | mode_group.add_argument("-c", "--centered", action="store_true", 47 | help="launch the window centered") 48 | mode_group.add_argument("-m", "--maximized", action="store_true", 49 | help="launch the window maximized") 50 | 51 | parser.add_argument("-d", "--decoration", metavar="yes|no", 52 | help="enable/disable window decoration") 53 | parser.add_argument("-f", "--focus", action="store_true", 54 | help="set keyboard focus to the window") 55 | 56 | args = parser.parse_args() 57 | if hasattr(args, "decoration"): 58 | if re.match(r"[Yy]([Ee][Ss])?", args.decoration): 59 | args.decoration = True 60 | else: 61 | args.decoration = False 62 | 63 | return args 64 | 65 | 66 | def main(): 67 | args = parse_args() 68 | 69 | win = find_window(args.title, args.pid, args.id) 70 | if not win: 71 | sys.stderr.write("No window found from the information you provided.\n") 72 | return 1 73 | 74 | if args.focus: 75 | win.raise_() 76 | win.focus() 77 | 78 | # Do all the resizing at once 79 | gdk_window_settings(win, args.x_pos, args.y_pos, args.width, args.height, 80 | args.decoration, args.maximized, args.centered) 81 | 82 | return 0 83 | 84 | if __name__ == "__main__": 85 | sys.exit(main()) 86 | -------------------------------------------------------------------------------- /dhcpcd-hooks/65-kano-dhcp-hook: -------------------------------------------------------------------------------- 1 | # 65-kano-dhcp-hook 2 | # 3 | # Copyright (C) 2017 - 2018 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # DHCP hook script to prepare Kano OS settings when network comes up 7 | # To enable this hook for logging, edit /etc/systemd/journalctl.conf and set Storage=persistent 8 | # Reboot and execute "kano-test-dhcp". 9 | # 10 | # In case that a user is already connected to a network (either multihomed or from 11 | # previous reboot) then gracefully exit with success/0 code. 12 | 13 | # A lock file to inform for network connectivity 14 | monitor_file="/var/run/internet_monitor" 15 | 16 | # Network service manager logging 17 | logger "kano-dhcp-hook: info reason=$reason iface=$interface" 18 | 19 | # Check if the network is already up 20 | # through another interface/device 21 | is_connected () { 22 | if [ -f $monitor_file ]; then 23 | logger "kano-dhcp-hook: REBOOT hook discarded, already networked from a previous REBOOT" 24 | exit 0 25 | else 26 | echo "internet: up" > $monitor_file 27 | fi 28 | } 29 | 30 | # Initialise the network through the kano hook and dashboard 31 | # network wrappers and perform tracking synchronisation 32 | kano_network_init () { 33 | systemctl restart internet-available.target 34 | 35 | # TODO: move everything below to a systemd service linked to the above target 36 | systemd-run --service-type=forking /usr/bin/kano-sentry-startup $reason 37 | systemd-run /usr/bin/kano-set-system-date $reason 38 | systemd-run /usr/bin/kano-network-hook $reason 39 | } 40 | 41 | # Sync objects from the content API in the background 42 | # TODO: This should be removed once we have the daemon done 43 | kano_daemon () { 44 | kano-content sync & 45 | kano-tracker-ctl +1 'internet-connection-established' & 46 | } 47 | 48 | # Check for a multihomed connection keep connected if sucessfull 49 | # otherwise disconnect with a notifying through the logger 50 | is_multihomed () { 51 | is_internet 52 | if [ $? -eq 0 ]; then 53 | logger "kano-dhcp-hook: $reason but still connected, aborting action." 54 | exit 0 55 | else 56 | logger "kano-dhcp-hook: $reason and connectivity lost." 57 | rm $monitor_file 58 | fi 59 | } 60 | 61 | # In case of lease RENEWal or REBOUND to a new DHCP server 62 | # check if connection is sucesfully active and modifly 63 | # the monitor_file accordingly 64 | is_reconnected () { 65 | if [ $? -eq 0 ]; then 66 | logger "kano-dhcp-hook: $reason but still connected...Aborting." 67 | exit 0 68 | elif ([ $reason = "INFORM" ] && [ $if_up ]); then 69 | kano_network_init 70 | kano_daemon 71 | else 72 | logger "kano-dhcp-hook: $ifname down after $reason." 73 | rm $monitor_file 74 | fi 75 | } 76 | 77 | case "$reason" in 78 | REBOOT | BOUND) 79 | ip_addr=`ip addr show wlan0 | grep inet | awk '{print $2}' | sed -s 's/\/.*//')` 80 | logger "kano-dhcp-hook: REBOOT event ipaddr=$ip_addr on iface=$interface" 81 | is_connected 82 | logger "kano-dhcp-hook: launching network up scripts" 83 | kano_network_init 84 | kano_daemon 85 | ;; 86 | 87 | INFORM|RENEW|REBIND|RELEASE) 88 | is_internet 89 | is_reconnected 90 | ;; 91 | 92 | NOCARRIER|STOP|STOPPED) 93 | is_multihomed 94 | ;; 95 | esac 96 | -------------------------------------------------------------------------------- /tests/test_rpi_models.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | 3 | # test_rpi_models.py 4 | # 5 | # Copyright (C) 2015 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # This module tests the detection of all RaspberryPI models 9 | # 10 | 11 | import unittest 12 | 13 | import sys 14 | sys.path.append('../') 15 | 16 | import kano.utils 17 | from kano.utils import get_rpi_model as getrpi 18 | 19 | 20 | class TestRaspberryPIModels(unittest.TestCase): 21 | 22 | def test_model_A(self): 23 | modela='RPI/A' 24 | self.assertIn(getrpi('0007'), modela) 25 | self.assertIn(getrpi('0008'), modela) 26 | self.assertIn(getrpi('0009'), modela) 27 | 28 | def test_module_a_plus(self): 29 | self.assertEqual(getrpi('0012'), 'RPI/A+') 30 | 31 | def test_model_B(self): 32 | modelb='RPI/B' 33 | 34 | self.assertIn('Beta', getrpi('Beta')) 35 | 36 | self.assertEqual(getrpi('0002'), modelb) 37 | self.assertEqual(getrpi('0003'), modelb) 38 | self.assertEqual(getrpi('0004'), modelb) 39 | self.assertEqual(getrpi('0005'), modelb) 40 | self.assertEqual(getrpi('0006'), modelb) 41 | 42 | self.assertEqual(getrpi('000D'), modelb) 43 | self.assertEqual(getrpi('000E'), modelb) 44 | self.assertEqual(getrpi('000F'), modelb) 45 | 46 | def test_model_B_plus(self): 47 | self.assertEqual(getrpi('0010'), 'RPI/B+') 48 | self.assertEqual(getrpi('0013'), 'RPI/B+') 49 | 50 | def test_compute_module(self): 51 | self.assertEqual(getrpi('0011'), 'RPI/Compute') 52 | 53 | def test_model_rpi2(self): 54 | self.assertEqual(getrpi('A01041'), 'RPI/2/B') 55 | self.assertEqual(getrpi('A21041'), 'RPI/2/B') 56 | 57 | def test_model_zero(self): 58 | self.assertEqual(getrpi('900092'), 'RPI/Zero') 59 | 60 | 61 | class TestRaspberryPIOverclocked(unittest.TestCase): 62 | 63 | def test_overclocked_models(self): 64 | for m in range(7, 9): 65 | model_name = getrpi('100{}'.format(m)).split() 66 | self.assertIn ('RPI/A', model_name) 67 | 68 | # Model A+ overclocked 69 | model_aplus=getrpi('1012') 70 | self.assertIn ('RPI/A+', model_aplus) 71 | 72 | expected_model_b='RPI/B' 73 | for m in range(2, 6): 74 | model_name = getrpi('100{}'.format(m)).split() 75 | self.assertIn (expected_model_b, model_name) 76 | 77 | self.assertEqual(getrpi('100D'), expected_model_b) 78 | self.assertEqual(getrpi('100E'), expected_model_b) 79 | self.assertEqual(getrpi('100F'), expected_model_b) 80 | 81 | self.assertEqual(getrpi('1010'), 'RPI/B+') 82 | 83 | self.assertEqual(getrpi('1011'), 'RPI/Compute') 84 | 85 | class TestModelAsserters(unittest.TestCase): 86 | 87 | def test_models_asserts(self): 88 | self.assertTrue (kano.utils.is_model_a('0007')) 89 | self.assertTrue (kano.utils.is_model_b('0004')) 90 | self.assertTrue (kano.utils.is_model_b_plus('0010')) 91 | self.assertTrue (kano.utils.is_model_2_b('0xA01041')) 92 | 93 | class TestModelAsserters(unittest.TestCase): 94 | 95 | def test_unknown(self): 96 | self.assertIn ('unknown', getrpi('ABCD')) 97 | 98 | 99 | if __name__ == '__main__': 100 | unittest.main() 101 | -------------------------------------------------------------------------------- /bin/typewriter_echo: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # typewriter_echo 4 | # 5 | # Copyright (C) 2014 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # Write to terminal in a Matrix-style 9 | # 10 | # Params: 11 | # $1: Sring to be printed 12 | # $2: Wait time at the end of the line (optional) 13 | # $3: Number of line breaks at the end (optional) 14 | # $4: Whether there is padding at the start of the line (0, 1 or X) (optional) 15 | # $5: Whether there is padding at the start of the next line (1 or 0) (optional) 16 | # $6: Number of line breaks at the start (optional) 17 | # 18 | 19 | PADDING=3 20 | 21 | function rand_wait 22 | { 23 | local low=$1 # miliseconds 24 | local high=$2 # miliseconds 25 | local rv=`bc <<< "scale=4; $[($RANDOM % ($high - $low)) + $low] / 1000"` 26 | echo "0$rv" 27 | } 28 | 29 | function clean_stdin 30 | { 31 | read -t 1 -n 10000 discard 32 | } 33 | 34 | # params: 35 | # $1: Number of characters to use to pad 36 | function add_character_padding 37 | { 38 | if [ -z $1 ] || [ $1 -eq 0 ]; then 39 | echo "" 40 | return 41 | elif [ $1 -eq 1 ]; then 42 | pad=$PADDING 43 | else 44 | pad=$1 45 | fi 46 | string="" 47 | for (( i=0; i<=$pad ; i++ )); do 48 | string+=" " 49 | done 50 | echo "${string}" 51 | } 52 | 53 | function add_line_padding 54 | { 55 | for n in `seq 1 $1`; do 56 | linebreak 57 | done 58 | } 59 | 60 | function linebreak 61 | { 62 | echo -ne "\n" 63 | } 64 | 65 | # Main function 66 | function typewriter_echo 67 | { 68 | string=$(python /usr/bin/colours-cli "$1") 69 | 70 | # Amount of whitespace at used for padding 71 | whitespace=`add_character_padding $4` 72 | 73 | # $6: Number of line breaks at the start 74 | if [ -n "$6" ]; then 75 | add_line_padding "$6" 76 | else 77 | add_line_padding 0 78 | fi 79 | 80 | # $4: Whether there is padding at the start of the line (1 or 0) 81 | if [ -n "$4" ] && [ "$4" -ne 0 ]; then 82 | echo -n "$whitespace" 83 | fi 84 | 85 | # Write the text 86 | while test -n "$string"; do 87 | c=${string:0:1} 88 | echo -ne "$c" 89 | # New word 90 | if [ "$c" == " " ]; then 91 | sleep 0.06 92 | # End of sentence 93 | elif [ "$c" == "." ] || [ "$c" == "?" ]; then 94 | sleep 0.04 95 | # Character 96 | else 97 | sleep 0.02 98 | fi 99 | string=${string:1} 100 | done 101 | 102 | # $2: Wait time at the end of the line 103 | if [ -n "$2" ]; then 104 | sleep `bc <<<"scale=4; ($2 / 4)"` 105 | fi 106 | 107 | # $3: Number of line breaks at the end 108 | if [ -n "$3" ]; then 109 | add_line_padding "$3" 110 | else 111 | add_line_padding 1 112 | fi 113 | 114 | # $5: Whether there is padding at the start of the next line (1 or 0) 115 | if [ -n "$5" ] && [ "$5" -ne 0 ]; then 116 | echo -n "$whitespace" 117 | fi 118 | 119 | } 120 | 121 | # Disable terminal echo, so the user cannot type between the text 122 | stty_orig=`stty -g` 123 | stty -echo 124 | 125 | typewriter_echo "$1" "$2" "$3" "$4" "$5" "$6" 126 | 127 | # Clean input and enable terminal echo again 128 | clean_stdin 129 | stty $stty_orig 130 | -------------------------------------------------------------------------------- /media/CSS/kano_button.css: -------------------------------------------------------------------------------- 1 | /** 2 | * kano_button.css 3 | * 4 | * Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | * 7 | * The main button that updates the changes made in each level 8 | */ 9 | 10 | .kano_button { 11 | outline-width: 0; 12 | border-radius: 4px; 13 | border-color: transparent; 14 | border-width: 3px; 15 | box-shadow: inset 0 0, inset 0 0; 16 | outline-width: 0; 17 | } 18 | 19 | .kano_button_padding { 20 | padding: 6px 16px 6px 16px; 21 | } 22 | 23 | .kano_button label { 24 | color: #ffffff; 25 | font-family: Bariol Bold; 26 | font-size: 13pt; 27 | } 28 | 29 | .kano_button:disabled { 30 | background: #dddddd; 31 | } 32 | 33 | .kano_button label:disabled { 34 | color: #aaaaaa; 35 | background: #dddddd; 36 | } 37 | 38 | .loading_kano_button label:disabled { 39 | color: #ffffff; 40 | } 41 | 42 | .green_background { 43 | background: @kano_green; 44 | } 45 | 46 | button.green_background:active { 47 | background: @kano_green_darker; 48 | } 49 | 50 | .green_background:hover { 51 | background: @kano_green_lighter; 52 | } 53 | 54 | .green_background:focus { 55 | border-color: @kano_green_darker; 56 | border-style: solid; 57 | } 58 | 59 | /* When spinner is spinning */ 60 | 61 | .green_background.loading_kano_button:disabled { 62 | background: @kano_green_darker; 63 | } 64 | 65 | .red_background { 66 | background: @kano_red; 67 | } 68 | 69 | button.red_background:active { 70 | background: @kano_red_darker; 71 | } 72 | 73 | .red_background:hover { 74 | background: @kano_red_lighter; 75 | } 76 | 77 | .red_background:focus { 78 | border-color: @kano_red_darker; 79 | border-style: solid; 80 | } 81 | 82 | /* When spinner is spinning */ 83 | 84 | .red_background.loading_kano_button:disabled { 85 | background: @kano_red_darker; 86 | } 87 | 88 | .orange_background { 89 | background: @kano_orange; 90 | } 91 | 92 | button.orange_background:active { 93 | background: @kano_orange_darker; 94 | } 95 | 96 | .orange_background:hover { 97 | background: @kano_orange_lighter; 98 | } 99 | 100 | .orange_background:focus { 101 | border-color: @kano_orange_darker; 102 | border-style: solid; 103 | } 104 | 105 | /* When spinner is spinning */ 106 | 107 | .orange_background.loading_kano_button:disabled { 108 | background: @kano_orange_darker; 109 | } 110 | 111 | .grey_background { 112 | background: @kano_grey; 113 | } 114 | 115 | button.grey_background:active { 116 | background: @kano_grey_darker; 117 | } 118 | 119 | .grey_background:hover { 120 | background: @kano_grey_lighter; 121 | } 122 | 123 | .grey_background:focus { 124 | border-color: @kano_grey_darker; 125 | border-style: solid; 126 | } 127 | 128 | /* When spinner is spinning */ 129 | 130 | .grey_background.loading_kano_button:disabled { 131 | background: @kano_grey_darker; 132 | } 133 | 134 | .blue_background { 135 | background: @kano_blue; 136 | } 137 | 138 | button.blue_background:active { 139 | background: @kano_blue_darker; 140 | } 141 | 142 | .blue_background:hover { 143 | background: @kano_blue_lighter; 144 | } 145 | 146 | .blue_background:focus { 147 | border-color: @kano_blue_darker; 148 | border-style: solid; 149 | } 150 | 151 | /* When spinner is spinning */ 152 | 153 | .blue_background.loading_kano_button:disabled { 154 | background: @kano_blue_darker; 155 | } 156 | -------------------------------------------------------------------------------- /libs/parson/lib/json_helpers.h: -------------------------------------------------------------------------------- 1 | /** 2 | * json_helpers.h 3 | * 4 | * Copyright (C) 2016 Kano Computing Ltd. 5 | * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 6 | * 7 | * Simplifications to the Parson library 8 | * TODO: Fix install path 9 | * 10 | */ 11 | 12 | 13 | #ifndef __JSON_HELPERS_H__ 14 | #define __JSON_HELPERS_H__ 15 | 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | 23 | // ---------------------------------------------------------------- 24 | 25 | 26 | template 27 | inline T get_json_val(__attribute__((unused)) const JSON_Object *root, __attribute__((unused)) const std::string key) 28 | { 29 | return T(); 30 | } 31 | 32 | template <> 33 | inline double get_json_val(const JSON_Object *root, const std::string key) 34 | { 35 | return json_object_get_number(root, key.c_str()); 36 | } 37 | 38 | 39 | template <> 40 | inline int get_json_val(const JSON_Object *root, const std::string key) 41 | { 42 | return static_cast( 43 | get_json_val(root, key) 44 | ); 45 | } 46 | 47 | 48 | template <> 49 | inline std::string get_json_val(const JSON_Object *root, const std::string key) 50 | { 51 | const char *val = json_object_get_string(root, key.c_str()); 52 | 53 | if (!val) 54 | return ""; 55 | 56 | return std::string(val); 57 | } 58 | 59 | 60 | template <> 61 | inline bool get_json_val(const JSON_Object *root, const std::string key) 62 | { 63 | return json_object_get_boolean(root, key.c_str()) == 1; 64 | } 65 | 66 | 67 | // ---------------------------------------------------------------- 68 | 69 | 70 | template 71 | inline T get_json_val(__attribute__((unused)) const JSON_Value *root) 72 | { 73 | return T(); 74 | } 75 | 76 | 77 | template<> 78 | inline double get_json_val(const JSON_Value *node) 79 | { 80 | if (json_value_get_type(node) != JSONNumber) { 81 | std::cout << "JSON value isn't of string type\n"; 82 | return double(); 83 | } 84 | 85 | return json_value_get_number(node); 86 | } 87 | 88 | 89 | template <> 90 | inline int get_json_val(const JSON_Value *node) 91 | { 92 | return static_cast( 93 | get_json_val(node) 94 | ); 95 | } 96 | 97 | 98 | template<> 99 | inline std::string get_json_val(const JSON_Value *node) 100 | { 101 | if (json_value_get_type(node) != JSONString) { 102 | std::cout << "JSON value isn't of string type\n"; 103 | return std::string(); 104 | } 105 | 106 | return json_value_get_string(node); 107 | } 108 | 109 | 110 | template <> 111 | inline bool get_json_val(const JSON_Value *node) 112 | { 113 | return json_value_get_boolean(node) == 1; 114 | } 115 | 116 | 117 | // ---------------------------------------------------------------- 118 | 119 | 120 | template 121 | inline std::vector get_json_array(const JSON_Object *root, const std::string key) 122 | { 123 | std::vector vals; 124 | 125 | if (!root) { 126 | std::cout << "Root node is null\n"; 127 | return vals; 128 | } 129 | 130 | JSON_Value *node = json_object_get_value(root, key.c_str()); 131 | 132 | if (!node) { 133 | std::cout << "Invalid key (" << key << ") for JSON\n"; 134 | return vals; 135 | } 136 | 137 | if (json_value_get_type(node) != JSONArray) { 138 | std::cout << "Value for JSON key '" << key << "' is not an array.\n"; 139 | return vals; 140 | } 141 | 142 | JSON_Array *arr = json_value_get_array(node); 143 | size_t len = json_array_get_count(arr); 144 | 145 | for (size_t i = 0; i < len; i++) { 146 | JSON_Value * val = json_array_get_value(arr, i); 147 | vals.push_back(get_json_val(val)); 148 | } 149 | 150 | return vals; 151 | } 152 | 153 | 154 | /** 155 | * A safe version of `json_object_get_string` 156 | */ 157 | inline std::string get_json_string(const JSON_Object *root, const std::string key) 158 | { 159 | return get_json_val(root, key); 160 | } 161 | 162 | 163 | #endif 164 | -------------------------------------------------------------------------------- /tests/test_wifi_countries.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # test_wifi_countries.py 4 | # 5 | # Copyright (C) 2017 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 7 | # 8 | # This module tests Wireless support for country codes 9 | # 10 | 11 | 12 | import os 13 | import textwrap 14 | import subprocess 15 | import pytest 16 | 17 | 18 | TEST_NETWORK = { 19 | 'essid': 'testessid', 20 | 'psk': 'testpassphrase', 21 | 'wpa_passphrase': textwrap.dedent(''' 22 | network={ 23 | ssid="testessid" 24 | #psk="testpassphrase" 25 | psk=5a27f391c26458ddb9be0c347291a0ee1f419c41be9e48091cf0bc4e59b00813 26 | } 27 | ''').lstrip() 28 | } 29 | TEST_ESSID = 'testessid' 30 | TEST_CONFFILE = '/tmp/test_wifi_countries.conf' 31 | 32 | 33 | 34 | @pytest.fixture(scope='function') 35 | def get_wpa_conf(monkeypatch, fs): 36 | monkeypatch.setenv('KANO_WIFI_COUNTRY', '') 37 | monkeypatch.setenv('LANG', '') 38 | 39 | def fake_subprocess_check_output(cmd): 40 | if type(cmd) != list: 41 | return 42 | 43 | if cmd[0] == 'wpa_passphrase': 44 | return TEST_NETWORK.get('wpa_passphrase') 45 | 46 | 47 | def get_conf(): 48 | if not os.path.exists(TEST_CONFFILE): 49 | return None 50 | 51 | with open(TEST_CONFFILE) as conf_f: 52 | conf = conf_f.readlines() 53 | 54 | for line in conf: 55 | if line.find('country=') != -1: 56 | return line[line.find('country=') + 8:].strip() 57 | 58 | return None 59 | 60 | monkeypatch.setattr( 61 | subprocess, 'check_output', 62 | fake_subprocess_check_output 63 | ) 64 | 65 | conf_dir = os.path.dirname(TEST_CONFFILE) 66 | if not fs.Exists(conf_dir): 67 | fs.CreateDirectory(conf_dir) 68 | 69 | return get_conf 70 | 71 | 72 | 73 | def test_country_lang_argentina(monkeypatch, get_wpa_conf): 74 | ''' 75 | Mimic automatic detection for Argentinian locale 76 | ''' 77 | 78 | monkeypatch.setenv('LANG', 'es_AR.UTF-8') 79 | 80 | from kano import network 81 | assert network.wpa_conf( 82 | TEST_NETWORK.get('essid'), 83 | TEST_NETWORK.get('psk'), 84 | TEST_CONFFILE 85 | ) 86 | 87 | assert get_wpa_conf() == 'AR' 88 | 89 | 90 | def test_country_lang_spanish(monkeypatch, get_wpa_conf): 91 | ''' 92 | Mimic automatic detection for Spanish locale 93 | ''' 94 | 95 | monkeypatch.setenv('LANG', 'es_ES.UTF-8') 96 | 97 | from kano import network 98 | assert network.wpa_conf( 99 | TEST_NETWORK.get('essid'), 100 | TEST_NETWORK.get('psk'), 101 | TEST_CONFFILE 102 | ) 103 | 104 | assert get_wpa_conf() == 'ES' 105 | 106 | 107 | def test_country_custom(monkeypatch, get_wpa_conf): 108 | ''' 109 | Custom country code must override LANG automatic detection 110 | ''' 111 | 112 | monkeypatch.setenv('LANG', 'en_US.UTF-8') 113 | monkeypatch.setenv('KANO_WIFI_COUNTRY', 'es_ES.UTF-8') 114 | 115 | from kano import network 116 | assert network.wpa_conf( 117 | TEST_NETWORK.get('essid'), 118 | TEST_NETWORK.get('psk'), 119 | TEST_CONFFILE 120 | ) 121 | 122 | assert get_wpa_conf() == 'ES' 123 | 124 | 125 | def test_country_custom_malformed(monkeypatch, get_wpa_conf): 126 | ''' 127 | Invalid format should not set any country 128 | ''' 129 | 130 | monkeypatch.setenv('KANO_WIFI_COUNTRY', 'ez-ZZ-NOT-GOOD') 131 | 132 | from kano import network 133 | assert network.wpa_conf( 134 | TEST_NETWORK.get('essid'), 135 | TEST_NETWORK.get('psk'), 136 | TEST_CONFFILE 137 | ) 138 | 139 | assert get_wpa_conf() is None 140 | 141 | 142 | def test_country_lang_us(monkeypatch, get_wpa_conf): 143 | ''' 144 | For US lang, we do not enforce wireless country code 145 | ''' 146 | 147 | monkeypatch.setenv('LANG', 'en_US.UTF-8') 148 | 149 | from kano import network 150 | assert network.wpa_conf( 151 | TEST_NETWORK.get('essid'), 152 | TEST_NETWORK.get('psk'), 153 | TEST_CONFFILE 154 | ) 155 | 156 | assert get_wpa_conf() != 'US' 157 | -------------------------------------------------------------------------------- /kano/decorators.py: -------------------------------------------------------------------------------- 1 | # decorators.py 2 | # 3 | # Copyright (C) 2015-2019 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2 5 | # 6 | # Decorators used to simplify control and ease modularity 7 | 8 | 9 | import os 10 | import sys 11 | import time 12 | import math 13 | from functools import wraps 14 | 15 | from kano.logging import logger 16 | 17 | ERR_ROOT_PERMISSIONS_REQ = -1 18 | 19 | 20 | def require_root(exit_on_failure=False, verbose=False): 21 | ''' 22 | Generates decorator to enforce root permissions 23 | 24 | NB: must be called when used as decorator, i.e. 25 | @require_root() 26 | def my_func(): 27 | pass 28 | 29 | @params exit_on_failure Quit application on failure 30 | @params verbose Print messages to stdout 31 | ''' 32 | 33 | def require_root_decorator(func): 34 | ''' 35 | Actual decorator that gets applied to functions 36 | ''' 37 | 38 | @wraps(func) 39 | def ensure_root(*args, **kwargs): 40 | if os.getuid() != 0: 41 | msg = 'You need to run this option as root, try with sudo' 42 | logger.error(msg) 43 | 44 | if verbose: 45 | sys.stdout.write('{}\n'.format(msg)) 46 | 47 | if exit_on_failure: 48 | sys.exit(ERR_ROOT_PERMISSIONS_REQ) 49 | 50 | return False 51 | 52 | return func(*args, **kwargs) 53 | 54 | return ensure_root 55 | 56 | return require_root_decorator 57 | 58 | 59 | def retry(tries, delay=3, backoff=2): 60 | ''' 61 | Taken from the sample decorators at: 62 | https://wiki.python.org/moin/PythonDecoratorLibrary#Retry 63 | 64 | Copyright retained by owners of wiki.python.org 65 | 66 | Retries a function or method until it returns True. 67 | 68 | delay sets the initial delay in seconds, and backoff sets the factor by which 69 | the delay should lengthen after each failure. backoff must be greater than 1, 70 | or else it isn't really a backoff. tries must be at least 0, and delay 71 | greater than 0.''' 72 | 73 | if backoff <= 1: 74 | raise ValueError("backoff must be greater than 1") 75 | 76 | tries = math.floor(tries) 77 | if tries < 0: 78 | raise ValueError("tries must be 0 or greater") 79 | 80 | if delay <= 0: 81 | raise ValueError("delay must be greater than 0") 82 | 83 | def deco_retry(f): 84 | def f_retry(*args, **kwargs): 85 | mtries, mdelay = tries, delay # make mutable 86 | 87 | rv = f(*args, **kwargs) # first attempt 88 | while mtries > 0: 89 | if rv is True: # Done on success 90 | return True 91 | 92 | mtries -= 1 # consume an attempt 93 | time.sleep(mdelay) # wait... 94 | mdelay *= backoff # make future wait longer 95 | 96 | rv = f(*args, **kwargs) # Try again 97 | 98 | return False # Ran out of tries :-( 99 | 100 | return f_retry # true decorator -> decorated function 101 | return deco_retry # @retry(arg[, ...]) -> true decorator 102 | 103 | 104 | def queue_cb(callback, callback_args=None, callback_kwargs=None, gtk=False): 105 | ''' 106 | Run the supplied callback after the function completes 107 | 108 | @param callback Function to run upon completion 109 | @param callback_args Arguments to send to the callback 110 | @param callback_kwargs Keyword arguments to send to the callback 111 | @param gtk Should the callback be run for Gtk 112 | ''' 113 | 114 | callback_args = callback_args or [] 115 | callback_kwargs = callback_kwargs or {} 116 | 117 | if gtk: 118 | from gi.repository import GObject 119 | callback_args.insert(0, callback) 120 | callback = GObject.idle_add 121 | 122 | def cb_decorator(func): 123 | @wraps(func) 124 | def run_cb(*args, **kwargs): 125 | func(*args, **kwargs) 126 | 127 | return callback(*callback_args, **callback_kwargs) 128 | 129 | return run_cb 130 | 131 | return cb_decorator 132 | -------------------------------------------------------------------------------- /kano/gtk3/kano_progress.py: -------------------------------------------------------------------------------- 1 | # 2 | # kano_progress.py 3 | # 4 | # Copyright (C) 2014 - 2018 Kano Computing Ltd. 5 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPLv2 6 | # 7 | # Customised progress bar widget 8 | # 9 | 10 | import os 11 | import sys 12 | 13 | from gi import require_version 14 | require_version('Gtk', '3.0') 15 | 16 | from gi.repository import Gtk, GObject 17 | 18 | if __name__ == '__main__' and __package__ is None: 19 | dir_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')) 20 | if dir_path != '/usr': 21 | sys.path.insert(1, dir_path) 22 | 23 | from kano.gtk3.apply_styles import apply_colours_to_screen, apply_styling_to_screen 24 | from kano.paths import common_css_dir 25 | 26 | # Be careful you don't call another function with this 27 | GObject.threads_init() 28 | import threading 29 | import time 30 | 31 | 32 | class ProgressBar(Gtk.ProgressBar): 33 | def __init__(self, pulse=True, rate=0.01): 34 | 35 | Gtk.ProgressBar.__init__(self) 36 | self.activity_mode = pulse 37 | self.still_working = True 38 | self.progress_rate = rate 39 | 40 | if self.activity_mode: 41 | self.pulse() 42 | else: 43 | self.set_fraction(0.0) 44 | 45 | GObject.timeout_add(50, self.on_timeout, None) 46 | 47 | def on_timeout(self, user_data): 48 | if self.activity_mode: 49 | self.pulse() 50 | else: 51 | new_value = self.get_fraction() + self.progress_rate 52 | if new_value > 1: 53 | new_value = 0 54 | self.win.close() 55 | 56 | self.set_fraction(new_value) 57 | 58 | if not self.still_working: 59 | self.win.close() 60 | 61 | # As this is a timeout function, return True so that it 62 | # continues to get called 63 | return self.still_working 64 | 65 | def work(self): # This would be the actual time-consuming workload 66 | if hasattr(self, "work_function"): 67 | 68 | if hasattr(self, "work_args"): 69 | self.work_function(self.work_args) 70 | else: 71 | self.work_function() 72 | 73 | def set_work_function(self, function, args=None): 74 | self.work_function = function 75 | if args is not None: 76 | self.work_args = args 77 | 78 | 79 | class KanoProgressBar(ProgressBar): 80 | CSS_PATH = os.path.join(common_css_dir, 'kano_progress.css') 81 | 82 | def __init__(self, pulse=True, title="", rate=0.01): 83 | apply_colours_to_screen() 84 | apply_styling_to_screen(self.CSS_PATH) 85 | 86 | ProgressBar.__init__(self, pulse, rate) 87 | self.get_style_context().add_class("KanoProgressBar") 88 | 89 | self.win = Gtk.Window() 90 | self.win.get_style_context().add_class("KanoProgressBar") 91 | self.win.set_decorated(False) 92 | self.win.set_resizable(False) 93 | self.win.set_position(Gtk.WindowPosition.CENTER) 94 | self.win.connect("delete-event", Gtk.main_quit) 95 | 96 | box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 97 | self.win.add(box) 98 | 99 | label = Gtk.Label(title) 100 | label.set_padding(10, 10) 101 | label.get_style_context().add_class("KanoProgressBar") 102 | box.pack_start(label, False, False, 5) 103 | box.pack_start(self, False, False, 0) 104 | 105 | def run(self): 106 | self.win.show_all() 107 | # Thread running the long process 108 | if self.activity_mode: 109 | wt = WorkerThread(self.work, self) 110 | wt.start() 111 | Gtk.main() 112 | 113 | 114 | class WorkerThread(threading.Thread): 115 | def __init__(self, function, parent): 116 | threading.Thread.__init__(self) 117 | self.function = function 118 | self.parent = parent 119 | 120 | def run(self): 121 | self.parent.still_working = True 122 | self.function() 123 | # This is here to make the GUI progress stop and quit 124 | self.parent.still_working = False 125 | 126 | 127 | if __name__ == "__main__": 128 | pb = KanoProgressBar(title="Here is a title") 129 | pb.set_work_function(time.sleep, 2) 130 | pb.run() 131 | --------------------------------------------------------------------------------