Блог о Gentoo и около-линуксовым штукам

13 августа 2017 г.

Заметки о proftpd

7:23 Опубликовал Дмитрий Исаенко Нет комментариев
Что нужно для быстрой развёртки:
1. Удалить ACL. Если в системе не используется, то вообще никаких проблем.
# echo 'net-ftp/proftpd openssl -acl' >> /etc/portage/package.use
2. Поставить:
# emerge net-ftp/proftpd
3. Добавить пользователя для анонимусов:
# echo ftp:x:21:21:added by portage for ftpbase:/home/ftp:/sbin/nologin >> /etc/passwd
# echo 'ftp:x:21:' >> /etc/group
4. Добавить имя хоста в /etc/hosts
# hostname
myAwesomeHost
# vim /etc/hosts
127.0.0.1 localhost myAwesomeHost
5. Поправить (или не править, он и так хорош) конфиг
# vim /etc/proftpd/proftpd.conf

ServerName "ProFTPD Server on vostro"

ServerType standalone
DefaultServer on
RequireValidShell off
AuthPAM off
AuthPAMConfig ftp

Port 21

Umask 022

MaxInstances 30

User ftp
Group ftp

DefaultRoot ~

AllowOverwrite on
<limit site_chmod="">
  DenyAll
</limit><anonymous>
  User anonymous
  Group anonymous
  AnonRequirePassword   off
  RequireValidShell     off

  # Clients can login with the username "anonymous" and "ftp".
  UserAlias anonymous ftp

  # Limit the maximum number of parallel anonymous logins to 10.
  MaxClients 10

  # Prohibit the WRITE command for the anonymous users.
  <limit write="">
    DenyAll
  </limit>
</anonymous>
6. Добавить в автозагрузку и запустить
# rc-update add proftpd default
# /etc/etc/init.d/proftpd start

6 августа 2017 г.

ImageMagic: конвертирование иконок для проектов AndroidStudio

16:07 Опубликовал Дмитрий Исаенко Нет комментариев
Для разных dpi можно воспользоваться встроенным инструментом Image Asset или сгенерировать их с помощью ImageMagic. Для второго варианта понадобиться базовое изображение, созданное для разрешение extra-extra-extra-high-density (xxxdpi).
Дальше используем скрипт:
#!/bin/bash
######################################
# As input we use file xxxhdpi  file #
######################################
convert $1 -resize 75% $1_xxhdpi.png
convert $1 -resize 50% $1_xhdpi.png
convert $1_xhdpi.png -resize 75% $1_hdpi.png
convert $1 -resize 25% $1_mdpi.png
convert $1_mhdpi.png -resize 75% $1_ldpi.png

#    Examples:
#    36x36 (0.75x) for low-density
#    48x48 (1.0x baseline) for medium-density
#    72x72 (1.5x) for high-density
#    96x96 (2.0x) for extra-high-density
#    144x144 (3.0x) for extra-extra-high-density
#    192x192 (4.0x) for extra-extra-extra-high-density
Всё.

30 июля 2017 г.

Виджет для Android (без конфигуратора и с IntentService)

7:23 Опубликовал Дмитрий Исаенко , Нет комментариев
Сегодня мы будем создавать виджет, который может обновлять текст в нём по нажатию на одну из его четырёх кнопок. По нажатию на первые две будет вызываться MainActivity и обновлять текст на тот, что был передан в зависимости от того, какая из двух кнопок нажата. По нажатию на третью и четвёртую текст просто будет обновляться с помощью IntentSerive службы.
Код проекта доступен по ссылке https://github.com/developersu/MyWidgetNoConf/ .

Создадим новый проект с именем MyWidgetNoConf.

Опишем в манифесте все наши классы
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blogspot.developersu.mywidgetnoconf">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver android:name=".myWidget">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
        </receiver>

        <service
            android:name=".MyIntentService"
            android:exported="false">
        </service>
    </application>

</manifest>
Теперь добавим XML с описанием возможнотей нашего виджета.

res→xml→widget_info.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minHeight="180dp"
    android:minWidth="180dp"
    android:updatePeriodMillis="1800000"
    android:resizeMode="horizontal|vertical"
    />
Добавим layout-файл для виджета:
res→layout→my_widget_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <TextView
        android:id="@+id/widgetText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="0" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
        <Button
            android:id="@+id/widgetBtn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Button 1" />
        <Button
            android:id="@+id/widgetBtn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Button 2" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="horizontal">
        <Button
            android:id="@+id/widgetBtn3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button 3" />
        <Button
            android:id="@+id/widgetBtn4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Button 4" />
    </LinearLayout>
</LinearLayout>
Получится следующее:
Добавим классы MyIntentService и myWidget в основной package.
java→com.blogspot.developersu.mywidgetnoconf
→MyIntentService
→myWidget

Перейдём собственно к коду. Начнём с описания функционала виджета. Мы будем использовать onUpdate и onEnabled плюс функцию для определения функций клавиш. Сохранением данных тестовом поле пренебрежем.

Итак, для начала код:
package com.blogspot.developersu.mywidgetnoconf;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;

public class myWidget extends AppWidgetProvider {

    private void setWidgetIntents(Context context, AppWidgetManager appWidgetManager, int appWidgetIds[]){

        RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.my_widget_layout);

        for (int appWidgetId : appWidgetIds) {
            Intent intentBtn = new Intent(context, MainActivity.class);
            intentBtn.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
            intentBtn.putExtra("signature", "Button 1");
            intentBtn.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP );
            PendingIntent pi = PendingIntent.getActivity(context, appWidgetId+0, intentBtn, PendingIntent.FLAG_UPDATE_CURRENT); 
            rv.setOnClickPendingIntent(R.id.widgetBtn, pi);
            /*===================================================================*/
            // Set the same for button 2
            Intent intentBtn2 = new Intent(context, MainActivity.class);
            intentBtn2.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
            intentBtn2.putExtra("signature", "Button 2");
            intentBtn2.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP );
            PendingIntent pi2 = PendingIntent.getActivity(context, appWidgetId+1, intentBtn2, PendingIntent.FLAG_UPDATE_CURRENT);
            rv.setOnClickPendingIntent(R.id.widgetBtn2, pi2);
            /*===================================================================*/
            Intent intentBtn3 = new Intent(context, MyIntentService.class);
            intentBtn3.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
            intentBtn3.putExtra("signature", "Button 3");
            PendingIntent pi3 = PendingIntent.getService(context, appWidgetId+2, intentBtn3, PendingIntent.FLAG_UPDATE_CURRENT);
            rv.setOnClickPendingIntent(R.id.widgetBtn3, pi3);
            /*===================================================================*/
            Intent intentBtn4 = new Intent(context, MyIntentService.class);
            intentBtn4.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
            intentBtn4.putExtra("signature", "Button 4");
            PendingIntent pi4 = PendingIntent.getService(context, appWidgetId+3, intentBtn4, PendingIntent.FLAG_UPDATE_CURRENT);
            rv.setOnClickPendingIntent(R.id.widgetBtn4, pi4);
            appWidgetManager.updateAppWidget(appWidgetId, rv);
        }
    }
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        setWidgetIntents(context, appWidgetManager, appWidgetIds);
    }

    @Override
    public void onEnabled(Context context) {
        AppWidgetManager awm = AppWidgetManager.getInstance(context);
        ComponentName compName = new ComponentName(context, myWidget.class);
        int[] widgetIds = awm.getAppWidgetIds(compName); // избыточно, но оставим для наглядности
        setWidgetIntents(context, awm, widgetIds);
    }
}
У нас есть функция setWidgetIntents которая принимает context, AppWidgetManager и множество ID среди которых есть и ID нашего виджета.
onUpdate — все данные у нас сразу имеются, так что просто передаём их в функцию
onEnabled — создаём объект  AppWidgetManager используя AppWidgetManager.getInstance(context), создаём ComponentName, который нам понадобится для вытаскивания передавая ему context и имя класса нашего виджета. IDs получаем через awm.getAppWidgetIds(compName).
setWidgetIntents — опеределим “RemoteViews rv” чтобы через него присваивать кнопкам действия. Далее создадим цикл для всех полученных widget ID.
Присваивать каждой кнопке действия следует используя PendingIntent. Для него требуется Intent в который в первом и втором случае обращён к MainActivitiy.class, а в третьем и четвёртом — к  MyIntentService.class.
В каждый такой Intent мы помещаем значение кнопки, которое будет передаваться. Это будет строка с именем “signature” и значением “Button N”, где N номер кнопки. Также передаём в него ID виджета, который инициировал вызов Intent.
Кроме того, в первых двух случаях мы определяем флаг  Intent.FLAG_ACTIVITY_SINGLE_TOP который говорит системе, что Activity не должен запускаться, если экземпляр уже существует в стеке. Таким образом, если activity уже запущен, то приниматься Intent с этим флагом будет в методе onNewIntent(). Альтернативой этому может быть установка в манифесте launchMode в singleTop (тогда этот флаг не нужен):
<activity android:name=".MainActivity" android:launchMode="singleTop">
Но этот вариант мы опустим. Трогать манифест не будем :)

Для обращения  к сервису этот флаг нам не потребуется, т.к. сервисы работают совсем не так как обычные Activity.
В конце мы “создаём” PendingIntent обращаясь к getActivity или getService. Оба этих метода принимают 4 значения: context, requestCode, Intent и флаги. В requestCode мы будем заносить ID нашего виджет плюс какую-то цифру, чтобы определить каждый PendingIntent отдельно. Их стандартное поведение может поначалу ввести в заблуждение, но, если коротко, то для разных PendingIntent лучше бы иметь разные коды. Используемый флаг PendingIntent.FLAG_UPDATE_CURRENT говорит системе, что если описываемый PendingIntent уже существует, то следует лишь заменить его extra-данные (те, что мы добавляли используя метод addExtra).

Далее присваеваем каждой кнопке действие и “применяем измененеия”
rv.setOnClickPendingIntent(R.id.widgetBtn, pi);
appWidgetManager.updateAppWidget(appWidgetId, rv);
Перейдём теперь к классу MyIntentService.
Класс наследует  IntentService и реагирует ловит входящие Intent в onHandleIntent. При запуске вызыватся конструктор, порождается новая нить, которая после выполнения метода  onHandleIntent() завершается.

Переходим к коду. Ничего, чтобы ещё хотелось выделить отдельно у меня нет.
package com.blogspot.developersu.mywidgetnoconf;

import android.app.IntentService;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.os.Bundle;
import android.widget.RemoteViews;


public class MyIntentService extends IntentService {

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            Bundle bndle = intent.getExtras();
            int wID = bndle.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
            String btnPressed = bndle.getString("signature");

            RemoteViews rv = new RemoteViews(getPackageName(), R.layout.my_widget_layout);
            AppWidgetManager awm = AppWidgetManager.getInstance(getApplicationContext());
            rv.setTextViewText(R.id.widgetText, btnPressed);
            awm.updateAppWidget(wID, rv);
        }
    }
}
Перейдём к MainActivity.

Тут как не было элементов управления так и нет. При получении Intent будет вызываться или onCreate() (если приложение закрыто) или onNewIntent() (если экземляр уже существует). Определением того, что мы получили в прилетевшем Intent будет заниматься метод handleIntent(), который принимает Intent.
В случае, если Intent не пустой и содержит хоть какие-то extras, будем пытаться(!) брать из него предполагаемые данные, т.е. строку “signature” и ID виджета.
Сразу после этого, выведем тост (Toast) с полученой информацией. Если пользователь просто запускает приложение, то ему покажется уведомление с widget ID = 0 и string = null. Далее приложение проверит эти поля и если они отличны от вышеприведённых значений, то присвоит строку в TextView виджета. За присвоение отвечает метод  sendBackToWidget().
package com.blogspot.developersu.mywidgetnoconf;

import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.RemoteViews;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private void handleIntent(Intent i){
        String recievedString;
        Bundle bndle = i.getExtras();
        if (bndle != null) {
            int awID = bndle.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
            recievedString = bndle.getString("signature");
            Toast.makeText(getApplicationContext(), "Got intent from: " + Integer.toString(awID) + " " + recievedString, Toast.LENGTH_SHORT).show();
            if (awID != 0 && recievedString != null)
                sendBackToWidget(awID, recievedString);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        handleIntent(getIntent());
        /* Почему мы вызываем handleIntent прямо тут? Потому, что если приложение закрыто а не работает в фоне, то при запуске его из виджета будет вызван
         * onCreate. Очевидно, что если это приложение уже будет находиться в стеке, то вызываться будет что-то другое :) */
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        //setIntent(intent);                    // Удобно использовать, если бы наш Intent был объявлен глобальной переменной. Тогда бы мы просто переопределили его через setintent().
        handleIntent(intent);
    }

    private void sendBackToWidget(int widgetID, String str){
        AppWidgetManager awManager = AppWidgetManager.getInstance(getApplicationContext());
        RemoteViews rv = new RemoteViews(getPackageName(), R.layout.my_widget_layout);
        rv.setTextViewText(R.id.widgetText, str);
        awManager.updateAppWidget(widgetID, rv);
    }
}
Код не идеален и оставляет множество мест для оптимизаций. Впрочем, нет ничего хуже, чем преждевременная оптимизация, так что оставлю это вам.

Посмотрим на то, что получилось.

10 июля 2017 г.

Пишем виджет для Android (с конфигуратором и профусктками)

5:49 Опубликовал Дмитрий Исаенко , Нет комментариев
Структурированно рассказать о том, как работает виджет сложновато, т. к. все составляющие очень тесно взаимосвязаны и всё это вызывает друг-друга туда-сюда как только хочет. Но давайте попробуем разобраться на живом примере. Полный код того, что получилось в конце страницы и на GitHub.

Виджеты бывают с «окном» конфигуратора и без него. Это «окно» является Activity, вызываемым при первом добавлении виджета на экран. Виджеты с ним и без него несколько отличаются в поведении. Такой конфигуратор нужен в основном для того, чтобы предопределить параметры для каждого нового добавляемого виджета. Это не значит, что добавляемые виджеты не могут быть одинаковыми, т.е. они могут, но без конфигуратора они будут одинаковыми гарантированно.
Итак конфигуратор, это обычная Activity которая получает параметры добавляемого виджета используя Intent, строго говоря она получает EXTRA_APPWIDGET_ID — ID добавляемого виджета и описывает какие параметры будут отображаться на виджете. По завершении, конфигуратор возвращает это значение назад и посылает RESULT_OK используя результирующий Intent. Как сказано ранее, конфигуратор должен не только вернуть эти данные, но и обновить объект RemoteViews, используемый для настройки отображаемых в виджете вещей, т. к. при добавлении виджета с помощью конфигуратора метод onUpdate() вызван не будет. Другими словами конфигуратор должен выставить необходимый текст в  TextView виджета, добавить слушателей на кнопки виджета и т.п. Определяется Activity конфигуратора в файле настройки XML виджета (см.далее).
В случае, если конфигуратора нет, при добавлении виджета будет вызван метод onUpdate() в котором как правило и описывает большая часть того, что виджет должен делать. Подробнее о методах виджета поговорим чуть позже.

Задачу поставим так: сделать виджет с конфигуратором, который будет представлять собой текст и кнопку. По нажатии на кнопку будет вызываться конфигуратор. В конфигураторе будет EditText и кнопка для применения изменений. По нажатии на неё текст в виджете будет меняться на установленный в EditText конфигуратора.

Шаг 1. Для начала создадим проект с пустой MainActivity (File → New Project → …).  Создадим внутри нашего проекта (пусть это будет в пакете java/widgets/) два java класса:
MyWidget    — сам будущий виджет
MyWidgetConfig — окно конфигурации для виджета
Вернёмся к ним позже.

Шаг 2
. Создаём XML файл в res/xml/example_info.xml ('это наш AppWidget Provider) Это то, что будет описывать нюансы самого виджета и опишет связку будущего виджета с activity конфигуратора (то окно, которое будет запускаться при добавлении виджета на экран):
<appwidget-provider 
    android:configure="widgets.MyWidgetConfig" 
    android:minheight="40dp" android:minwidth="110dp" 
    android:resizemode="horizontal|vertical" 
    android:updateperiodmillis="1800000" 
    xmlns:android="http://schemas.android.com/apk/res/android">
</appwidget-provider>
Пройдёмся по основным составляющим этого файла.
configure определяет activity отвественную за конфигурацию виджета при добавлении и вообще.
minHeight и minWidth определяет минимальные размеры для корректного отображения виджета. К сожалению, тут нет механизма в стиле “1 клеточка на 2 клеточки”. На странице документации по andoroid есть неплохое описание какие размеры должны быть (См. ссылки в конце статьи). В этом примере 1 клетка в высоту, 2 в ширину.
updatePeriodMillis может быть установлен в  1800000 (30мин). Меньше вроде как не умеет (Вообще умеет, но требует реализации таймера).
resizeMode определяет как пользователь может изменять размеры виджета. Можно установить в “none”, тогда размеры изменять будет нельзя. Можно в “horizontal”, можно  в “vertical”, можно в оба одновременно “horizontal|vertical”. Названия говорят сами за себя.

Шаг 3
. Заходим в AndroidManifest.xml
Добавляем наш виджет и его кофигуратор в секцию <application>:
<!-- added -->
<receiver android:name="widgets.MyWidget">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
        android:resource="@xml/example_info" />
</receiver>
<activity android:name="widgets.MyWidgetConfig">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
    </intent-filter>
</activity>
<!-- added -->
Шаг 4. Добавляем UI. Добавляем в layout/ два новых файла через New->XML->Layout XML widget_main.xml widget_main_config.xml В widget_main_config.xml закидываем элементы EditText, Button со следующими ID: configWidgetText configWidgetBtn.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <EditText
        android:id="@+id/configWidgetText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="textPersonName"
        android:text="Name" />
    <Button
        android:id="@+id/configWidgetBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button" />
</LinearLayout>
Добавим в widget_main следующие элементы: TextView, Button со следующими ID:
widgetText
widgetBtn

Установим ширину и высоту для layout в значения “wrap_content”.

Итого:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <TextView
        android:id="@+id/widgetText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TextView" />
    <Button
        android:id="@+id/widgetBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button" />
</LinearLayout>
Шаг 5. Вернёмся к MyWidget.java
Виджеты являются в определённом роде BroadcastReciever которые обрабатывают определённые события. Как и BroadcastReciever, обрабатывают действия они методом onRevieve() который в дальнейшем обрабатывает данные другими методами. Тем не менее, виджет является наследником класса AppWidgetProvider (в свою очередь, наследник того самого BroadcastReciever), который всё несколько упрощает.
Рассмотрим что нам предлагает AppWidgetProvider:
onEnabled — вызывается один раз, когда виджет создаётся (добавляется на экран).
onUpdate — вызывается при обновлении виджета в заданный в XML файле интервал. Основной функционал виджета находится именно здесь. Как уже было сказано, если вам нужно вызывать этот метод чаще чем раз в 30 минут, следует добавить таймер (используя класс AlarmManager).
onDeleted — вызывается, когда виджет удалён с экрана.
onDisabled — вызывается, когда все виджеты удалены с экрана. Т.е. можно добавлять сколько угодно экземпляров этого виджета, и когда последний из добавленных будет удалён, этот метод сработает. В этом и отличие от onDeleted
onRestored — вызывается при восстановлении AppWidget из бекапа. Откликается на ACTION_WIDGET_RESTORED. В обычном случае будет сразу вызван onUpdate. Даже не знаю как этот метод можно использовать.
OnAppWidgetOptionsChanged — вызывается при изменении размеров виджета (да, если такие изменения вообще разрешены в XML настройках).
onReceive — вызывается для обработки событий другими методами.

Наследуем наш класс от AppWidgetProvider (extends AppWidgetProvider).
Жмём правой кнопкой на классе, потом Generate -> Override Methods…
Выбираем onUpdate и onDelete. Первый будет вызываться при обновлении виджета, второй — при удалении. 

Для меня стало открытием, так что сейчас покажу :)

Для разнообразия, добавим всплывающее окно при удалении виджета через Toast:
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
    super.onDeleted(context, appWidgetIds);
    Toast.makeText(context, "Widget deleted", Toast.LENGTH_SHORT).show();
}
Теперь переместимся в метод onUpdate.
Важно!
Этот метод будет вызываться в зависимости от настроек из  res/xml/example_info.xml но при создании через конфигуратор он вызван не будет. Будет вызван onEnabled() (но и то как-то, по моим ощущениям, кривовато).

Для начала, установим что-нибудь в первое поле TextView. Какой смысл добавлять что-то в TextView, если это изменит текст через заданный интервал, тем более, что мы этот текст выставляем в конфигураторе? Смысла в этом нет. Просто это даст чуть более полную картину того, как работать с виджетами. В чём есть смысл, так это в определении кнопке действия, т.к. после перезагрузки устройства работать она перестанет (мы говорим о том, что если действие для кнопки будет определяться в конфигруаторе, то эта привязка перестанет работать после перезагурзки, т.к. в этом случае созданием виджета будет заниматься не конфигуратор а система). Но определять их надо в onEnabled, т.к. после перезагруки именно он и будет вызван.
Создадим экземпляр класса RemoteViews (референс: RemoteViews(String packageName, int layoutId) ). Где обращаемся к context из пренимаемых onUpdate параметров (берем оттуда имя) и layout самого виджета. Установим текст "Hello widget!" методом .setTextViewText  (референс: setTextViewText(int viewId, CharSequence text)). После чего добавим ещё одну строку, которая укажет приложению на необходимость применить сделанные изменения. Такой финт не типичен для работы с Activity, но для виджетов каждое изменение в виджете должно сопровождаться обновлением.
Без такого обновления будет вылетать ошибка “Problem Loading Widget”. Проделывать эти действия надо со всеми appWidgetIds. Используем конструкцию for (foreach):
for (int appWidgetId : appWidgetIds) {
    RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.widget_main);
    view.setTextViewText(R.id.widgetText, "Hello widget!");
    appWidgetManager.updateAppWidget(appWidgetId, view);
}

Добавим метод onEnabled и назначим кнопке на виджете действе: вызов конфигуратора по нажатию. В обычной Activity достаточно было бы создать Intent, но в виджетах используется несколько более сложная схема. Итак, необходимо:
  1. Создать Intent. В качетсве параметров — контекст и имя класса Activity-сонфигуратора с .class в конце. 
  2. Добавить в него ID виджета, который запрашивает вызов конфигуратора (с помошью .putExtra). Ожидается переменная int для «поля» AppWidgetManager.EXTRA_APPWIDGET_ID 
  3. Создать объект PendingIntent, который будет назначен кнопке для посылки-по-нажатию. См.аргументы ниже. 
Попутно определим все необходимые параметры ещё раз (тут не плохо было бы подумать о том, как не повторяться с тем, что мы описывали  в onUpdate). В отличии от onUpdate, принимаемое значение метода только context, так что надо вытащить откуда-то и ID. Для этого вводим объект класса ComponentName. Итак код:
    @Override
    public void onEnabled(Context context) {
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_main);
        AppWidgetManager awm = AppWidgetManager.getInstance(context);
        ComponentName compName = new ComponentName(context, MyWidget.class);
        int[] widgetIds = awm.getAppWidgetIds(compName);
        for (int widgetId : widgetIds) {
            Intent intentBtnPwr = new Intent(context, MyWidgetConfig.class);
            intentBtnPwr.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
            PendingIntent pi = PendingIntent.getActivity(context, widgetId, intentBtnPwr, PendingIntent.FLAG_UPDATE_CURRENT);
            remoteViews.setOnClickPendingIntent(R.id.widgetBtn, pi);
            awm.updateAppWidget(widgetId, remoteViews);
        }
    }

Шаг 6. Перейдём к MyWidgetConfig.java Объявляем переменные для всего класса. EdText — поле ввода на экране конфигурации виджета awManager — менеджер, который понадобиться для работы с виджетом, thisContext — контекст awID — переменная для хранения ID добавляемого виджета Кнопку добавлять не будем, просто привяжем её к Listener и опишем необходимое поведение позже.
public class NoWolWidgetConfig extends Activity {
    EditText EdText;
    AppWidgetManager awManager;
    Context thisContext;
    int awID;
...
Наследуем класс от Activity, добавляем метод onCreate через Generate… (тем же способом, что описан выше). Ну и сразу привязываем класс к layout.
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.widget_main_config);
…
Теперь установим связку для EditText
EdText = (EditText)findViewById(R.id.configWidgetText);
Опишем Intent, чтобы получить информацию для текущей Activity о том, какой виджет вызвал эту Activity. В первый раз, при добавлении виджета, он помещается на экран и получает свой ID, после чего запускается конфигуратор. Если результат выполнения конфигуратора не успешен, или он был закрыт (кнопкой назад, например) виджет удаляется, т. к. не получает результрующего статуса RESULT_OK от конфигуратора. Также во входящем Intent конфигуратор получает ID экземпляра виджета и обязан по завершению передать его обратно, также используя механизм Intent. Объявим Intent и привяжем его к полученному:
Intent intent = getIntent();
Создадим хранилище (Bundle) и выгрузим в него данные из полученного интента —присвоем awID значение ID виджета. В случае неудачи просто закроем конфигуратор и тогда виджет создан не будет.
Bundle bundleExtras = intent.getExtras();
if (bundleExtras != null){
    awID = bundleExtras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
}else
 finish();
Теперь определим контекст в переменную и с его использованием привяжем AppWidgetManager к нашему классу (нужен для такой же работы с виджетом, как и внутри самого кода виджета). И да, мы уже делали такое чуть ранее:
thisContext = NoWolWidgetConfig.this;
awManager = AppWidgetManager.getInstance(thisContext);
Создадим переменную для RemoteViews которая позволит обращаться к элементам виджета.
final RemoteViews awRV = new RemoteViews(thisContext.getPackageName(), R.layout.widget_main);
Теперь привяжем нашу единственную кнопку к listener и опишем её поведение.
findViewById(R.id.configWidgetBtn).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        // Action starts here ;)
    }
});
Как вы заметили, для неё нет отдельной переменной. Но работать будет :)
Теперь заставим по клику в конфигураторе брать значение из EditText и засовывать его во второй TextView нашего виджета:
awRV.setTextViewText(R.id.widgetText, ((EditText) findViewById(R.id.configWidgetText)).getText().toString());
Назначим кнопке действие тем же способом, что и ранее мы проделывали в виджете в методе onEnabled.
Intent intentBtnPwr = new Intent(thisContext, MyWidgetConfig.class);
                intentBtnPwr.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, awID);
                PendingIntent pi = PendingIntent.getActivity(thisContext, awID, intentBtnPwr, PendingIntent.FLAG_UPDATE_CURRENT);
                awRV.setOnClickPendingIntent(R.id.widgetBtn, pi);
После всего этого необходимо обновить виджет используя метод .updateAppWidget объекта класса AppWidgetManager. Он обновит в виджете только то, что мы изменили в виджете и не тронет другое. Проще говоря, смотрите на это как на кнопку «Применить».
awManager.updateAppWidget(awID, awRV);
Теперь мы передаём результат (ID виджета) в Intent в наш виджет и закрываем Activity конфигуратора. Так виджет будет знать, что конфигуратор успешно выполнился и будет отображен на экране. Точнее сказать, он не будет с него удалён, что произошло бы при закрытии конфигуратора (повторяюсь).
Intent resultIntent = new Intent();
resultIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, awID);
setResult(RESULT_OK, resultIntent);
finish();
Вот что у нас получилось:

Получилось вертикальное видео. На этом пожалуй всё. Как и обещал, код основных компонентов полностью:
manifests/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.blogspot.developersu.we.widgetexample">

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <!-- added -->
    <receiver android:name="widgets.MyWidget">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data android:name="android.appwidget.provider"
            android:resource="@xml/example_info" />
    </receiver>
    <activity android:name="widgets.MyWidgetConfig">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
        </intent-filter>
    </activity>
    <!-- added -->
</application>

</manifest>
com.blogspot.developersu.we.widgetexample/MainActivity
package com.blogspot.developersu.we.widgetexample;
/*
*  It doesn't make sense to cover this code by any license, 'cause everything described here
*  has been widely used by thousands of coders all around the world. So most likely I've already
*  stolen this code in their eyes and you're going to steal it from me. LOL.
*  Bye!
*
*  Dmitry Isaenko,
*  2017, Russia
*  https://developersu.blogspot.com/2017/07/androidwidget.html
*
 */
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
int awID;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }
}
widgets/MyWidget
package widgets;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import android.widget.Toast;

import com.blogspot.developersu.we.widgetexample.R;

public class MyWidget extends AppWidgetProvider {

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        for (int appWidgetId : appWidgetIds) {
            RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.widget_main);
            view.setTextViewText(R.id.widgetText, "Hello widget!");
            appWidgetManager.updateAppWidget(appWidgetId, view);
        }
    }

    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        super.onDeleted(context, appWidgetIds);
        Toast.makeText(context, "Widget deleted", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onEnabled(Context context) {
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_main);
        AppWidgetManager awm = AppWidgetManager.getInstance(context);
        ComponentName compName = new ComponentName(context, MyWidget.class);
        int[] widgetIds = awm.getAppWidgetIds(compName);
        for (int widgetId : widgetIds) {
            Intent intentBtnPwr = new Intent(context, MyWidgetConfig.class);
            intentBtnPwr.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
            PendingIntent pi = PendingIntent.getActivity(context, widgetId, intentBtnPwr, PendingIntent.FLAG_UPDATE_CURRENT);
            remoteViews.setOnClickPendingIntent(R.id.widgetBtn, pi);
            awm.updateAppWidget(widgetId, remoteViews);
        }
    }
}
widgets/MyWidgetConfig
package widgets;


import android.app.Activity;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.RemoteViews;

import com.blogspot.developersu.we.widgetexample.R;


public class MyWidgetConfig extends Activity {

    EditText EdText;
    AppWidgetManager awManager;
    Context thisContext;
    int awID;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.widget_main_config);

        EdText = (EditText)findViewById(R.id.configWidgetText);

        Intent intent = getIntent();
        Bundle bundleExtras = intent.getExtras();
        if (bundleExtras != null){
            awID = bundleExtras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
        }else
            finish();

        thisContext = this.getApplicationContext();
        awManager = AppWidgetManager.getInstance(thisContext);
        final RemoteViews awRV = new RemoteViews(thisContext.getPackageName(), R.layout.widget_main);

        findViewById(R.id.configWidgetBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                awRV.setTextViewText(R.id.widgetText, ((EditText) findViewById(R.id.configWidgetText)).getText().toString());
                //*added
                Intent intentBtnPwr = new Intent(thisContext, MyWidgetConfig.class);
                intentBtnPwr.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, awID);
                PendingIntent pi = PendingIntent.getActivity(thisContext, awID, intentBtnPwr, PendingIntent.FLAG_UPDATE_CURRENT);
                awRV.setOnClickPendingIntent(R.id.widgetBtn, pi);
                // added end */
                awManager.updateAppWidget(awID, awRV);

                Intent resultIntent = new Intent();
                resultIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, awID);
                setResult(RESULT_OK, resultIntent);
                finish();
            }
        });
    }
}
res/layout/activity_main
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.blogspot.developersu.we.widgetexample.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>
res/layout/widget_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/widgetText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TextView" />

    <Button
        android:id="@+id/widgetBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button" />

</LinearLayout>
res/layout/widget_main_config
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/configWidgetText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="textPersonName"
        android:text="Name" />

    <Button
        android:id="@+id/configWidgetBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button" />
</LinearLayout>
res/xml/example_info
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:configure="widgets.MyWidgetConfig"
    android:minHeight="40dp"
    android:minWidth="110dp"
    android:updatePeriodMillis="1800000"
    android:resizeMode="horizontal|vertical"
    />

 p.s. Есть небольшая проблема с MainActivity. Если он открыт (onPause, а не onActive, очевидно), и вы вызываете конфигуратор кнопкой на виджете, то MainActivity будет отображен после внесения изменений, хотя ожидается увидеть экран с виджетом. Виджет при этом отобразит сделанные в кнофигураторе изменения.

23 апреля 2017 г.

ECM to ISO в Linux

18:56 Опубликовал Дмитрий Исаенко , , Нет комментариев
В последнее время покачиваю образы с old-games.ru, а там формат bin.ecm. Сегодня будет рецепт конвертации в каноничный ISO.
Для начала, как обычно, надо поставить себе нативный ecm. Похоже, проект уже загнулся и сайт их не работает. Так что в SRC_URL будет линка на интернет архив. Но в любом случае это работает, так что переживать не о чем :)
Добавим ebuild в домашний оверлей:
# mkdir /usr/local/portage/app-cdr/
# mkdir /usr/local/portage/app-cdr/ecm
# vim ecm-1.0.0.ebuild

# Copyright 1999-2010 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: $

EAPI=2

inherit versionator
MY_P=$(delete_all_version_separators ${P})

DESCRIPTION="ECM prepares CD images (BIN, ISO, etc.) so that they'll compress far better"
HOMEPAGE="http://www.neillcorlett.com/ecm/"
SRC_URI="https://web.archive.org/web/20091021035854/http://www.neillcorlett.com/downloads/${MY_P}.zip"

LICENSE="GPL-2"
SLOT="0"
KEYWORDS="~amd64 ~x86"
IUSE=""

DEPEND="app-arch/unzip"

src_unpack() {
        mkdir "${S}"
        unzip -d "${S}" "${DISTDIR}"/${MY_P}.zip
}

src_compile() {
        gcc ${CFLAGS} -o ecm ecm.c || die "ecm compilation failed"
        gcc ${CFLAGS} -o unecm unecm.c || die "unecm compilation failed"
}

src_install() {
        dobin ecm unecm
        dodoc format.txt readme.txt
}                      

# chown -R portage:portage /usr/local/portage/
# repoman manifest
# eix update 
И установим его, плюс ещё bchunk, который тоже понадобиться
# emerge app-cdr/ecm app-cdr/bchunk
Теперь открываем какой-нибудь архив с сайта, и делаем следующее:
# unecm image.bin.ecm 
# bchunk <image.bin> <image.cue> <image_name>
# mount -o loop ./image_name.iso /media/something/
Готово!

См.также: https://github.com/developersu/loperOverlay

29 марта 2017 г.

loperIRCLogBot

7:47 Опубликовал Дмитрий Исаенко , Нет комментариев
loperIRCLogBot - очередной бот для записи логов на канале IRC. Написан на С. Использует слегка модифицированную libircclient. Он может заходить на 1 сервер, 1 канал и писать всё что там происходит. Может собираться под openWRT и наверное под много чего ещё. В общем все подробности чуть ниже. Этот пост (наверное) будет обновляться по мере выхода новых версий.
Написанное верно для версии: 1.1.
А теперь документация в виде FAQ.
Он работает под Windows?
Нет.

А если я его соберу под Windows?
Всё ещё нет.

OS X, BSD, AIX?
Не знаю, может быть. Попробуй и расскажи потом.

Как собирать?
$ make

Оно не работает, и тут нет ./configure. Что мне делать? Почему это происходит со мной?
Измени Makefile и ./files/Makefile. Они там простые. Я не особо разбираюсь в том, что есть ./configure поэтому его там нет.

При компиляции я вижу "warning:". Это норма?
Ну, вроде того. Я тоже иногда вижу подобное. Надо будет найти время и что-то с этим сделать.

Где исполняемый файл? Что запускать?
$ ./bin/loperIRCLogBot

Что с лицензиями?
Исходный код, который фактически является лишь файлом loperIRCLogBot.c распространяется под лицензией GNU General Public License v.3. Ты можешь прочесть текст лицензии в файле LICENSE, (распространяется вместе с кодом) или посетив сайт https://www.gnu.org/ (там он определённо должен быть). Также приложение использует (немного) модифицированную libircclient написанную George Yunaev. Libircclient распространяется под лицензией GNU Lesser General Public License v.3 или более поздней.

Можно ли (собрать и) использовать эту программу вместе с динамической библиотекой libircclient, которая у меня уже установлена?
Нет. Эта маленькая модификация, которая нужна для работы loperIRCLogBot не позволяет использовать оригинальную немодифицированную библиотеку. Посмотри на патч в директории ./files (в нём вся суть).

Где указывать настройки и как?
В файле ./bin/bot.conf. Ты можешь сгенерировать шаблон выполнив 
$ ./loperIRCLogBot -g
Файл настроек должен храниться (и будет сгенерирован) в той же директории, что и исполняемый файл. Пока это единственный вариант.
Из файла настроек приложение читает лишь первые 15 строк, так что удостоверься, что ты не прописал ничего важного ниже первых 15 строк.
Вот пример:
server:          irc.example.com
channel:         #channel
port:            6667
nick:            awesome_nick_here
username:        usrname
realname:        realname
password:        0 
maxNickLength:   30
logPath:         0 
link:            http://localhost:8080/logs/
reJoin:          yes
'server' должен быть установлен в описанном выше формате. Никаких irc://, слэшей и бэкслешей быть не должно.
'channel' это канал, к которому нужно подключиться. Должен начинаться с символа решетки '#'.
'port' порт сервера
'nick' ник на сервере. Ограничен параметром 'maxNickLength'. Т.е. длина ника не должна превышать число символов, определённое в maxNickLength.
'username'  имя пользователя, которое устанавливается вторым параметром в нике-сервера. Например nick!~[USERNAME]@ip_adress
'realname' это реальное имя. Не спрашивай, просто поставь тут что-то, ладно?
'password' это пароль от ника. Он передаётся серверу в виде '/nickserv IDENTIFY [password]'. Если установлен '0', то пароль не передаётся.
'maxNickLength' это максимальная длина ника. Различные серверы ограничивают максимальную длину ника. Значения в '30' символов тут будет достаточно. Максимально допустимое значение - 128, но я не уверен что тебе следует использовать максимальное значение, потому, что если сервер не поддерживает ники подобной длины, то программа может выполняться неправильно.
'logPath' путь к директории ./logs где хранятся логи. Если '0' то они все будут храниться  в той же папке, что и исполняемый файл. Обрати внимание, что путь должен быть абсолютным, а не относительным. Недопустимо указывать что-то типа ~/logs или ../../logs.
'link' это ссылка, которая будет служить ответом, когда кто-то на канале начинает своё сообщения с ника бота. Если '0', то в ответ никакой ссылки они не получат. Только смайлик.
'reJoin' определяет, следует ли автоматически заходить на канал после кика. Если 'yes' то следует, если 'no' то нет.

Есть ли какие-то ещё опции у приложения?
Да. Когда ты запускаешь приложение, ему можно передать следующие ключи:
-g, --genconf — создаёт шаблон конфигурационного файла. ОСТОРОЖНО! Это действие перезапишет существующий конфигурационный файл.
-n, --nomessage — подавляет вывод сообщений (на stdin, stderr).
-v, --version — показывает версию программы.
--help — показывает помощь по ключам.
Обратите внимание, что '-n' просто отключает вывод сообщений. Там нет всяких специфичных для демонов опций. Пока.

Можно как-то зайти больше чем на один канал?
Неа.

Если пропадёт соединение, сможет ли бот переподключиться? Если да, то когда это произойдёт?
Да. Через 20 минут. Возможно раньше. Зависит от активности на канале и пингов от сервера. Предсказание этого сейчас реализовано крайне криво.

29 августа 2016 г.

Vostro 5470: про Optimus и кнопки настройки яркости

5:32 Опубликовал Дмитрий Исаенко , , Нет комментариев
Сегодня будем разбираться, как избавиться от чёрного экрана при запуске SDDM (замена KDM) в Dell Vostro 5470. Тут я не ставлю целью добиться максимального энергосбережения, т.к. KDE всё равно использует для отрисовки графики OpenGL, ведь суть optimus - использовать для отрисовки 3D как раз nvidia чип, а не встроенный intel. Также мы рассмотрим тему оживления кнопок настроек яркости, а в конце ещё и заставим кнопку suspend уводить ноутбук в ждущий режим.

Итак, чтобы настроить работу Nvidia Optimus нужно, кроме того, что описано в wiki, добавить в настройки sddm строку modesetting. Эта фича не работает в случае, если прописать её в ~/.xinitrc как некоторые советуют. И это ключевой момент.
# vim /usr/share/sddm/scripts/Xsetup
xrandr --setprovideroutputsource modesetting NVIDIA-0
xrandr --auto
Касательно xorg.conf. В моём случае выглядит как-то так:
# cat /etc/X11/xorg.conf
Section "ServerLayout"
        Identifier "layout"
        Screen 0 "nvidia"
        Inactive "intel"
EndSection

Section "Device"
        Identifier "nvidia"
        Driver "nvidia"
        BusID "PCI:09:00:0"
EndSection

Section "Screen"
        Identifier "nvidia"
        Device "nvidia"
EndSection

Section "Device"
        Identifier "intel"
        Driver "modesetting"
        BusID "PCI:00:02:00"
        Option "AccelMethod"  "none"
EndSection
 
Section "Screen"
        Identifier "intel"
        Device "intel"
EndSection 
Теперь по драйверам. Используется проприетарные nvidia-drivers и интеловские драйвера. В make.conf имеется следующее:
VIDEO_CARDS="nvidia intel i965 modesetting"
Что касается настроек в ядре:
> Device Drivers > Graphics support >
<*> /dev/agpgart (AGP Support)  --->         ##есть подозрение, что это совершенно не нужно
      {*}   Intel 440LX/BX/GX, I8xx and E7x05 chipset support
-*- VGA Arbitration
(16)  Maximum number of GPUs
[*] Laptop Hybrid Graphics - GPU switching support
<*> Direct Rendering Manager (XFree86 4.1.0 and higher DRI support)  --->
      [*]   Enable legacy fbdev support for your modesetting driver
<M> Intel 8xx/9xx/G3x/G4x/HD Graphics
    Frame buffer Devices  --->
      [*] Enable Video Mode Handling Helpers
      [*] VESA VGA graphics support
-*- Backlight & LCD device support  --->
      <*>   Lowlevel LCD controls
      <*>     Platform LCD controls
      {*}   Lowlevel Backlight controls
      <*>     Generic (aka Sharp Corgi) Backlight Driver
    Console display driver support  --->
      -*- VGA text console   
      [*]   Enable Scrollback Buffer in System RAM
      (64)    Scrollback Buffer Size (in KB)      
      (80) Initial number of console screen columns      
      (25) Initial number of console screen rows  
      -*- Framebuffer Console support      
      -*-   Map the console to the primary display device
      [ ]   Framebuffer Console Rotation   
      [*]   Support for the Framebuffer Console Decorations
[*] Bootup logo  --->

Ну-с, вроде всё. Теперь перейдём к теме яркости. Почему-то у меня из коробки не работают кнопки увеличения и уменьшения яркости. Т.е. они, конечно, работают как кнопки, но яркость не меняют. Проверим для начала, определяет ли система нажатие на эти кнопки. Для этого запустим acpi_listen и пожмакаем. Должно появиться примерно следующее:
# acpi_listen 
video/brightnessdown BRTDN 00000087 00000000 K
video/brightnessdown BRTDN 00000087 00000000
video/brightnessup BRTUP 00000086 00000000
video/brightnessup BRTUP 00000086 00000000 K
^C
Если этого не произошло, идём смотреть ядро:
Device Drivers > X86 Platform Specific Device Drivers
    <*>   Dell Laptop Extras
    <*>   Dell Latitude freefall driver (ACPI SMO88XX)
    <M>   Intel PMC IPC Driver

Power management and ACPI options >
    [*] Suspend to RAM and standby         
    [*] Hibernation (aka 'suspend to disk')
    ()  Default resume partition           
    [ ] Opportunistic sleep                
    [ ] User space wakeup sources interface
    -*- Device power management core functionality
    [ ]   Power Management Debug Support   
    [ ] Enable workqueue power-efficient mode by default
    [*] ACPI (Advanced Configuration and Power Interface) Support  --->
      --- ACPI (Advanced Configuration and Power Interface) Support 
      [ ]   AML debugger interface (EXPERIMENTAL)
      [ ]   Deprecated power /proc/acpi directories
      [*]   Allow supported ACPI revision to be overriden
      < >   EC read/write access through /sys/kernel/debug/ec
      <*>   AC Adapter
      <*>   Battery
      {*}   Button
      {*}   Video
      {*}   Fan
      [*]   Dock
      <*>   Processor
      < >   Processor Aggregator
      <*>   Thermal Zone
      -*-   NUMA support
      [ ]   ACPI tables override via initrd
      [ ]   Debug Statements
      [ ]   PCI slot detection driver
      -*-   Container and Module Devices
      <*>   Smart Battery System
      < >   Hardware Error Device
      < >   Allow ACPI methods to be inserted/replaced at run time
      <M>   ACPI NVDIMM Firmware Interface Table (NFIT)
      [ ]   ACPI Platform Error Interface (APEI)
      [ ]   PMIC (Power Management Integrated Circuit) operation region support  ----
    [*] SFI (Simple Firmware Interface) Support  ----
        CPU Frequency scaling  --->        
        CPU Idle  --->                     
    [*] Cpuidle Driver for Intel Processors
        Memory power savings  --->
          <*> Intel chipset idle memory power saving driver
Это должно помочь.

Также, у вас должен быть в системе следующий путь:
/sys/class/backlight/intel_backlight/
В директории /sys/class/backlight вы также можете обнаружить под-директорию dell_backlight . Скорее всего вы что-то перемудрили с настройками. В целом - то, что эта папка есть, нет ничего страшного, ведь что там не меняй - подсведка меняться не будет.

Если кнопки определились, то пропишем правило скрипту ACPI. Тут привожу скрипт(ы) в полном варианте. Изменённые секции выделены красным.
# vim /etc/acpi/default.sh 
#!/bin/sh
# /etc/acpi/default.sh
# Default acpi script that takes an entry for all actions

set $*

group=${1%%/*}
action=${1#*/}
device=$2
id=$3
value=$4

log_unhandled() {
        logger "ACPI event unhandled: $*"
}

case "$group" in
        button)
                case "$action" in
                        power)
                                /etc/acpi/actions/powerbtn.sh
                                ;;

                        *)      log_unhandled $* ;;
                esac
                ;;

        ac_adapter)
                case "$value" in

                        *)      log_unhandled $* ;;
                esac
                ;;

        video)
                case "$action" in
                        brightnessdown)

                                /etc/acpi/actions/brightness.sh -

                                ;;

                        brightnessup)

                                /etc/acpi/actions/brightness.sh +

                                ;;

                        *)      log_unhandled $* ;;
                esac
                ;;

        *)      log_unhandled $* ;;
esac
Ну и сделаем для этого ещё один скрипт:
# vim /etc/acpi/actions/brightness.sh 

#!/bin/bash
bl_dev=/sys/class/backlight/intel_backlight/
step=10   #set 

case $1 in
        -) echo $(($(< $bl_dev/brightness) - $step)) >$bl_dev/brightness ;;
        +) echo $(($(< $bl_dev/brightness) + $step)) >$bl_dev/brightness ;;
esac
И тут вы можете заметить, что т.к. в acpi_listen одно нажатие кнопки определялось двумя ивентами ACPI, то и изменение яркости будет происходить с двойной силой. Итого, устанавливая step=10 он фактически будет отрабатывать 20-ку. Ну и ладно. Я вот ставил step=1 и сначала думал, что изменение яркости вообще не происходит. 10 - это норма ;)
# chmod +x /etc/acpi/actions/brightness.sh 

А чтобы два раза не вставать, настроим заодно и поведение кнопки sleep. Я постоянно пользуюсь утилитой pm-suspend и хотел бы отправлять в сон ноут по одному клику. Для начала установим пакет, содержащий pm-suspend.
# emerge sys-power/pm-utils
Посмотрим, как эта кнопка определяется системой:
# acpi_listen 
button/sleep SBTN 00000080 00000000 K
^C
Всё вроде хорошо. Осталось модифицировать наш скрипт.
# cat /etc/acpi/default.sh    
#!/bin/sh
# /etc/acpi/default.sh
# Default acpi script that takes an entry for all actions

set $*

group=${1%%/*}
action=${1#*/}
device=$2
id=$3
value=$4

log_unhandled() {
        logger "ACPI event unhandled: $*"
}

case "$group" in
        button)
                case "$action" in
                        power)
                                /etc/acpi/actions/powerbtn.sh
                                ;;

                        # if your laptop doesnt turn on/off the display via hardware
                        # switch and instead just generates an acpi event, you can force
                        # X to turn off the display via dpms.  note you will have to run
                        # 'xhost +local:0' so root can access the X DISPLAY.
                        #lid)
                        #       xset dpms force off
                        #       ;;

                        sleep)
                                /usr/sbin/pm-suspend
                                ;;

                        *)      log_unhandled $* ;;
                esac
                ;;

        ac_adapter)
                case "$value" in
                        # Add code here to handle when the system is unplugged
                        # (maybe change cpu scaling to powersave mode).  For
                        # multicore systems, make sure you set powersave mode
                        # for each core!
                        #*0)
                        #       cpufreq-set -g powersave
                        #       ;;

                        # Add code here to handle when the system is plugged in
                        # (maybe change cpu scaling to performance mode).  For
                        # multicore systems, make sure you set performance mode
                        # for each core!
                        #*1)
                        #       cpufreq-set -g performance
                        #       ;;

                        *)      log_unhandled $* ;;
                esac
                ;;

        video)
                case "$action" in
                        brightnessdown)

                                /etc/acpi/actions/brightness.sh -
                                ##echo $group -  $action - $device - $id - $id>> /tmp/1.sh

                                ;;

                        brightnessup)

                                /etc/acpi/actions/brightness.sh +
                                ##echo $group -  $action - $device - $id - $id>> /tmp/1.sh

                                ;;

                        *)      log_unhandled $* ;;
                esac
                ;;

        *)      log_unhandled $* ;;
esac

На этом всё.

Ссылки:
https://wiki.archlinux.org/index.php/acpid
https://wiki.gentoo.org/wiki/ACPI