О чем речь (цитаты хайпующего в настоящий момент в лентах новостей deepseekа):
- Quansheng UV-K5 — это недорогая портативная радиостанция с поддержкой диапазонов VHF и UHF, широким функционалом, возможностью модификации прошивки и программирования, популярная среди радиолюбителей и профессионалов.
- Прошивка — это изменение или обновление встроенного программного обеспечения радиостанции, которое влияет на ее базовую функциональность.
- Программирование — это настройка параметров работы радиостанции в рамках ее текущей прошивки.
В сети полно инструкций про Windows, я коротко расскажу про работу со станцией под Linux
1. Кабель.
Для прошивки и программирования подходит один и тот же кабель. Самый обычный, совместимый с популярными аналоговыми Баофенгами вроде Baofeng BF-888S.
Кабель работает под Linux из коробки, определяется как USB Serial, цитата из вывода команды dmesg:
[ 69.062664] usb 3-1: new full-speed USB device number 4 using xhci_hcd
...
[ 69.274720] usb 3-1: ch341-uart converter now attached to ttyUSB0
Если у пользователя не хватает прав для работы с портом, скорее всего поможет добавление в группу dialout (команда sudo usermod -a -G dialout $USER) и последующий ребут.
2. Прошивка
Технологии дошли до того, что прошить радиостанцию (в том числе под Linux) можно прямо из браузера, но душа ретрограда требует консольной утилиты, и такая есть — k5tool.
Утилита на писана на C#, для ее работы нужно накатить в систему пакет mono-runtime (для debian-подобных дистрибутивов — командой sudo apt install mono-runtime), после чего утилиту можно запускать скриптом k5tool из корня скачанного git-репозитория.
Порт ttyUSB0 утилита распознает сама, указывать его требуется.
Далее нужно выбрать прошивку. Я выбрал прошивку egzumer, как достаточно функциональную, с хорошей документацией и прозрачной разработкой на githubе.
Перед манипуляциями рекомендуется забекапиться.
- Сделаем бекап калибровки:
k5tool -rdee 0x1E00 0x0200 eeprom-calib.raw - И бекап памяти eeprom целиком:
k5tool -rdee - После чего прошьем прошивку (это действие нужно проводить в особом режиме — рацию нужно включить с зажатой клавишей PTT, все остальные действия в статье работают на включенной обычным образом рации):
k5tool -wrflash ./egzumer_v0.22.packed.bin
3. Программирование
Для программирования радиостанций под Linux есть популярная графическая программа — chirp. В сети много информации про плагины к ней для различных кастомных прошивок, но в первую очередь — смотрите документацию на выбранную прошивку. Так, с прошивкой egzumer, все просто — свежий chirp поддерживает ее из коробки (уже имеет встроенный плагин), в том числе позволяет настраивать специфичные для этой кастомной прошивки параметры. То, что chirp распознал прошивку, можно увидеть в окне Radio -> Download from radio... по надписи "Quansheng UV-K5 egzumer".
Официальная документация chirp рекомендует использовать для установки менеджер пакетов pipx, я же ограничился Debianовским aptом:
- Установил зависимости (полный список зависимостей см. в requirements.txt):
sudo apt install python3-wxgtk4.0 python3-serial python3-requests python3-suds python3-yattag python3-lark - После чего chirp можно запускать командой ./chirpwx.py из корня скачанного git-репозитория chirp.
Ссылки по теме UV-K5
Ключевые слова: Радиостанция, Debian, Linux, Quansheng
Пожалуй, главный совет: покупая устройство Xiaomi, которое вы хотите использовать в составе умного дома, крепитесь! Теперь расскажу о своем опыте с парой таких устройств. Все манипуляции проводил со стандартным (не модифицированным) приложением Mi Home из play-маркета.
Xiaomi Smartmi Humidifier 2
- Стандартной рекомендации для китайских устройств Xiaomi — выставить материковый Китай в приложении Mi Home, для увлажнителя недостаточно. Ошибка с неподдерживаемым регионом остается.
- Добавить аппарат в Mi Home можно только установив на телефоне часовой пояс Китая. После этого аппарат остается в приложении Mi Home, обновляются его параметры (температура, влажность), но управлять им через Mi Home можно только с выставленным китайским временем, что, конечно, не удобно.
- Спасет отца демократии приложение Умный дом от Яндекса, с которым нужно связать аккаунт Mi Home. Тогда увлажнитель появится среди устройств умного дома Яндекса и через приложение Яндекса (а заодно и через Алису) им можно будет управлять и из российского часового пояса.
Xiaomi Mijia Smart Pet Feeder 2 (MJWSQ02) CN
- Безуспешно пробовал — отключена 5Ггц сеть (кормушка поддерживает только 2.4 Ггц), сменены SSID и пароль на короткие слова из маленьких латинских букв, сменен режим Wifi (на фиксированный 802.11n), перебраны режимы WPA/WPA2/WPA+WPA2, шифрования AES/TKIP, DNS-сервера Яндекса, Google и Cloudflare, разные каналы — ничего из этого не помогло.
- Попробовал раздать интернет с телефона — и, о чудо, кормушка успешно подключилась!
- Виноват провайдер или роутер? Воткнул в ПК, который подключен к роутеру, Wifi-свисток и раздал с него интернет. С ним кормушка также заработала! Перепроверил — подключился с ноута к этой же импровизированной сети — ПК не добавил каких-то новых левых DNS-серверов, используется DNS провайдера. Значит провайдер не виноват, все дело — в роутере.
- Роутер у меня — SmartBox Turbo+ от Beeline, хотя провайдер давно уже другой, но роутеры от Билайна — универсальны и хороши по отношению цена/качества, менять роутер не хотелось. Решил установить на него OpenWRT. Обращу внимание, что при установке OpenWRT советуйтесь в первую очередь с официальным вики. Например, для моего роутера в сети полно устаревших инструкций, которые предлагают менять загрузчик, меж тем как технологии уже дошли до того, что OpenWRT в два клика шьется через стоковый билайновский web-интерфейс.
- Затем настроил новую прошивку (см. неплохую статью про минимальную настройку).
- И вуоля, кормушка полноценно заработала! Т.е. имеется какая-то несовместимость кормушки со стоковой прошивкой билайновского роутера.
- Кормушка автоматом подхватывается и в приложении Умного дома Яндекса, хотя там возможности работы с ней сильно ограничены по сравнению с Mi Home.
2020-й год, Yandex.Browser версии 19.12.3.332, Debian 10 Buster и сайт с DRM-видео, на примере Кинопоиск HD.
Трейлеры воспроизводятся, т.е. можно предположить, что с кодеками все в порядке. Однако при попытке воспроизведения купленного фильма видим: «Пожалуйста, подождите, идет настройка браузера для проигрывания видео. Для ручной настройки ознакомьтесь с инструкцией».
В своей инструкции Яндекс и авторы других прошлогодних инструкций предлагают распаковать libwidevinecdm.so в /opt/yandex/browser-beta/. Не помогает! Утилита lsof показывает, что файл libwidevinecdm.so браузером не открыт, а с помощью strace выясняется, что Яндекс.Браузер ищет библиотеку в каталоге /opt/yandex/browser-beta/WidevineCdm/_platform_specific/.
Вот исправленная рабочая версия инструкции:
- Проверьте версию браузера. Если версия ниже 18.11.1, обновите браузер;
- Скачайте архив плагина Widevine Content Decryption Module;
- Извлеките из архива файл libwidevinecdm.so;
- Разместите его по пути
/opt/yandex/browser-beta/WidevineCdm/_platform_specific/linux_x64/ ; - Измените права доступа к файлу libwidevinecdm.so на 644.
Блюдя тематику (GNU/Linux) блога сравнивались только мобильные приложения для Android и десктопные web-интерфейсы.
В сравнении не участвует Spotify из-за своей скорости принятия корпоративных решений, потому что он уже более пяти лет скоро-скоро запустится в России. Также не участвуют сервисы Google из-за вечного бардака. Не понятно, на какой сервис можно положиться (Google Play Music/YouTube Music), какой выживет, а какой будет закопан, как были закопаны Picasa, Блокнот, Google Reader и так далее.
| Параметр для сравнения | Яндекс.Музыка | Deezer |
|---|---|---|
| Синхронизация текущего списка воспроизведения | В Deezer действительно можно прийти домой и одним кликом запустить на ПК то, что только играло в наушниках на телефоне. Причем возможность эта отключается в настройках. | |
| Поиск | Поиск у Яндекса лучше, даже по иностранной музыке | Поиск уступает Яндексу |
| Присутствие альбомов | Явно больше альбомов русских исполнителей. Из зарубежных исполнителей по сравнению с Deezer не удалось найти (понятно, что выборка очень субъективна) только пару альбомов каверов. | Немного меньше русской музыки. Что хуже: иногда неправильно проставлены даты выходов альбомов русских исполнителей, кажется вместо дат выходов указаны даты добавления в каталог (лейбла/самого Deezerа/...) |
| Гибкость рекомендаций | У Яндекса всегда под рукой радио по исполнителю, альбому (чего у Deezer нет), треку, субъективно разнообразней тематические подборки | Миксы только по исполнителю и отдельному треку |
| Качество | Mp3 320 kb/s | Mp3 320 kb/s на обычных подписках и flac на подписке HiFi за отдельную плату |
| Интерфейс Андроид-приложения | Отличный | Субъективно приложение для Андроид выглядит неаккуратным |
| Web-интерфейс | Не идеальный, очередь воспроизведения не под рукой | Отличный в целом, явно указывается качество в kb/s |
| Запуск/остановка воспроизведения медиаклавишей клавиатуры | UPD от марта 2020 г.: Яндекс таки починил прошлогодний баг — теперь все ОК |
Просто работает |
| Дополнительные плюшки подписки | Скидки на различные сервисы Яндекса | Доп. плюшек нет |
| Семейная подписка | Дороже (299 руб.), меньшее количество человек (4) | Дешевле (255 руб.), большее количество человек (6), но есть и минус — на семью единый аккаунт с единым логином-паролем с шестью профилями, т.е. подписку получится делить лишь между людьми, которым вы полностью доверяете (действительно подписка именно семейная) |
| Цена индивидуальной версии | 169₽/месяц, 1690₽/год, но есть акционные варианты за 99₽/месяц и 990₽/год | 169₽/месяц, 1690₽/год |
Ключевые слова: Яндекс.Музыка, Android, Deezer, Google, Google Music, Spotify, YouTube
sudo chown user:user /dev/input/by-id/usb-Logitech_USB_Keyboard-event-kbdДалее для снятия дампа в hex-виде предлагаю такую замысловатую команду:
sleep 1 ; hexdump -e '24/1 " %02X" "\n"' /dev/input/by-id/usb-Logitech_USB_Keyboard-event-kbd /dev/input/by-id/usb-Logitech_USB_Keyboard-event-kbd & sleep 5 ; pkill hexdumpЗдесь через 1 секунду (sleep 1, чтобы нажатие Enter не попало в дамп) начнется захват данных с клавиатуры, будет выводиться по 24 байта на строку (24/1), еще через 5 секунд (sleep 5) захват будет остановлен (pkill hexdump, чтобы мы смогли вновь пользоваться клавиатурой после снятия дампа).
sleep 1 ; hexdump -e '24/1 " %02X" "\n"' /dev/input/by-id/usb-Logitech_USB_Keyboard-event-kbd /dev/input/by-id/usb-Logitech_USB_Keyboard-event-kbd & sleep 5 ; pkill hexdump [1] 14693 15 87 FC 5D 00 00 00 00 40 CA 07 00 00 00 00 00 04 00 04 00 3A 00 07 00 15 87 FC 5D 00 00 00 00 40 CA 07 00 00 00 00 00 01 00 3B 00 01 00 00 00 15 87 FC 5D 00 00 00 00 40 CA 07 00 00 00 00 00 00 00 00 00 00 00 00 00 15 87 FC 5D 00 00 00 00 3E C4 08 00 00 00 00 00 04 00 04 00 3A 00 07 00 15 87 FC 5D 00 00 00 00 3E C4 08 00 00 00 00 00 01 00 3B 00 00 00 00 00 15 87 FC 5D 00 00 00 00 3E C4 08 00 00 00 00 00 00 00 00 00 00 00 00 00 16 87 FC 5D 00 00 00 00 37 C4 08 00 00 00 00 00 04 00 04 00 3A 00 07 00 16 87 FC 5D 00 00 00 00 37 C4 08 00 00 00 00 00 01 00 3B 00 01 00 00 00 16 87 FC 5D 00 00 00 00 37 C4 08 00 00 00 00 00 00 00 00 00 00 00 00 00 16 87 FC 5D 00 00 00 00 75 60 09 00 00 00 00 00 04 00 04 00 3A 00 07 00 16 87 FC 5D 00 00 00 00 75 60 09 00 00 00 00 00 01 00 3B 00 00 00 00 00 16 87 FC 5D 00 00 00 00 75 60 09 00 00 00 00 00 00 00 00 00 00 00 00 00
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
Структура имеет размер 24 байта (на 64-битной системе), именно поэтому выше мы выводили дамп по 24 байта на строку.Первое поле структуры input_event — также структура, timeval. Использовалась 64-битная система, поэтому timeval состояла из двух 64-битных (по 8 байт) целых чисел — значения секунд и микросекунд. По первым 8 байтам каждой строки дампа видно, как меняется значение секунд (почти не меняется, только первый байт поменялся с 15 на 16, когда секунда "тикнула"). Следующие 8 байт каждой строки отвечают за микросекунды, а потому меняются хаотично (нажатие/отпускание кнопки F1 приходилось на произвольную долю секунд).
А самое интересное происходит в последних восьми байтах каждой строки дампа. Сначала идет двухбайтное поле type — его значение менялось с 4 (EV_MSC — некая дополнительная информация, разбирать ее не будем) на 1 (EV_KEY — непосредственно событие кнопки клавиатуры) и 0 (EV_SYN — разделитель).
Разберем события EV_KEY, их было всего 4:
15 87 FC 5D 00 00 00 00 40 CA 07 00 00 00 00 00 01 00 3B 00 01 00 00 00 15 87 FC 5D 00 00 00 00 3E C4 08 00 00 00 00 00 01 00 3B 00 00 00 00 00 16 87 FC 5D 00 00 00 00 37 C4 08 00 00 00 00 00 01 00 3B 00 01 00 00 00 16 87 FC 5D 00 00 00 00 75 60 09 00 00 00 00 00 01 00 3B 00 00 00 00 00За полем event следует также двухбайтное поле code — код клавиши. В нашем случае — 3B в hex или 59 в десятичном виде. Смотрим файл input-event-codes.h. Действительно, 59 — это код клавиши F1.
И, наконец, последнее поле — четырехбайтное value. Здесь все просто: значение 1 соответствовало нажатию клавиши F1, значение 0 — отпусканию.
Первый раз после выключения аутентификации и повторного включения роутер мой продолжил работать как нужно, но через несколько месяцев (возможно после кратковременного отключения электричества?) проблема повторилась. На этот раз кратковременное отключение аутентификации ситуацию не исправляло. Без аутентификации — Wi-Fi работает, с аутентификацией — нет.
В сети было найдено несколько обсуждений описанной проблемы, но ответ содержал лишь один тред (да еще и на второй странице) на форуме DD-WRT. Проблема в том, что логи исчерпали место на устройстве!
Решение просто как 2x2: Веб-интерфейс → Status → WAN → Data Administration → Delete.
Получается: либо типы всех потенциально загружаемых из файла объектов должны быть заранее зарегистрированы, т.е. все (обязательно все) и явно (в каком-то виде) типы должны быть прописаны в коде разработчиком, либо же нужен неизящный и непортабельный механизм вроде поиска в таблице символов исполняемого файла для имени каждого подгружаемого из файла класса соответствующего метода foo_bar_get_type... и вызова этого метода.Все почти так и есть, но в коде GtkBuilder случайно был найден довольно изящный и портабельный механизм поиска foo_bar_get_type. Идея простая: нужно создать объект GModule не для динамически загружаемого модуля, а для самой программы. Для чего в метод g_module_open(const gchar *file_name, GModuleFlags flags) вместо имени файла нужно передать NULL:
file_name — the name of the file containing the module, or NULL to obtain a GModule representing the main program itself.Пример на Vala на основе идей из GtkBuilder (механизм получения типа по имени класса иллюстрирует функция type_from_name_lazy):
public abstract class Animal : Object
{
public abstract string to_string();
}
public class Cow : Animal
{
public override string to_string()
{
return "I'm a cow!";
}
}
public class Dog : Animal
{
public override string to_string()
{
return "I'm a dog!";
}
}
int main()
{
Animal a;
Type type;
a = new Dog(); //< Явно создаем Dog, имя типа зарегистрировано в системе типов.
print("1) %s\n", a.to_string());
if((type = Type.from_name("Dog")) != Type.INVALID)
{
a = Object.new(type) as Animal;
print("2) %s\n", a.to_string());
}
else
print("2) Failed to get type!\n");
if((type = Type.from_name("Cow")) != Type.INVALID)
{
a = Object.new(type) as Animal;
print("3) %s\n", a.to_string());
}
else
{
print("3) Failed to get type! Trying type_from_name_lazy...\n");
if((type = type_from_name_lazy("Cow")) != Type.INVALID)
{
a = Object.new(type) as Animal;
print("3) %s\n", a.to_string());
}
else
print("3) Failed to get type!\n");
}
return 0;
}
[CCode (has_target = false)]
delegate Type TypeGetFunc();
Type type_from_name_lazy(string name)
{
void *func = null;
if(Module.open(null, 0).symbol(ascii_camel_to_snake(name) + "_get_type", out func))
return ((TypeGetFunc)func)();
else
return Type.INVALID;
}
string ascii_camel_to_snake(string input)
{
var sb = new StringBuilder();
for(int i = 0; input[i] != '\0'; i++)
{
if(
( input[i].isupper() && i > 0 && !input[i - 1].isupper() ) ||
( (i > 2 && input[i].isupper()) && input[i - 1].isupper() && input[i - 2].isupper() )
)
sb.append_c('_');
sb.append_c(input[i].tolower());
}
return sb.str;
}
Вывод программы: 1) I'm a dog!И немного про Windows
2) I'm a dog!
3) Failed to get type! Trying type_from_name_lazy...
3) I'm a cow!
Лично у меня данный пример как есть в Windows не заработал (собирал компилятором mingw). Не удавалось найти указатель на функцию cow_get_type. Опытным путем выяснилось, что работает такой механизм при соблюдении двух условий:
- Объект должен быть реализован в отдельной dll;
- До попытки поиска указателя должна быть вызвана хотя бы одна функция из этой dll.
Если попробуем просто слинковать lib и dll-файлы из MKL со своим проектом (передав их линковщику через gcc -l), то увидим ошибку вида:
...
C:\Views\dockd_headers\lip__mgls_nt\mgls_nt\src\release\mgls64.lib(./x64/release/lpclsdll.obj):(.text[?messageBox@@YAXPEAD@...]+0xcb): undefined reference to `__security_check_cookie'
C:\Views\dockd_headers\lip__mgls_nt\mgls_nt\src\release\mgls64.lib(./x64/release/lpclsdll.obj):(.xdata[$unwind$?messageBox@@YAXPEAD@...]+0xc): undefined reference to `__GSHandlerCheck'
...
Как видно, линковщик не может найти символы __GSHandlerCheck и __security_check_cookie.
На sourceforge.net есть обсуждение, где приведены вышеобозначенные ошибки и даже есть идеи, в какую сторону копать.
Я же расскажу, что делать, по шагам:
- Искомые символы можно найти в библиотеках libcmt.lib и BufferOverflowU.lib соответственно. В свою очередь библиотеки найти можно в составе различных девелоперских продуктов от Microsoft, в том числе бесплатных. Я взял WinDDK 7600.16385.1.
- Просто так слинковать их со своим проектом опять же не получится, т.к. libcmt.lib библиотека жирная и будет конфликтовать с внутренностями mingw:
... C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/5.2.0/../../../../x86_64-w64-mingw32/lib/../lib/libmingw32.a(lib64_libmingw32_a-mingw_helpers.o):mingw_helpers.c:(.text+0x0): multiple definition of `_decode_pointer' C:/msys64/home/user/test/libs/libtest/../mkl/lib/libcmt.lib(d:/5359.obj.amd64fre/minkernel/crts/crtw32/startup/mt/objfre/amd64/tidtable.obj):(.text[_decode_pointer]+0x0): first defined here C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/5.2.0/../../../../x86_64-w64-mingw32/lib/../lib/libmingw32.a(lib64_libmingw32_a-mingw_helpers.o): In function `_encode_pointer': C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/mingw_helpers.c:26: multiple definition of `_encode_pointer' C:/msys64/home/user/test/libs/libtest/../mkl/lib/libcmt.lib(d:/5359.obj.amd64fre/minkernel/crts/crtw32/startup/mt/objfre/amd64/tidtable.obj):(.text[_encode_pointer]+0x0): first defined here C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/5.2.0/../../../../x86_64-w64-mingw32/lib/../lib/libmingw32.a(lib64_libmingw32_a-pesect.o): In function `_ValidateImageBase': C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/pesect.c:31: multiple definition of `_ValidateImageBase' C:/msys64/home/user/test/libs/libtest/../mkl/lib/libcmt.lib(d:/5359.obj.amd64fre/minkernel/crts/crtw32/misc/mt/objfre/amd64/pesect.obj):(.text[_ValidateImageBase]+0x0): first defined here C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/5.2.0/../../../../x86_64-w64-mingw32/lib/../lib/libmingw32.a(lib64_libmingw32_a-pesect.o): In function `_FindPESection': C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/pesect.c:51: multiple definition of `_FindPESection' C:/msys64/home/user/test/libs/libtest/../mkl/lib/libcmt.lib(d:/5359.obj.amd64fre/minkernel/crts/crtw32/misc/mt/objfre/amd64/pesect.obj):(.text[_FindPESection]+0x0): first defined here C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/5.2.0/../../../../x86_64-w64-mingw32/lib/../lib/libmingw32.a(lib64_libmingw32_a-pesect.o): In function `_IsNonwritableInCurrentImage': C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/pesect.c:173: multiple definition of `_IsNonwritableInCurrentImage' C:/msys64/home/user/test/libs/libtest/../mkl/lib/libcmt.lib(d:/5359.obj.amd64fre/minkernel/crts/crtw32/misc/mt/objfre/amd64/pesect.obj):(.text[_IsNonwritableInCurrentImage]+0x0): first defined here C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/5.2.0/../../../../x86_64-w64-mingw32/lib/../lib/libmsvcrt.a(dyyns00201.o):(.text+0x0): multiple definition of `_errno' C:/msys64/home/user/test/libs/libtest/../mkl/lib/libcmt.lib(d:/5359.obj.amd64fre/minkernel/crts/crtw32/dos/mt/objfre/amd64/dosmap.obj):(.text[_errno]+0x0): first defined here ... - Решим вопрос, взяв из libcmt.lib только нужный нам obj-файл. Называется он gshandler.obj, что логично. Для извлечения его из lib-файла нам все же понадобится Visual Studio (я использовал старенькую 2005-ю).
- Открываем Visual Studio Command Prompt.
- Получаем список obj-файлов в libcmt.lib с помощью утилиты lib.exe:
lib /list libcmt.lib > libcmt.txt
- В получившемся libcmt.txt ищем полный путь к gshandler.obj, у меня он выглядел так:
d:\5359.obj.amd64fre\minkernel\crts\crtw32\misc\mt\objfre\amd64\gshandler.obj
- Теперь можем извлечь его из lib-файла:
lib /extract:d:\5359.obj.amd64fre\minkernel\crts\crtw32\misc\mt\objfre\amd64\gshandler.obj libcmt.lib
- Мне показалось удобным и стройным завернуть извлеченный obj-файл в персональный lib-файл:
lib /out:gshandler.lib gshandler.obj
- Готово! Линковка проекта someproj с базовым набором dll и lib-файлов MKL в CMake-файле выглядит примерно так:
set(MKL_LIBRARIES mkl_core mkl_avx mkl_sequential mkl_intel_lp64_dll BufferOverflowU gshandler) target_link_libraries(someproj ${MKL_LIBRARIES})
Идея красивая, но здесь мы сталкиваемся с одним ограничением системы типов GObject (ограничением вполне естественным, т.к. написан GObjecct на Си и вся поддержка ООП реализована на уровне библиотеки): чтобы создать экземпляр класса по имени класса нужно, чтобы этот класс уже был зарегистрирован в системе типов GObject. Т.е. для типа FooBar должен быть заранее вызван метод foo_bar_get_type непосредственно или опосредованно (например, через метод создания вроде foo_bar_new).
Получается: либо типы всех потенциально загружаемых из файла объектов должны быть заранее зарегистрированы, т.е. все (обязательно все) и явно (в каком-то виде) типы должны быть прописаны в коде разработчиком, либо же нужен неизящный и непортабельный механизм вроде поиска в таблице символов исполняемого файла для имени каждого подгружаемого из файла класса соответствующего метода foo_bar_get_type (см. тему на stack overflow) и вызова этого метода.
UPDATE: Найден довольно изящный и портабельный способ поиска метода foo_bar_get_type, см. Создание объектов GObject по имени типа. Часть 2.
Пример на Vala ниже иллюстрирует описанную проблему. Мы можем создать объект типа Dog по имени, т.к. выше уже создан объект типа Dog и класс зарегистрирован в системе типов. Создание объекта типа Cow же завершается неудачей (см. вывод программы после исходного кода).
public abstract class Animal : Object
{
public abstract string to_string();
}
public class Cow : Animal
{
public override string to_string()
{
return "I'm a cow!";
}
}
public class Dog : Animal
{
public override string to_string()
{
return "I'm a dog!";
}
}
int main()
{
Animal a;
Type type;
a = new Dog(); //< Явно создаем Dog, здесь класс и регистрируется в системе типов.
print("1) %s\n", a.to_string());
if((type = Type.from_name("Dog")) != 0)
{
a = Object.new(type) as Animal;
print("2) %s\n", a.to_string());
}
else
print("2) Failed to get type!\n");
if((type = Type.from_name("Cow")) != 0)
{
a = Object.new(type) as Animal;
print("3) %s\n", a.to_string());
}
else
print("3) Failed to get type!\n");
return 0;
}
Вывод программы: 1) I'm a dog!
2) I'm a dog!
3) Failed to get type!
Чтобы сохранять после перезапуска программы список фильтров и их параметры, была идея сделать некий обобщенны код, не зависимый от конкретной задачи, который бы сохранял объекты Gobject в файлы (типы объектов и значения свойств) и потом воссоздавал последовательность объектов по этим файлам.
В качестве "proof of concept" был написан код, клонирующий объект Gobject. Функция g_object_clone возвращает копию объекта, переданного ей в качестве параметра src. Код основан на примере со StackOverflow. Обращаю внимание, что полноценно копируются только простые свойства, для boxed-типов копируется указатель с инкрементированием счетчика ссылок.
Код gobject_clone.c:
// gcc `pkg-config --libs --cflags glib-2.0 gobject-2.0` gobject_clone.c point.c -o gobject_clone #include <glib-object.h> #include "point.h" #include <stdio.h> #include <string.h> //< For strcmp.В качестве объектов в примере взят простенький класс Point с гитхаба за авторством Jacob Gelbman. Код point.h:// See http://stackoverflow.com/questions/3003655/is-there-a-good-way-to-copy-a-gtk-widget?answertab=votes#tab-top static GObject *g_object_clone(GObject *src) { GObject *dst; GParameter *params; GParamSpec **specs; guint n, n_specs, n_params; specs = g_object_class_list_properties(G_OBJECT_GET_CLASS(src), &n_specs); params = g_new0(GParameter, n_specs); n_params = 0; for (n = 0; n < n_specs; ++n) if((specs[n]->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE) { params[n_params].name = g_intern_string(specs[n]->name); g_value_init(¶ms[n_params].value, specs[n]->value_type); g_object_get_property(src, specs[n]->name, ¶ms[n_params].value); ++n_params; } dst = g_object_newv(G_TYPE_FROM_INSTANCE(src), n_params, params); g_free(specs); g_free(params); return dst; } // Print, clear, print again. static void psp (Point *p) { g_print("OBJECT %p\n", p); g_print(" Before clearing:\n"); point_print(p); g_signal_emit_by_name(p, "clear"); g_print(" After clearing:\n"); point_print(p); g_print(" ...\n"); } int main(int argc, char **argv) { g_type_init(); GArray *array = g_array_new(FALSE, FALSE, sizeof(gint)); gint val1 = 33, val2 = 44; g_array_append_val(array, val1); g_array_append_val(array, val2); g_array_append_val(array, val1); GObject *p1 = g_object_new(TYPE_POINT, "x", 5, "y", 10, "a", array, NULL); GObject *p2 = g_object_clone(p1); //< Note: garray uses ref/unref for copy/free. psp(POINT(p1)); g_print("Appending value '%d' to array.\n", val2); g_print(" ...\n"); g_array_append_val(array, val2); psp(POINT(p2)); g_clear_object(&p1); g_clear_object(&p2); return 0; }
// https://github.com/zorgnax/gobject-examples/tree/master/02-point-inheritance #ifndef __POINT_H__ #define __POINT_H__ #include <glib.h> #include <glib-object.h>Код point.c:#define TYPE_POINT (point_get_type ()) #define POINT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_POINT, Point)) #define POINT_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST ((cls), TYPE_POINT, PointClass)) #define IS_POINT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_POINT)) #define IS_POINT_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE ((cls), TYPE_POINT)) #define POINT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_POINT, PointClass)) typedef struct _PointClass PointClass; typedef struct _Point Point; struct _PointClass { GObjectClass parent; void (*clear) (Point *self); }; struct _Point { GObject parent; gint x; gint y; GArray *array; }; GType point_get_type(void); void point_print(Point *self); #endif /*__POINT_H__*/
// https://github.com/zorgnax/gobject-examples/tree/master/02-point-inheritance #include <glib-object.h> #include "point.h"G_DEFINE_TYPE(Point, point, G_TYPE_OBJECT); enum { PROP_0, PROP_ARRAY, PROP_X, PROP_Y }; enum { CLEAR, LAST_SIGNAL }; static guint point_signals[LAST_SIGNAL] = {0}; void point_print (Point *self) { g_print(" x: %d, y: %d\n", self->x, self->y); g_print(" ar:"); if(self->array) { guint i; for(i = 0; i < self->array->len; i++) g_print(" %d", g_array_index(self->array, gint, i)); } else g_print("null"); g_print("\n"); } static void point_clear (Point *self) { g_object_set(self, "x", 0, "y", 0, "a", NULL, NULL); } static void point_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) { Point *point = POINT(obj); switch (prop_id) { case PROP_ARRAY: g_value_set_boxed(value, point->array); break; case PROP_X: g_value_set_int(value, point->x); break; case PROP_Y: g_value_set_int(value, point->y); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); break; } } static void point_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) { Point *point = POINT(obj); switch (prop_id) { case PROP_ARRAY: if(point->array) g_array_unref(point->array); point->array = g_value_dup_boxed(value); break; case PROP_X: point->x = g_value_get_int(value); break; case PROP_Y: point->y = g_value_get_int(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); break; } } static void point_finalize(GObject *point) { if(POINT(point)->array) g_array_unref(POINT(point)->array); G_OBJECT_CLASS(point_parent_class)->finalize(point); } static void point_class_init (PointClass *cls) { GObjectClass *g_object_class = G_OBJECT_CLASS(cls); GParamSpec *array_param; GParamSpec *x_param; GParamSpec *y_param; g_object_class->get_property = point_get_property; g_object_class->set_property = point_set_property; g_object_class->finalize = point_finalize; cls->clear = point_clear; array_param = g_param_spec_boxed( "a", "a", "some int array", G_TYPE_ARRAY, G_PARAM_READWRITE); x_param = g_param_spec_int( "x", "x", "x loc of point", INT_MIN, /* => */ INT_MAX, 0, G_PARAM_READWRITE); y_param = g_param_spec_int( "y", "y", "y loc of point", INT_MIN, /* => */ INT_MAX, 0, G_PARAM_READWRITE); g_object_class_install_property( g_object_class, PROP_ARRAY, array_param); g_object_class_install_property( g_object_class, PROP_X, x_param); g_object_class_install_property( g_object_class, PROP_Y, y_param); point_signals[CLEAR] = g_signal_new( "clear", /* signal_name */ TYPE_POINT, /* itype */ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, /* signal_flags */ G_STRUCT_OFFSET(PointClass, clear), /* class_offset */ NULL, /* accumulator */ NULL, /* accu_data */ g_cclosure_marshal_VOID__VOID, /* c_marshaller */ G_TYPE_NONE, /* return_type */ 0); /* n_params */ } static void point_init(Point *point) { point->array = NULL; }
Ниже пример на питоне. Отмечу, что обязательно нужно класть в label_widget GtkBox, а уже в него складывать свои активные виджеты. Функцию hacked_idle можно объявить один раз и использовать для всех Expander'ов.
import gi, sys
from gi.repository import Gtk, GLib
def hacked_idle(expander):
hbox = expander.get_label_widget()
expander.set_label_widget(None)
expander.set_label_widget(hbox)
return False
def expander_realize_cb(widget):
GLib.idle_add(hacked_idle, widget)
return False
class Test:
def __init__(self):
window = Gtk.Window()
window.set_default_size(640, 480)
window.connect_after("destroy", self.destroy)
exp = Gtk.Expander()
exp.connect("realize", expander_realize_cb)
exp.set_expanded(True)
window.add(exp)
hbox = Gtk.Box(Gtk.Orientation.HORIZONTAL, 5)
exp.set_label_widget(hbox)
check = Gtk.CheckButton()
check.set_label("Hi, There!!");
hbox.add(check)
switch = Gtk.Switch()
hbox.add(switch)
big_label = Gtk.Label()
big_label.set_markup("<big>Hello, World!!!</big>")
exp.add(big_label)
window.show_all()
def destroy(window, self):
Gtk.main_quit()
def main():
app = Test()
Gtk.main()
if __name__ == "__main__":
sys.exit(main())
Ключевые слова: Gtk, GtkCheckButton, GtkExpander
$ORIGIN используется в RPATH (...) и соответствующий каталог невозможно определить из-за отсутствия подкаталога DEBIAN в корне дерева сборки пакетаИли, для CLANG=C:
CPackDeb: dpkg-shlibdeps: dpkg-shlibdeps: warning: $ORIGIN is used in RPATH of ... and the corresponding directory could not be identified due to lack of DEBIAN sub-directory in the root of package's build treeБеда в том, что каталог "debian" есть, но название его состоит из маленьких букв, а не больших, как того хочет dpkg-shlibdeps.
Есть простенький патч для модуля CPackDeb.cmake, решающий эту проблему.
Однако, если нет возможности пропатчить CMake, то обойти проблему можно следующим, хоть и довольно грязным, костылем: прописать установку какого-нибудь файла, можно даже пустого, в каталог /DEBIAN:
install(FILES dirty_fix.txt DESTINATION /DEBIAN)Вполне допускаю, что на основе этого костыля можно соорудить костыль поизящней, например удалять файл во время сборки, чтобы он не попадал в deb-пакет. В этом смысле совет выше можно рассматривать как направление, куда в дальнейшем копать.
Ключевые слова: Хозяйке на заметку, CMake, deb, Linux
В GError при этом можно было увидеть ошибку:
'Действие не поддерживается'.Или, с LANG=C:
'Operation not supported'.
Естественно, yelp был установлен.
Вопреки некоторым советам из интернетов, gvfs тут ни при чем.
Проблема решилась установкой пакета desktop-file-utils.
Ключевые слова: Хозяйке на заметку, AstraLinux, Gtk, Linux
- У вас есть образ раздела (скажем, sda1), но не всего диска (sda). Т.е. без таблицы разделов. Образ сделан с помощью старого доброго dd.
- Нужно из него создать диск для VirtualBox'а (или другой виртуальной машины, например, VMware).
- Создаем пока пустой файл myvm.img для будущего образа диска (всего диска, в т.ч. с разделами). Размер файла выбираем исходя из размера образа, но с запасом, чтобы поместилась еще и таблица разделов. Скажем есть образ в 99 Гб, возьмем сильно с запасом и создадим файл под образ диска в 100 Гб:
dd if=/dev/zero of=myvm.img bs=1G count=0 seek=100
- Разметим файл утилитой fdisk (нужно создать раздел и не забыть сохранить изменения командой 'w'):
fdisk myvm.img
- Теперь сделаем полученный образ с разделами доступным в виде устройств в /dev:
sudo kpartx -a myvm.img
- Копируем образ нашего раздела поверх раздела во вновь созданном файле:
sudo cp sda1.raw /dev/mapper/loop0p1
- Можно, чтобы не пропадало место (тот самый "запас"), расширить файловую систему на разделе (учтите, что утилита resize2fs работает только с системами семейства ext{2-4}!):
sudo resize2fs /dev/mapper/loop0p1
- Теперь можно убрать наш файл myvm.img из /dev:
sudo kpartx -d myvm.img
- Получился "сырой" образ диска с таблицей разделов и собственно нашим разделом. Осталось сконвертировать его в формат нужной виртуальной машины. Для VirtualBox:
VBoxManage convertdd myvm.img myvm.vdi --format VDI
Для VMware:VBoxManage convertdd myvm.img myvm.vmdk --format VMDK
- Готово.
Ссылки:
- Алгоритм создания образа диска из образа раздела в обсуждении на stackexchange.com;
- Статья о конвертации 'сырых' образов дисков в формат образов для виртуальных машин (и обратно);
- Про восстановление загрузчика grub в треде на askubuntu.com.
Ключевые слова: fdisk, kpartx, Linux, VirtualBox, VMware
Решение — XServer под виндой (я выбрал Cygwin/X) и ssh-сервер на Linux-машине.
Алгоритм следующий:
- На Linux-машине должен быть поднят ssh-сервер;
- На планшете накатываем cygwin вместе с XServer-ом и ssh-клиентом отсюда (см. раздел "Downloading and Installing"). При установке не забываем (иначе придется переставлять cygwin заново) поставить пакеты xorg-server, xinit и openssh (зачем — см. здесь, пункт 15). Есть смысл сразу же накатить еще subversion и wget, чтобы иметь возможность в любой момент доставлять пакеты с помощью apt-cyg;
- Запускаем Cygwin Terminal (ярлык на рабочем столе);
- В Cygwin Terminal запускаем команду "startxwin.exe". Открывается классический xterm;
- В xterm запускаем ssh-клиент примерно так: "ssh -Y user@host", user и host меняем на пользователя и IP Linux-машины;
- После логина в ssh получаем командную строку Linux-машины, где можем запускать графические программы с выводом сюда же, на экран планшета (даже мультитач в GIMP'е работает). См. gedit на скриншоте ниже.
Ключевые слова: Планшет, Acer, apt-cyg, cygwin, Iconia Tab, Intel Atom, Linux, ssh, sshd, xorg
Поэтому выкладываю пару откомментированных на русском примеров. Примеры из официальной поставки, конкретно версии pixman-0.30. В комментариях могут быть ошибки, неточности и т.п. Тем не менее, вот они:
- trap-test — вырезает из зеленого изображения трапецию по маске с альфа-каналом;
- checkerboard — рисует подобие разноцветной шахматной доски и затем трансформирует его (псевдо-3D перспектива).
Скрин:
Код:
https://docs.google.com/file/d/0B40UHa6ea9QkdUFTVnRqNnFaTDg/edit
Ключевые слова: example, Gtk, off-screen window, Vala
Сам vapi-файл:
[CCode (cprefix = "fftwf_", cheader_filename = "fftw3.h")] namespace Fftwf { [CCode(cprefix = "FFTW_")] enum Sign { FORWARD = -1, BACKWARD = 1 } [CCode(cprefix = "FFTW_")] enum Flag { MEASURE = (0U), DESTROY_INPUT = (1U << 0), UNALIGNED = (1U << 1), CONSERVE_MEMORY = (1U << 2), EXHAUSTIVE = (1U << 3), PRESERVE_INPUT = (1U << 4), PATIENT = (1U << 5), ESTIMATE = (1U << 6) } [CCode(cname = "fftwf_plan")] public struct Plan : int { } // fake int void destroy_plan(Plan p); void execute(Plan p); void free(float *p); float* malloc(size_t n); Plan plan_dft_1d(int n, float* in_ptr, float* out_ptr, Sign sign, Flag flags); }
Пример использования:
{
float input[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
float *data;
float *fft_result;
float *ifft_result;
Fftwf.Plan plan_forward, plan_backward;
int i;
data = Fftwf.malloc( 2 * sizeof(float) * input.length );
fft_result = Fftwf.malloc( 2 * sizeof(float) * input.length );
ifft_result = Fftwf.malloc( 2 * sizeof(float) * input.length );
plan_forward = Fftwf.plan_dft_1d( input.length, (void*)data, (void*)fft_result, Fftwf.Sign.FORWARD, Fftwf.Flag.ESTIMATE );
plan_backward = Fftwf.plan_dft_1d( input.length, (void*)fft_result, (void*)ifft_result, Fftwf.Sign.BACKWARD, Fftwf.Flag.ESTIMATE );
/* populate input data */
for(i = 0 ; i < input.length ; i++)
{
data[2 * i + 0] = input[i];
data[2 * i + 1] = 0.0f;
}
/* print initial data */
for( i = 0 ; i < input.length ; i++ )
{
print("data[%d] = { %2.2f, %2.2f }\n",
i, data[2 * i + 0], data[2 * i + 1] );
}
Fftwf.execute( plan_forward );
/* print fft result */
for(i = 0 ; i < input.length ; i++)
{
print("fft_result[%d] = { %2.2f, %2.2f }\n",
i, fft_result[2 * i + 0], fft_result[2 * i + 1] );
}
Fftwf.execute( plan_backward );
/* print ifft result */
for( i = 0 ; i < input.length ; i++ )
{
print("ifft_result[%d] = { %2.2f, %2.2f }\n",
i, ifft_result[2 * i + 0] / input.length, ifft_result[2 * i + 1] / input.length );
}
/* free memory */
Fftwf.destroy_plan( plan_forward );
Fftwf.destroy_plan( plan_backward );
Fftwf.free(data);
Fftwf.free(fft_result);
Fftwf.free(ifft_result);
}
В заключение хочу порекомендовать отличное how-to по написанию vapi к не-GObject-библиотекам.
{
uint8[] buf = new uint8[sizeof(uint32) + sizeof(uint32) +
sizeof(double) + sizeof(double)];
{
var data_stream = new DataInputStream(file.read());
data_stream.read(buf);
}
Variant variant = Variant.new_from_data<uint8>(
new VariantType("(uudd)"), buf, true);
uint32 int1 = variant.get_child_value(0).get_uint32();
uint32 int2 = variant.get_child_value(1).get_uint32();
double double1 = variant.get_child_value(2).get_double();
double double2 = variant.get_child_value(3).get_double();
stdout.printf("int 1 = %u\n", int1);
stdout.printf("int 2 = %u\n", int2);
stdout.printf("double 1 = %f\n", double1);
stdout.printf("double 2 = %f\n", double2);
}
Чтобы сохранить комментарии и время коммитов патч нужно формировать командой:
git format-patch -k --stdout $HASH > ./patch_fileГде $HASH — хэш коммита, начиная с которого изменения необходимо сохранить в патче.
Накладывать полученный патч следует командой (одной строкой):
GIT_COMMITTER_NAME='Paul' GIT_COMMITTER_EMAIL='paul@example.com' git am -k --committer-date-is-author-date < patch_fileГде Paul — имя автора коммитов из патча, paul@example.com — адрес его почты (как указано в конфиге гита).
UPD: если использование патчей не является обязательным условием, а нужен механизм синхронизации репозиториев как таковой, то возможно вам стоит копнуть в сторону git bundle.

![[Image]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglZx1zAOvuAotO85VPgPbF8nLu2RwCZXqkZz3VRwGE7TFJVGFHBbS3U9L09AZTRxUFLX6nCWslVt6GxQP6rs26bUlUwaQ3UsSAkXAEiYBSY5DvLvDEj_In0eNrPpwjnGJY2ASx5bBp-eA/s320/rotated_widget.png)