Введение


Голосовые ассистенты в мобильных устройствах не стоят на месте и непрерывно развиваются. Голосовой помощник для Sailfish OS, представленный осенью прошлого года, не исключение и тоже обрастает новым функционалом.

В той статье рассматривался базовый принцип внутренней работы приложения. Данный материал открывает серию из двух статей, в которой он будет рассмотрен подробнее:
  1. Работа с недокументированным API для управления устройством (текущая);
  2. Работа с интерфейсами D-Bus, предоставляемыми операционной системой.

В текущей статье описывается как управлять яркостью экрана и системной громкостью, а также как включать и выключать Bluetooth и режим полёта.

Подразумевается, что читатель уже установил Sailfish OS SDK и разрабатывал приложения с его использованием.

Как получать информацию


Во время разработки под Sailfish OS нельзя обойтись без документации, представленной и на официальном сайте, и в IDE. Документация написана качественно, с примерами, но не без недостатка — она коротка и освещает только базовый функционал, доступный программисту.

Но Sailfish OS, как и другие операционные системы, скрывает в себе больше описанного в документации. Надо только знать где искать. А искать надо в исходном коде, как в самом достоверном источнике информации. Благо Sailfish OS — достаточно открытый программный продукт на базе GNU/Linux.

Для начала потребуется подключиться к телефону по SSH (рисунок 1). Для этого надо разрешить удалённое соединение (1), установить для него пароль (2) и воспользоваться одним из указанных IP-адресов (3). После проделанных действий выполнить переход в каталог /usr/share. В данном каталоге находятся коды интерфейса и скрипты практически всех приложений, установленных на телефоне.

Рисунок 1 — Настройка подключения к телефону по SSH.
[Рисунок 1 — Настройка подключения к телефону по SSH.]

Каталоги приложений сторонних разработчиков начинаются (в большинстве случаев) с приставки harbour, за которой следует идентификатор программы. Такой префикс позволяет избежать возможного пересечения названий с системными именами. Каталоги стандартных приложений сопровождаются приставками jolla и sailfish. Остальное относится к системным функциям.

Рисунок 2 — Пример каталогов приложений.
[Рисунок 2 — Пример каталогов приложений.]

Настройка яркости экрана


Управление яркостью экрана относится к пользовательским настройкам системы, следовательно интерес представляет каталог /usr/share/jolla-settings, который содержит следующие объекты:
  • Каталог entries — хранит информацию о структуре настроек;
  • Каталог pages — хранит страницы и элементы настроек;
  • Каталог widgets — обычно не используется;
  • Файл settings.qml — главный файл запуска приложения настроек;
  • Файл x-openvpn.xml — хранит информацию о соединении с VPN-сервером.

Из списка видно, что в обозначенной задаче требуется обратиться к каталогу pages. Он разбит на подкаталоги согласно типам настроек: bluetooth, sounds и др. Настройки экрана расположены в директории display. Следовательно /usr/share/jolla-settings/pages/display является полным путём, относительно которого будет проходить последующая работа.

Первым делом выполняется поиск по ключевому слову:
grep -H 'brightness' *
$ cd /usr/share/jolla-settings/pages/display
$ grep -H 'brightness' *
BrightnessSlider.qml: label: qsTrId("settings_display-la-brightness")
BrightnessSlider.qml: value: displaySettings.brightness
BrightnessSlider.qml: onValueChanged: displaySettings.brightness = Math.round(value)
BrightnessSlider.qml: onBrightnessChanged: slider.value = brightness
display.qml: id: brightnessSlider
display.qml: entryPath: "system_settings/look_and_feel/display/brightness_slider"
display.qml: text: qsTrId("settings_display-la-adaptive_brightness")


Из полученных результатов видно, что код настройки яркости экрана находится в файле BrightnessSlider.qml.

Далее требуется уточнить как именно выполняется управление:
grep -C 1 'displaySettings' ./BrightnessSlider.qml
$ grep -C 1 'displaySettings' ./BrightnessSlider.qml
label: qsTrId("settings_display-la-brightness")
maximumValue: displaySettings.maximumBrightness
minimumValue: 1
value: displaySettings.brightness
stepSize: 1

onValueChanged: displaySettings.brightness = Math.round(value)

DisplaySettings {
id: displaySettings
onBrightnessChanged: slider.value = brightness


После поиска по файлу становится очевидным, что для управления яркостью устройства требуется создать объект DisplaySettings и менять значение поля brightness, принимая во внимание, что минимальное значение яркости равно единице, а максимальное хранится в поле maximumBrightness.

Последний штрих — определить необходимый импорт:
grep 'import' ./BrightnessSlider.qml
$ grep 'import' ./BrightnessSlider.qml
import QtQuick 2.0
import Sailfish.Silica 1.0
import com.jolla.settings.system 1.0
import org.nemomobile.systemsettings 1.0


QtQuick и Sailfish.Silica являются стандартными модулями для разработки интерфейса и не предоставляют доступ к настройкам. com.jolla.settings.system содержит внутренние объекты для работы приложения настроек и не представляет интереса для других программ. Остаётся org.nemomobile.systemsettings, описывающий необходимый компонент.

Таким образом, становится возможным сформировать свой элемент управления яркостью экрана для последующего включения в проект:
BrightnessControl.qml
import QtQuick 2.0
import org.nemomobile.systemsettings 1.0

Item {
    id: brightnessControl

    /* Установка яркости на указанное значение.
     * @param: value -- число от 1 (минимум) до maximumBrightness (максимум)
     */
    function setBrightness(value) {
        if (value <= 1) setMinimumBrightness();
        else if (value >= displaySettings.maximumBrightness) setMaximumBrightness();
        else displaySettings.brightness = value;
    }

    /* Увеличение яркости.
     * @param: percents -- процент от диапазона яркости.
     */
    function increaseBrightness(percents) {
        setBrightness(displaySettings.brightness + _calcDelta(percents))
    }

    /* Уменьшение яркости.
     * @param: percents -- процент от диапазона яркости.
     */
    function decreaseBrightness(percents) {
        setBrightness(displaySettings.brightness - _calcDelta(percents))
    }

    /* Установка минимальной яркости.
     */
    function setMinimumBrightness() {
        displaySettings.brightness = 1
    }

    /* Установка максимальной яркости.
     */
    function setMaximumBrightness() {
        displaySettings.brightness = displaySettings.maximumBrightness
    }

    /* Расчёт абсолютного значения изменения.
     * @param: percents -- процент от диапазона яркости.
     * @return: Значение изменения яркости в абсолютных единицах.
     */
    function _calcDelta(percents) {
        return Math.round(displaySettings.maximumBrightness / 100 * percents)
    }

    DisplaySettings { id: displaySettings }
}


Настройка системной громкости


Здесь используется уже известный метод поиска информации. Переходим в каталог /usr/share/jolla-settings/pages/sounds и проверяем ключевые слова:
grep -i -C 1 -H 'volume' *
$ cd /usr/share/jolla-settings/pages/sounds
$ grep -i -C 1 -H 'volume' *
SoundsPage.qml-
SoundsPage.qml: VolumeSlider {
SoundsPage.qml: id: volumeSlider
SoundsPage.qml: property string entryPath: "system_settings/look_and_feel/sounds/ringer_volume"
SoundsPage.qml- width: parent.width
--
VolumeSlider.qml- height: implicitHeight + valueLabel.height + Theme.paddingSmall
VolumeSlider.qml: //% "Ringtone volume"
VolumeSlider.qml: label: qsTrId("settings_sounds_la_volume")
VolumeSlider.qml- maximumValue: 100
--
VolumeSlider.qml- if (!externalChange) {
VolumeSlider.qml: profileControl.ringerVolume = value
VolumeSlider.qml- profileControl.profile = (value > 0) ? "general" : "silent"
--
VolumeSlider.qml- slider.externalChange = true
VolumeSlider.qml: slider.value = profileControl.ringerVolume
VolumeSlider.qml- slider.externalChange = false
--
VolumeSlider.qml-
VolumeSlider.qml: onRingerVolumeChanged: {
VolumeSlider.qml- slider.externalChange = true
VolumeSlider.qml: slider.value = profileControl.ringerVolume
VolumeSlider.qml- slider.externalChange = false


Из результата выполнения команды видно, что максимально допустимое значение громкости — 100, минимальное — 0; а управление выполняется с помощью элемента profileControl, и требует не только указания значения громкости, но и переключения профиля. Получим более подробную информацию об упомянутом элементе:
grep -C 1 'profileControl' ./VolumeSlider.qml
$ grep -C 1 'profileControl' ./VolumeSlider.qml
if (!externalChange) {
profileControl.ringerVolume = value
profileControl.profile = (value > 0) ? "general" : "silent"
}
--
slider.externalChange = true
slider.value = profileControl.ringerVolume
slider.externalChange = false
--
ProfileControl {
id: profileControl

--
slider.externalChange = true
slider.value = profileControl.ringerVolume
slider.externalChange = false
$ grep 'import' ./VolumeSlider.qml
import QtQuick 2.0
import Sailfish.Silica 1.0
import com.jolla.settings.system 1.0
import org.nemomobile.systemsettings 1.0


На основе результатов поиска и информации из предыдущего раздела становится возможным реализовать модуль для управления системной громкостью:
VolumeControl.qml
import QtQuick 2.0
import org.nemomobile.systemsettings 1.0

Item {
    id: volumeControl

    /* Установка громкости на указанное значение.
     * @param: value -- число от 0 (минимум) до 100 (максимум).
     */
    function setVolume(value) {
        if (value <= 0) {
            setMinimumVolume();
        } else if (value >= 100) {
            setMaximumVolume();
        } else {
            profileControl.ringerVolume = value;
            _setProfile();
        }
    }

    /* Увеличение громкости.
     * @param: percents -- процент от диапазона громкости.
     */
    function increaseVolume(percents) {
        setVolume(profileControl.ringerVolume + percents)
    }

    /* Уменьшение громкости.
     * @param: percents -- процент от диапазона громкости.
     */
    function decreaseVolume(percents) {
        setVolume(profileControl.ringerVolume - percents)
    }

    /* Установка минимальной громкости.
     */
    function setMinimumVolume() {
        profileControl.ringerVolume = 0;
        _setProfile();
    }

    /* Установка максимальной громкости.
     */
    function setMaximumVolume() {
        profileControl.ringerVolume = 100;
        _setProfile();
    }

    /* Установка профиля относительно выставленного значения громкости.
     */
    function _setProfile() {
        profileControl.profile = (profileControl.ringerVolume > 0) ? "general" : "silent"
    }

    ProfileControl { id: profileControl }
}


Здесь стоит обратить внимание на использование ключевых слов general и silent для управления звуковым профилем системы (основной и бесшумный соответственно). Это два зарезервированных значения, определяющих поведение системы в зависимости от выставленного значения громкости.

Переключение режима полёта и Bluetooth


Принцип поиска API абсолютно идентичен описанному выше, поэтому перейдём непосредственно к рассмотрению уже готовых элементов.

Для управления режимом полёта требуется подключить модуль MeeGo.Connman, предоставляющий компонент NetworkManagerFactory. Выставляя значение поля instance.offlineMode данного объекта в true или false, становится возможным включать и выключать указанный режим соответственно.
FlightControl.qml
import QtQuick 2.0
import MeeGo.Connman 0.2

Item {
    id: flightControl

    /* Включение режима “В самолёте”.
     */
    function turnOnFlightMode() {
        connMgr.instance.offlineMode = true
    }

    /* Выключение режима “В самолёте”.
     */
    function turnOffFlightMode() {
        connMgr.instance.offlineMode = false
    }

    /* Переключение режима “В самолёте”.
     */
    function switchFlightMode() {
        connMgr.instance.offlineMode = !connMgr.instance.offlineMode
    }

    NetworkManagerFactory { id: connMgr }
}


Для управления состоянием Bluetooth также подключается модуль MeeGo.Connman, но при этом используется компонент TechnologyModel, значение поля powered которого принимает true или false для включения или выключения Bluetooth соответственно. Стоит заметить, что остальная работа с интерфейсом производится с помощью стандартных возможностей Qt.
BluetoothControl.qml
import QtQuick 2.0
import MeeGo.Connman 0.2

Item {
    id: bluetoothControl

    /* Включение bluetooth.
     */
    function turnOnBluetooth() {
        btTechModel.powered = true
    }

    /* Выключение bluetooth.
     */
    function turnOffBluetooth() {
        btTechModel.powered = false
    }

    /* Переключение bluetooth.
     */
    function switchBluetooth() {
        btTechModel.powered = !btTechModel.powered
    }

    TechnologyModel {
        id: btTechModel
        name: "bluetooth"
    }
}


Заключение


В данной статье показан один из способов поиска недокументированных возможностей в Sailfish OS и представлены примеры кода для контроля яркости, громкости, Bluetooth и режима полёта. Более подробно ознакомиться с реализацией этих и других модулей управления системными настройками (а также со способами их применения) можно на GitHub.

Однако, прежде чем смотреть готовый код, попробуйте самостоятельно реализовать и подключить какой-нибудь модуль, и только потом сравнивайте с опубликованным примером. Можно, например, попробовать сменить оформление (поиск по ambience) или системный размер шрифта (fontSize), управлять Wi-Fi (wifi) или общим доступом к интернету (tethering).

Следующая статья будет посвящена тому, какие интерфейсы и функции D-Bus предоставляет Sailfish OS программисту.

Возникающие по ходу разработки вопросы и идеи всегда можно обсудить в Telegram-чате и в группе ВКонтакте.

Выражаю благодарность хабраюзеру ragequit за инвайт, давший возможность опубликовать эту и последующие статьи.

Комментарии (3)


  1. lieff
    24.08.2017 14:03

    Меня больше всего интересует как использовать GLES API без QML. GL код обычно уже готовый, переписывать все на QML ради одной платформы — не вариант. Видел что там вроде разрешили SDL приложения, но как штатно инициализировать GLES так и не нашел.


    1. chuvilin
      24.08.2017 19:27

      А если через Scene Graph попробовать?


      1. lieff
        25.08.2017 02:19

        Если делать новое приложение с нуля и только под QT — то подойдет. А если уже есть нативный GL код — то все равно придется портировать. И не понятно, зачем нужен SDL без GL без QT.