четверг, 15 декабря 2005 г.

Продолжаю добывать прошивку своего сименса

Напомню, что мне нужна прошивка, чтобы найти способ включения подсветки клавиатуры и включения динамического света в немигающий режим - все это чтобы сделать фонарик более ярким (у каждого своя шиза).

Слегка продвинуться на пути получения прошивки удалось найдя утилиту для выковыривания xbz файла из файлов вида CX65250300.xbz_update.exe. Заметьте, найдя - а не написав, я очень законопослушный гражданин (это могут подтвердить все, кто меня хорошо знает) и если в лицензионном соглашении исполняемого файла с зашифрованной прошивкой написано, что программу нельзя дизассемблировать и модифицировать - то я этого конечно же не буду делать! *невин *миг :)

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

С другой стороны, ми опять не добрался до прошвки. Дело в том, что xbz это сжатая версия xbi.. и как его развернуть ми еще не думал. Будем искать на досуге.

Дизассемблировать код сименовской программы нельзя, поэтому ограничусь изучением кода найденной утилиты :). Судя по-всему *xbz_update.exe файлы состоят из четырех частей:
1. Собтсвенно сама программа обновления телефона прошивкой приложенной к файлу.
2. Информация для расшифровки файла и контроля целостности. Это версия формата, таймстамп используемый как одна из составляющих IV и 128 байт сигнатуры с которой скорее всего для контроля целостности необходимо сверить хэш разшифрованного файла.
3. Зашифрованный с помощью Rijndael-128 (AES) файл с прошивкой в формате xbz.
4. Блок данных в конце exe файла в котором указано с какого смещения в exe файле начинается зашифрованная часть. Это блок данных тоже хитрым способом организован, что должно прятать смещение от посторонних глаз.
128 битный ключ, судя по коментарию в исходнике утилиты (кусок асеблерного кода) :), был выдран прям из exe файла, как и 128 битный IV (но первые его 4 байта это timestamp). Полагаю, что вполне вероятна ситуация в которой ключ не подойдет и надо будет его искать заново... (а приведенный кусок асм кода должен в этом помочь) кто их этих сименцев знает.. может у них на каждый апдейт свой ключ...
Более подробно можно узнать посмотрев исходник утилиты, или дизассемблировать программу обновления прошивки (только это запрещено лицензионным соглашением).

К слову о... Вообще, из собственного опыта изучения программ, чье лицензионное соглашение не запрещает этого делать могу сказать, что начинать надо с сообщений (например об ошибках)!
Загружаем файл в какой-нибудь интерактивный дизассемблер (под unix и win32 есть бесплатный и замечательный hte), находим интересное сообщение, такое, которое вполне вероятно используется в том коде, который мы хотим найти (например password check failure) и просим интерактивный дизассеблер дать нам все места с которых есть ссылки на это сообщения... Ну, а дальше дело техники, знания ассеблера и того языка на котором написана подобытная программа.

На самом деле, получить прошивку можно еще и:
1. Скачав ее из интернета. Самый простой вариант. Но если ты на GPRS (как я сейчас временно) - то он не подходит, прошивка не маленькая.
2. Вытащить из телефона. Для этого нужен шнурок. Я за 3000км от своего шнурка, а брать новый (400р) жаба душит.
3. Попробовать вытащить прошивку через irda с телефона. Помучив телефон по протоколу BFC через IRDA и увидев как на все мои корректные запросы телефон реагирует только эхом.. а на некорректные не реагирует вообще я понял, что занятие бесполезное или у меня лапы кривые. Хотя выключение телефона BFC командой работает... забавно ).
4. Загрузить программу обновления в vmware, сделать виртуальный порт который будет обслуживать программа имитатор моблиьного телефона. Программу я эту написал, но далеко дело не пошло, я не знаю как должен реагировать телефон на те или иные команды и поэтому дописать эмулятор телефона не смог.
5. Загрузить программу обновления прошивки в vmware и сделать suspend. Открыть файл памяти виртуально машины и выдрать оттуда прошивку. Самый просто вариант, но нужно знать какой последовательностью начинается прошивка (теперь я знаю, что она начинается Siemens Mobile Phones:SIGNATURE...) и какую длинну она имеет.

среда, 7 декабря 2005 г.

Безопасное использование VMWare 5.0 (или о том как сделать бесконечный винт)

На моем ноутбуке установлены две операционные системы - Linux и Windows XP. До не давнего времени меня это вполне устраивало, загружался в винду я раз в месяц, чтобы поиграть в какую-нибудь игру. И вот вчера я поставил одну нужную мне программу, которая к сожалению есть только для Windows, а работать то всеже хочется в основном в linux вот и встал вопрос об одновременной работе обеих операционных систем на одном компьютере. На первый взгляд все очень просто: ставим VMWare настраиваем ее и вот все готово. Но на самом деле.... на самом деле не хочется делать полностью виртуальную машину, ставить новую windows и все программы, а хочется использовать уже установленную.. вот тут-то и начинается самое веселое.

А суть проблемы вот в чем. VMWare позволяет использовать существующие диски, но как выяснилось в процессе, доступ ей необходим ко ВСЕМУ винчестеру, а не только к той партиции которая реально с этого винчестера нужна. В моем случае windows установлена на /dev/hda1, linux swap на /dev/hda2, сам linux на /dev/hda3. Таким образом, чтобы использовать раздел /dev/hda1 в качестве раздела внутри vmware я должен добавить в конфигурацию виртуальной машины /dev/hda. А что тут такого, надо указать /dev/hda ну и укажем, подумаешь. На самом деле, указать мало, надо еще дать права чтения-записи /dev/hda пользователю который собирается работать в vmware с windows. А ведь /dev/hda это не только windows, но еще и swap и собственно сам раздел с linux. Это ж такая дырка не хилая получается, что уж почти тоже самое работать из под root все время.

I. Первый небезопасный способ.
Да можно просто дать права на /dev/hda и не страдать, если руки не шаловливые, если незнакомые программы не пускаем, если данные не жалко, если не параноик, то смело прописываем себя в /etc/groups в группу disks или делаем:
chmod 660 /dev/hda
chown akshaal:root /dev/hda
или даже более правильное:
cp -r /dev/hda /dev/hdw
chown akshaal:root /dev/hdw
chmod 600 /dev/hdw
Потом создаем виртуальную машину, выбираем для диска физический диск, говорим, использовать определенные партиции: помечаем hda1, а остальные не трогаем. Грузим виртуальную машину и видим LI 1919191919191919(19) или если у нас grub, то видим error 17. Это означает, что наши любимые загрузчики не смогли прочитать куски своих жирных туш находящихся в тех партициях которые мы не включили. В моем случае, это /dev/hda3. Если у кого-то grub/lilo лежат в /boot, то тут все немного проще становится, достаточно разрешить в vmware доступ к этой partition и все будет ок. Мне же получается надо открыть доступ гостевой системе к hda3, только тогда я увижу долгожданную менюшку. Но сделав это я открываю гостевой системе доступ к линуховому разделу, и могу вполне себе нечайно так загрузить linux еще раз, но уже внутри виртуальной машины тем самым испортить файловую системы в которую будут одновременно писать как гостевой linux так и хостовый.
Резюме:
решение простое, но это единственный его плюс, а из минусов видимость безопасности, отсутствие защиты от самого себя как в виртуальной машине, так и в реальной.

II. Способ второй, тоже не безопасный.
Вариация первого способа. Делать /dev/hda доступным для пользователя не обязательно, можно ведь еще пускать vmware через sudo. Но с одной стороны мы больше не даем пользователю доступа к /dev/hda, а с другой стороны кто его знает каких дел можно наворотить из suid'нутой vmware?! Там вроде даже скрипты можно запускать.. Да и выглядят приложения пущенные из под root не в общей визуальной теме.
Страшно в общем это все. К тому же это не решает проблему grub и lilo в гостевой машине.

III. Способ третий, все еще не безопасный.
Сам не пробовал, но в теории можно сделать sudo'ным вот такой вот скрипт:
cp -r /dev/hda /dev/hdw
chown akshaal:root /dev/hdw
nohup su -c vmware akshaal &
sleep 10
rm -f /dev/hdw
Или по-русски, мы делаем копию /dev/hda доступной для пользователя в течении 10 секунд за которые пользователь должен включить виртуальную машину в работу, после чего дырка закрывается (в unix'ах можно без проблем удалять открытые файлы, это никак не мешает программе которая с удаленным файлом работает). Если так сделать, то дырка в реальной машине будет существовать в течении только 10 секунд, за которые что-то можно сделать разве что специально. Но это опять не решает проблемы внутри гостевой машины.

IV. Способ четвертый.
Способ вообще не рабочий, даже рассматривать лень.
(основан на идее давать права не на /dev/hda, а на /dev/hda1, 2, 3. но из-за того, что vmware даже и не смотрит в сторону этих файлов, он не рабочий).

V. Безопасный способ решения проблемы.
А что если попробовать виртуально склеить /dev/hda1 с другими устройствами и получить совершенно новый диск частью которого бы являлся наш /dev/hda1 (fat32 раздел). Потом дать на этот диск права пользователю и использовать именно его из vmware. Развивая эту мысль вспоминаем о технологии LVM которая кажется чем-то таким занимается (почти software raid). Почитав подробнее про LVM понятно - она занимается не этим и не подходит, но есть одна деталь, LVM сама в свою очередь использует технологию device mapper появившуюся в ядрах 2.4 и 2.6. Идея device mapper: мы задаем из каких кусков состоит наше новое устройство, задавая интервалы секторов и соответствия этих интервалов данным других устройств (подробнее в документации).

Итак, для начала надо определиться, что мы хотим создать. Я создаю новое устройство - винчестер, по размеру аналогичный существующему винчестеру hda. Мой новый винчестер (назовем hdw) должен иметь такуюже разбивку как и реальный. И самое главное, новый винчестер должен использовать в качестве раздела hdw1 реальный раздел hda1 реального винчестера. Вот основное требование к новому винчестеру.

Теперь более детально обдумываем ситуацию. Вспоминаем, что жесткий диск имеет следующую структуру:
Сначала идут 512 байт принадлежащие master boot record'у. В этих же 512 байтах MBR'а лежит partition table (таблица разделов), в которой описано с какого сектора и по какой расположена каждая из четырех партиций. На каком-то расстоянии от MBR расположен первый раздел. За котором следующий и так далее (между разделами вполне вероятны промежутки).

Таким образом, в соответствии с задачей наш винчестер разделяется на три области:
1. То, что идет до hda1 (это MBR и все что за ней до начала раздела, назовем эту область PRE).
2. Раздел hda1 на котором лежит Windows XP.
3. Все, что после раздела hda1 (назовем эту область POST).
Для того, чтобы воссоздать аналогичную структуру на hdw, надо знать параметры этих частей, а именно их длинны в секторах.
Узнать смещение hda1 относительно начала диска, можно посмотрев в hex редакторе partition table. Для этого сделаем:
dd if=/dev/hda of=/tmp/hda count=1 bs=512

Откроем /tmp/hda на просмотр в mc и нажмем f4. 4 байта по смещению 0x000001c6 определяют начало первого раздела (в секторах). Мой раздел hda1 начинается с 63го сектора.
Теперь определим количество секторов в разделе hda1, это можно сделать так:
blockdev --getsize /dev/hda1

В моем разделе hda1 оказалось 20000862 сектора.
Теперь вычислим длинну оставшейся области винчестера в которой находятся hda2 и hda3, для этого получим общее количество секторов в hda:
blockdev --getsize /dev/hda

Мой hda имеет 78140160 секторов. Если из этого числа вычесть количество секторов занятых под hda1 и под область перед hda1, то выходит длинна области за hda1.

После всех вычислений имеем следующую информацию по hda:
1. Область PRE: Начинается в секторе 0 и имеет длину 63 сектора.
2. Область hda1: Начинается в секторе 63 и имеет длину 20000862.
3. Область POST: Начинается в секторе 20000925 и имеет длинну 58139235.
(это же самую информацию можно получить и через fdisk, нажав там u, а потом p :) )

У нас есть достаточно данных, чтобы написать конфигурацию для нового винчестера!
Итак вот она (/etc/hdw/dm.cfg)
0        63        linear /dev/loop3 0
63 20000862 linear /dev/hda1 0
20000925 58139235 zero

Первый столбец - номер сектора с которого начинается интервал.
Второй столбец - количество секторов в интервале.
Третий столбец - метод описывающий откуда проецировать сектора.
Четвертый столбец - параметры метода.
В моем конфигурационном файле написано, что новый винчестер состоит из трех областей, где первые 63 сектора проецируются из устройства /dev/loop3, следующие из устройства /dev/hda1 и наконец остаток забивается нулями ибо нам не интересен совсем.
Теперь надо создать облать PRE. Сделаем это так:
dd if=/dev/hda of=/etc/hdw/pre.img count=63 bs=512

- мы скопировали первые 63 сектора из винчестера в файл образа.
Теперь делаем:
losetup /dev/loop3 /etc/hdw/pre.img

- и область PRE для нового винчестера готова. Вот теперь у нас есть все, чтобы создать устройства нового винчестера.
Длеаем:
dmsetup create hdw /etc/hdw/dm.cfg
chmod 600 /dev/mapper/hdw
chown akshaal:akshaal /dev/mapper/hdw
ln /dev/mapper/hdw /dev/hdw


После всех этих шагов, в /dev должно появится устройство hdw, которое побайтно соответствует устройству hda на протяжении первых 20000925 секторов, после которых в hdw начинаются 0. Но если мы изменим сектор лежащий в интервал 0...62 (включительно) в устройстве hdw, то hda затронут не будет, а изменится файл /etc/hdw/pre.img. Если же мы изменим значение в секторах с 63 по 20000924 в hdw, то изменения отразятся и в hda1. Запись в сектора с номерами больше 20000924 в hdw смысла не имеет (она игнорируется, а при чтении всегда будет 0).

Хорошо, у нас есть изолированный винчестер доступ к которому вполне безопасно (разве что windows затрется) давать пользователю. Включим же его в работу! Пытаемся. А VMWare говорит, что bus error и она его использовать не будет! Предвкушая перспективу реализовывать эмуляцию ioctl'ов нужных vmware, но сохраняя надежду более простого решения, лезим в google и находим вот такую ссылку. Оказывается, совсем недавно (в ноябре этого года) кто-то столкнулся с аналогичной проблемой подключения loop устройств в vmware в качестве винтов, ну и в конце концов была написана вот такая программа обертка о которой можно прочитать тут или сразу скачать тут. Компиляция и установка программы происходит как обычно и без проблем. Работает программа тоже без нареканий. Для запуска vmware с обвязкой надо написать вот такой скрипт (~/bin/vmware-in-nutshell):
 #!/bin/sh LD_PRELOAD=libvmware-bdwrapper.so.0 VMWARE_BDWRAPPER_DEVICES=/dev/hdw vmware 

и запускать уже его.

Теперь vmware прекрасно запускается и нормально позволяет создать виртуальную машину с /dev/hdw в качестве физического диска.

Запускаем vmware и правильно... получаем совершенно ожидаемое li или error 17. Lilo и grub пытаются прочитать свои хвосты в hdw3 а там у нас полностью нулевая область. Пострадав и поискав около пары часов способ снести grub, находим в своем дистрибутиве пакет ms-sys и ставим:
apt-get install ms-sys 


Прочитав man на программу ms-sys, делаем:
ms-sys -f -m /dev/hdw

на что получаем "Windows 2000/XP/2003 master boot record successfully written to /dev/hdw".

Если в процессе поиска решения об удалении grub'а вы нечайно снесли еще и бутовую область раздела hda1 (как это сделал я), то делаем еще и (для XP):
ms-sys -2 -w /dev/hda1


Операции по созданию устройства следует вынести в отдельный файл и загружать его из rc.boot.

Вот и все. Теперь сразу после включения виртуальной машины у нас грузится Windows XP. Оно того стоило.

При загрузке реальной машины появляется обыкновенное меню выбора операционной системы (linux, windows).
При загрузке виртуальной машины Windows XP грузится сразу.
В обоих случаях для работы используется одна и та же область жесткого диска с одним тем же набором программ windows.
Оно того стоило.

В Vmware 5.0 появилась возможность вписать рабочий стол гостевой системы в окно vmware. Делается это одним нажатием хоткея. Так у меня сейчас Windows XP имеет разрешение 1016x610.
Еще появились какие-то Shared Folders... надо посмотреть, похоже это технология более простого доступа к содержимому хоста из гостевой машины.

PS. Чтобы не было мешанины из драйверов и настроек при работе то в виртуальной машине, то в реальной, следует использовать профили оборудования. Еще лучше выключить засыпание винды при долгом простое, а то после ее пробуждения настройки мыши как-то сбрасываются нехорошо...
И самое главное, не стоит использовать снапшоты! Не стоит делать саспенд в виртуальной машины, грузить винду с реальной, а потом делать ресаме в виртуальной. Такой бардак приведет к разрушению файловой системы.
В конце-концов эксплуатая dual booting системы в vmware не такая уж и сложная. Главное знать что делать и не делать лишнего.

понедельник, 21 ноября 2005 г.

Топчемся по интернациональным граблям

Эта печальная и утомительная история началась с того, что я решил чуть-чуть поднастроить emacs, а именно мне хотелось иметь возможность выполнять команды привязанные к клавиатурным комбинациям без переключения на латиницу. Да и не плохо бы, чтобы каждый буфер emacs'a помнил свою раскладку, а раскладок то чтобы три штуки было (латиница, русская, украинская) и переключаться между ними не циклически (чтоб не задумываться о текущей раскладке и количестве нажатий для переключения в нужную). Естественно мне не хотелось у emacs'a иметь отдельный от всех остальных программ набор клавиатурных сочетаний для переключения языков. Одни и те же клавиатурные сочетания должны переключать языки как в emacs так и в остальных программах. По ходу мне захотелось, чтобы помимо emacs'a каждое иксовое окно находилось в своей раскладке и каким-то образом ее отображало. Неплохо бы еще, чтобы при переключении раскладок менялся словарик орфографический на соответствующий выбранному языку.

Итогом моего двухдневного труда (помимо нервно изгрызанных ногтей) стало достижение всех выше указанных прихотей и теперь я свободно могу написать:
Hello native England!
Привет родная Россия!
Привіт рідна Україна!

Итак, что мы хотим:
RAlt-1 латиница
RAlt-2 русский
RAlt-3 украинский
(RAlt - правый Alt)

Для начала изучаем документацию по xkb (спасибо Ivan Pascal). Вдумчиво прочитав все от корки до корки пытаемся, сделать первые шаги в написании необходимой конфигурации и сразу выясняем, что setxkbmap ну никак не хочет грузить наши файлы лежащие в домашнем каталоге, ему обязательно их хочется видеть в /usr/-что-то-там-такое. Править системные файлы - очень плохое решение. Если копать дальше, то рано или поздно можно заметить вот такую строку из мана по setxkbmap:
setxkbmap -print us | xkbcomp - $DISPLAY

Аха! Вот оно! Оказывается, с помощью xkbcomp можно не только компилировать конфигурации для xkb, но и сразу их загружать в xkb. Делаем:
setxkbmap > ~/.local-xkb
и берем полученный файл за основу нашей конфигурации.

В конце-концов (потратив кучу нервов, наступив на множество граблей и попробовав несколько неработающих вариантов) для переключения раскладок с помощью задуманных клавиш создаем вот такой файл конфигурации:

// Evgeny "Akshaal" Chukreev (C) 2005

xkb_keymap {
xkb_keycodes {
include "xfree86+aliases(qwerty)"
};

xkb_types {
include "complete"
};

xkb_compat {
include "complete"
};

xkb_symbols {
include "pc/pc(pc104)+pc/us+pc/ru(winkeys):2+pc/ua(winkeys):3"

key <AE01> {
overlay1 = <I71>
};

key <AE02> {
overlay1 = <I72>
};

key <AE03> {
overlay1 = <I73>
};

replace key <I71> {
radiogroup=2, [F31],
actions[Group1] = [ LockGroup(group=1) ]
};

replace key <I72> {
radiogroup=2, [F32],
actions[Group1] = [ LockGroup(group=2) ]
};

replace key <I73> {
radiogroup=2, [F33],
actions[Group1] = [ LockGroup(group=3) ]
};

replace key <RALT> {
[NoSymbol],
actions[Group1] = [ SetControls(controls=overlay1) ]
};
};

xkb_geometry {
include "pc(pc104)"
};
};

Как будет работать икскабэ по этому сценарию: При нажатии на RALT выполняется действие SetControls(controls=overlay1), включающее оверлейный режим. Теперь палец (не отпуская RALT) тянется к кнопкам 1, 2 или даже 3, но допустим к кнопке 2. При нажатии на кнопку два, клавиатура отсылает в икскабэ сканкод клавиши выглядящий в символьном обозначении как <AE02>. Икскабэ смотрит свои таблицы и находит, что для этого сканкода, при включенном оверлейном режиме, надо сделать вид, что клавиатура прислала сканкод с символьным обозначением <I72> (такое поведение определенно строкой overlay1 = <I72>, а сканкод клавиши в оверлейном режиме выбран таким образом, чтлбы клавиатура никогда сама такой сканкод не присала). Теперь икскабэ смотрит запись для сканкода <I72> и выполняет действие actions[Group1] = [ LockGroup(group=2) ], устанавливающее активной вторую группу (согласно этой строке 'include "pc/pc(pc104)+pc/us+p /ru(winkeys):2+pc/ua(winkeys):3"', вторая группа у нас pc/ru(winkeys)). Помимо прочего, икскабэ посылает приложению
событие о нажатии на кнопку со сканкодом I72 и символьным обозначением F32. Теперь мы отпускаем RAlt-2 и икскабэ сбрасывает оверлейный режим. Если кратко то все именно так и работает. Для сочетаний RAlt-1 и RAlt-3 все происходит аналогично, но RAlt-1 устанавливает группу 1 и посылает приложениям сообщение о нажатии F31, а сочетание RAlt-3 соответственно группу 3 и кнопку F33.

Для использования этой конфигурации необходимо выполнить команду:
xkbcomp ~/.local-xkb $DISPLAY
(лучше всего прописать ее в ~/.xsession)

На этом можно было бы и остановиться, но ведь еще нужно настроить емакс, а это самое интересное! Если настройка емакса не нужна, то в конфигурации xkb надо заменить символы F31, F32 и F33 на NoSymbol. Тогда переключение клавиатуры будет незаметным для приложений. Если замену не произвести и больше ничего не делать, то как минимум будут проблемы в шеле, который при нажатии получит загадочную ескэйп последовательность и вставит ее как есть в строку ввода. Но это я продолжил настройку емакса.

Первым делом необходимо придумать как уберечь программы от событий нажатия на несуществующие клавиши F31, F32, F33 в момент переключения раскладок. Прослойкой между иксами и сервером является оконный менеджер, он первым отлавливает нажатия и если они не его, то отдает их дальше программе находящейся в фокусе. К счастью или несчастью имеющимся оконным менеджером является ion3. В нем мы может просто забиндить что-то на эти клавиши и все, приложение о нажатии ничего не узнает. Но ведь нам надо, чтобы емакс таки узнал о нажатии и произвел необходимые действия по переключению своей внутренней раскладки... Одним из вероятных решений была бы возможность забиндить нажатие таким образом, чтобы в момент срабатывания мы могли определить какое приложение находится в фокусе и при необходимости передать ему событие дальше или иначе проигнорировать нажатие. Ion3 поддерживает написание скриптов на языке lua и используя эту возможность мы можем... мы могли бы фильтровать событие нажатия, если бы не очередное, но: в ионе нет луа фукции для передачи приложению события о нажатии клавиши. Да, с помощью луа мы можем отловить нажатие на клавиши F31, F32, F33, мы можем определит какому приложению эти клавиши предназначаются, но мы не можем при надобности передать нажатие приложению. В поисках решения я натолкнулся на пост (за который спасибо) жж юзера besm6 (которого я кажется видел еще в свое время в Фидо). Артем решил проблему передачи нажатия через вызов
внешней простенькой утилитки. Взяв идею пишем вариацию на тему, для передачи произвольного сочетания произвольному иксовому окну:

/* Evgeny Chukreev (C) 2005, GNU GPL */

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Main function */
int main (int argc, char *argv[])
{
Display *display;
XKeyEvent event;
int window_id;
char *keysym;
int state;

/* Check arguments */
if (argc < 4) {
fprintf (stderr, "Usage: %s <window_id> <symbol> <state>\n", argv[0]);
return -1;
}

/* Parse arguments */
window_id = strtol (argv[1], NULL, 0);
keysym = argv[2];
state = strtol (argv[3], NULL, 0);

/* Open display */
display = XOpenDisplay (NULL);
if (!display) {
fprintf (stderr, "%s: Can't open display\n", argv[0]);
return -4;
}

/* Init event */
memset (&event, 0, sizeof (event));
event.window = window_id;
event.display = display;
event.root = RootWindow (display, DefaultScreen (display));
event.state = state;
event.keycode = XKeysymToKeycode (display, XStringToKeysym (keysym));

/* Send KeyPress event */
event.type = KeyPress;

if (!XSendEvent (display, window_id, False, 0, (XEvent *) &event)) {
fprintf (stderr, "%s: Can't send KeyPress event\n", argv[0]);
return -2;
}

XSync (display, False);

/* Send KeyRelease event */
event.type = KeyRelease;

if (!XSendEvent (display, window_id, False, 0, (XEvent *) &event)) {
fprintf (stderr, "%s: Can't send KeyRelease event\n", argv[0]);
return -3;
}

XSync (display, False);

/* Close display */
XCloseDisplay (display);

/* That is all */
return 0;
}

Конечно, мы могли бы сделать из этого библиотеку для луа, там это очень просто, но лично я делать этого не стал, ибо луа меня раздражает еще больше чем перл, поэтому перебьется, хотя сделать библиотеку было бы правильнее.

Имея возможность передать событие нажатия в программу, мы можем написать луа код для обработки F31, F32, F33. Вот что получается (aksendkey):
-- Запускаем интернацианализирующие механизмы по кнопке
-- Соответственно разным приложениям можно посылать
-- разные значения групп и разные символы при переключении раскладки
function i18n_by_key (win, key)
if obj_is (win, "WClientWin") then
-- Реальное окно приложения
pass_by_class_and_set_group (win, key, 0, 'Emacs', 0)
else
-- Фигня какая-то, оставляем в латинице
ioncore.exec ("aklockxkbgroup -1 0")
end
end

-- Если класс окна win равен class, то посылаем ей символ keysym в состоянии
-- state и устанавливаем текущую группу в значение group.
-- Иначе ничего не делаем.
function pass_by_class_and_set_group (win, keysym, state, class, group)
if win:get_ident().class == class then
local windowid = win:xid ()
ioncore.exec ("aksendkey " .. windowid .. " " .. keysym .. " " .. state)
ioncore.exec ("aklockxkbgroup " .. windowid .. " " .. group)
end
end
Здесь используется еще одна утилита, которая устанавливает текущую группу в заданное значение. Нужно это для того, чтобы при работе с emacs'ом не было двойного переключения раскладок (один раз через хкб, второй раз в емаксе). Вот она эта программа aklockxkbgroup:

/* Evgeny Chukreev (C) 2005, GNU GPL */

#include <X11/XKBlib.h>
#include <stdio.h>
#include <stdlib.h>

/* Main function */
int main (int argc, char *argv[])
{
Display *display;
int window_id, group, res;

/* Check arguments */
if (argc < 3) {
fprintf (stderr, "Usage: %s <window_id> <group>\n", argv[0]);
return -1;
}

/* Parse arguments */
window_id = strtol (argv[1], NULL, 0);
group = strtol (argv[2], NULL, 0);

/* Open display */
display = XkbOpenDisplay (NULL, NULL, NULL, NULL, NULL, NULL);
if (!display) {
fprintf (stderr, "%s: Can't open display\n", argv[0]);
return -2;
}

/* Init XKB */
res = XkbQueryExtension (display, NULL, NULL, NULL, NULL, NULL);
if (!res) {
fprintf (stderr, "%s: Can't init XKB\n", argv[0]);
return -3;
}

/* Set Focus */
if (window_id > 0) {
XSetInputFocus (display, window_id, RevertToParent, CurrentTime);
XSync (display, False);
}

/* Main fun */
res = XkbLockGroup (display, XkbUseCoreKbd, abs (group % 4));
if (!res) {
fprintf (stderr, "%s: Can't lock group\n", argv[0]);
return -3;
}

XSync (display, False);

/* Close display */
XCloseDisplay (display);

/* That is all */
return 0;
}
Теперь биндим луа функцию на клавиши и почти (хехе) все готово. Это прописывается в разделе WMPlex биндингов:

bdoc ("I18n: LAT mode."),
kpress ("F31", "i18n_by_key (_sub, 'F31')"),

bdoc ("I18n: RUS mode."),
kpress ("F32", "i18n_by_key (_sub, 'F32')"),

bdoc ("I18n: UKR mode."),
kpress ("F33", "i18n_by_key (_sub, 'F33')"),
Таким образом при нажатии, например на RAlt-2, срабатывает наш луа скрипт, он проверяет и если текущее онко содержит емакс, то сбрасывает группу в 0 (латиница) и посылает емаксу событие о нажатии клавиши F32.

Теперь пришло время научить emacs реагировать на приходящие к нему нажатия F31, F32, F33. Тут все почти просто. Загвоздка только в том, что я желаю, чтобы русская и украинская расскладки в емакс соответствовали аналогичным
в остальных программах. В конфигурации xkb прописаны (мои любимые) ru(winkeys) и ua(winkeys) - это такие же раскладки как и в виндовс. Так вот, в емакс 21.4 ничего подобного нет. То что там есть для русского языка еще куда ни шло, а украинская раскладка вообще фонетическая, логику которой
я не понимаю. Поэтому пришлось найти последнюю версию пакета leim и выдернуть оттуда файлик cyrillic.el. Кладем его в ~/elisp/updates/, компилируем через M-x byte-compile-file, добавляем в ~/.emacs строчку:
(load-file "~/elisp/updates/cyrillic.elc")


И вот мы имеем методы ввода с названиями: russian-computer, ukrainian-computer. Для переключения методов ввода пишем в ~/.emacs подобный код:

;; Клавиша перехода в режим латиницы
(define-key global-map [(f31)]
`(lambda ()
(interactive)
(inactivate-input-method)
(reset-flyspell-with-new-dict "american")
))

;; Клавиша для перехода в режим русского языка
(define-key global-map [(f32)]
`(lambda ()
(interactive)
(set-input-method 'russian-computer)
(reset-flyspell-with-new-dict "russian")
))

;; Клавиша для перехода в режим украинского языка
(define-key global-map [(f33)]
`(lambda ()
(interactive)
(set-input-method 'ukrainian-computer)
(reset-flyspell-with-new-dict "ukrainian")
))


;; Перезапуск проверки орфографии с новым словарем
(defun reset-flyspell-with-new-dict (dict)
"Set new dictionary and restart flyspell"

; Смена словаря
(unless (equal dict ispell-local-dictionary)
(setq ispell-local-dictionary dict)
(when flyspell-mode
(flyspell-mode)
(flyspell-mode)))

; Перепроверяем видимую область, если флай моде включен
(when flyspell-mode
(save-excursion
(flyspell-region (window-start) (window-end))))

(message nil)
)

Из код видно, что при переключении происходит орфографическая проверка видимой области буфера. На больших файлах теоретически она может сильно тормозить процесс переключения. В таком случаем можно убрать из кода этот участок:

; Перепроверяем видимую область, если флай моде включен
(when flyspell-mode
(save-excursion
(flyspell-region (window-start) (window-end))))
Все бы работало замечательно без очередного напильника, но это же GNU! Проблема: при переключении на ukrainian словарь емакс ругается, что такого словаря немає. Поэтому чтобы можно было переключится на украинский словарик пишем очередной код (записи о наличии украинского словаря в списке ispell-dictionary-alist по-умолчанию нет по понятным причинам о которых скажу дальше, но мы это исправляем приведенным кодом):

;; Возможные словари
(setq ispell-dictionary-alist
'(("american"
"[A-Za-z]"
"[^A-Za-z]"
"[']"
nil
("-B" "-d" "american")
nil
iso-8859-1)

("russian"
"[АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯабвгдеёжзийклмнопрстуфхцчшщьыъэюя]"
"[^АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯабвгдеёжзийклмнопрстуфхцчшщьыъэюя]"
"[-]"
nil
("-C" "-d" "russian")
nil
koi8-r)

("ukrainian"
"[абвгдґеєжзиіїйклмнопрстуфхцчшщьюяАБВГДҐЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ]"
"[^абвгдґеєжзиіїйклмнопрстуфхцчшщьюяАБВГДҐЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ]"
"[-']"
nil
("-C" "-d" "ukrainian")
nil
koi8-u)

(nil
"[A-Za-z]"
"[^A-Za-z]"
"[']"
nil
("-B")
nil
iso-8859-1))
)
Уже лучше, но теперь при попытке переключится возникает сообщение о несуществующей кодировке koi8-u! Вот это уже настоящая проблема (собственного поэтому украинского словаря по-умолчанию и нет в списке доступных). Можно конечно поискать и даже наверное найти способ научить емакс новой кодировке. Наверняка цвс емакс эту кодировку знает и если оттуда выдрать нужный файл, то есть некоторая вероятность, что все заработает,
но мне уже лениво, и я все-таки доберусь до Киева и выделенки и сделаю таки apt-get install emacs-snapshot. А пока украинский словарик <<не працюе>>.

Вобщем-то почти все сделано. Осталось решить две небольших (еще раз хехе) проблемы: при переключении между окнами должна восстанавливаться группа локальная для окна.

Пробуем решить проблему через xxkb. Все замечательно, только эпизодически иконки отображаются белым цветом, когда над одним окном несколько таскбаров иконка пказывается не на том... и вообще, если запускать xxkb через .xsession, то он перестает запоминать группы (если запускать с консоли, то все ок), также он мешает работе нашей aklockxkbgroup (xxkb зачем-то по получению события о смене группы пытается выставить ее еще раз). Вобщем убив кучу времи, на поиск проблемы, мы забиваем на нее. Может вообще забить на запоминание группы для окна? Можно, только при переключении с любого окна в емакс нужно сбрасывать текущую раскладку в латиницу, чтобы в емаксе не было двойной перекодировки так сказать (заметно при нажатии shift-цифра). Можно было бы отловить в емаксе события смены фокуса, но судя по исходникам емакса и его документации так просто он это событие не выдаст, в общем у меня не получилось. К томуже, если даже отловить в емаксе событие получения фокуса, то придет оно в момент нажатия клавиши (такой вот он емакс мерзкий), что есть плохо так как мы должны были сбросить раскладку еще ДО нажатия клавиши. Отловить смену фокуса в ion'e тоже не так просто, если вообще возможно.. Поэтому единственный выход - писать программу, отслеживающую ход фокуса и сбрасывающую фокус в латиницу при достижении фокусом емакса. Но не торопимся. Посмотрим сначало свежее мясо и найдем там xkbind - разработка некоего CHG. Судя по описанию, программа замечательная, судя по исходнику хорошо написанная и вообще прелесть. Но она не работает :). Обидно. Открываем исхоники, изучаем процесс передачи фокуса, материм ион, иксы и вообще все. Делаем такой патч для xkbind:

diff -urN old/xkbind.c new/xkbind.c
--- old/xkbind.c 2005-01-03 21:10:21.000000000 +0200
+++ new/xkbind.c 2005-11-21 12:50:14.000000000 +0200
@@ -233,7 +233,9 @@
}
fprintf(stderr, "window: %d\n\n", ev.core.xfocus.window);
#endif
- if(ev.core.xfocus.detail == NotifyNonlinear &&
+ if((ev.core.xfocus.detail == NotifyNonlinear ||
+ ev.core.xfocus.detail == NotifyAncestor ||
+ ev.core.xfocus.detail == NotifyInferior) &&
(ev.core.xfocus.mode==NotifyNormal ||
ev.core.xfocus.mode==NotifyWhileGrabbed) &&
(group=GetWindowGroup(ev.core.xfocus.window))!=-1)
Теперь все замечательно отслеживается. В заголовке приписывается текущий
активный режим и тд.. все как и положено. Прописываем в ~/.xsession что-нибудь типа:
xkbind -label0 '--L-- ' -label1 '==R== ' -label2 '##U## ' -defgrp 0

Все! Счастлив тот, кто не убьет столько времени на брожение по различным граблям,
прочитав это описание, сколько убил я не найдя подобного. %)

Ну вроде ничего не забыл и описал все, что сделал.

ЗЫ По ходу избавился полностью от остатков гнома (типа gnome-session) мешавших работать.

четверг, 17 ноября 2005 г.

BBK Козлы?

Купил я как-то усилитель BBK AV212T. Через какое-то время он перестал включаться. Отнес в гарантийный ремонт. Починили. Заменили какой-то чип. Через какое-то время он опять перестал включаться. Его опять починили, но уже за деньги. Потом опять. Последние два ремонта я пропустил (был в Москве и Киеве и вообще сейчас усилителем пользуется сестра).
Теперь вот он сломался и сейчас в ремонте. Любопытство взяло верх и я нашел статью. Впечатляет причина повисания которая там описана. Интересно, это специально сделано?? Не является ли это вообще заводским браком?! Кошмар короче. Усилитель расчитаный на 255 включений это порнография какая-то. Я pic'и никогда не программировал, как и что с ним сделать не знаю. Может есть какое-то простое решение, чтобы каждые пол года не тратить деньги? У меня есть две идеи - не выключать. Но не сдохнет ли он этого еще раньше? Поискать человека, чтобы помог перепрошил или приделал PIC. Но таких знакомых у меня кажется нет (может есть у кого в Омске?). А самому разбираться некогда да и не специалист я ни разу.

Update: спустя 40 минут:
В голове возникло брутальное решение в хакерско-советском стиле. Припаять к контактам GND и SDA EEPROM'а два проводка, вывести их за пределы корпуса. И так оставить. Когда надо очистить EEPROM, то замыкать их. Ну можно тумблер приделать для красоты.

Ускоряем работу emacs/gnus

На самом деле работа emacs и gnus происходит в достаточной мере быстро. Но ручная доводка никогда не бывает лишней.

Начнем с общего ускорния работы емакса и гнуса. Первым делом устанавливаем суммарное количество памяти, которое должна быть выделенно в процессе работы emacs'а, чтобы произошла сборка мусора. По-умолчанию это значение равно 450000 (450килобайт), что означает необходимость производить сборку мусора после отъедания emacs'ом очередным ~450к ОЗУ. Современные машины имеют приличный объем памяти и мы можем пожертвовать под мусор лишние ~6мб ОЗУ. Теперь сборка мусора будет срабатывать значительно реже. (сборка мусора есть процесс сканирования всей памяти используемой emacs'ом и поиск тех участков которые более не нужны и их освобождение... процесс этот как и любой другой требует вычислительных ресурсов)
(setq gc-cons-threshold 6000000)

Есть еще один способ которым можно ускорить загрузку и работу emacs'а на какой-то процент - это байт-компиляция конфигурационных файлов. У меня сделано это следующим образом: в конфиге указаны файлы, которые необходимо перекомпилировать при их обновлении. Перекомпиляция производится по необходимости при закрытии буфера с файлом:

;; - - - - - - - - - - - - - - - - - - - - - - - - - - -- - - - - - - -
;; Следим за лисповыми файлами которые надо компилировать при их обновлении

(defmacro my-watch-and-byte-compile (sourcefile compiledfile)
`(add-hook 'kill-buffer-hook
(lambda ()
"Hook for updating file"
(when (and (string-equal buffer-file-name (expand-file-name ,sourcefile))
(file-newer-than-file-p ,sourcefile ,compiledfile)
(y-or-n-p (concat "byte-compile " ,sourcefile " to " ,compiledfile " ? ")))
(byte-compile-file ,sourcefile)
)
)
)
)

(my-watch-and-byte-compile "~/.emacs" "~/.emacs.elc")
(my-watch-and-byte-compile "~/.gnus" "~/.gnus.elc")

Компилировать необходимо не только файлы конфигурации (которые по сути есть elisp программы), но и прочие подгружаемые файлы написанные на языке elisp. В моем случае это все файлы лежащие в каталоге ~/elisp/. Компиляция отдельно взятого файла производится командой M-x byte-compile-file. Ну и наконец можно ускорить работу gnus'а, добавив в конец файла ~/.gnus такую вот строчку:
(gnus-compile)

В результате будет происходить компиляция различных параметров, что приведет к увеличению скорости вхождения в группу (за счет увеличения скорости создания списка писем - генерации summary).

среда, 16 ноября 2005 г.

Patch для cabal

Наконец-то мой патч, написанный и отправленный разработчику Cabal, два месяца назад, включен в darcs версию. Как приятно иногда вносить свой вклад в Open Source :). Пользоваться mplayer'ом, alsaplayer'ом, vim'ом, и осозновать, что и там есть твой код, пуская не всегда внушительный...

Настраеваем emacs/gnus

Надеюсь кому-то пригодятся те изменения которые я вчера вечером проделал со своим gnus'ом. Жду коментарии :)

В обычном своем виде, почтовый клиент gnus похож на текстовый редактор. Глядя на список почтовых папок (групп) кажется, что нажми ты кнопку backspace на клавиатуре, как незамедлительно будет удалена часть названия папки, как это происходит при редактировании документов. Мигающий курсор сбивает с толку. Поэтому мы его убираем первым же делом:
;; Прячем курсор в режиме групп и списка сообщений
(add-hook 'gnus-group-mode-hook
(lambda ()
(setq cursor-type nil)
))

(add-hook 'gnus-summary-mode-hook
(lambda ()
(setq cursor-type nil)
))

А для того, чтобы видеть какую строку (с названием почтовой папки или описанием письма из папки) мы в данный момент выбираем, включаем режим подсветки текущей строки буфера. Включаем это дело глобально ибо вещь полезная не только в гнусе:
;; Включаем подсветку текущей строки
(highlight-current-line-on t)

Настроить режим подсветки (задать фон, подчеркивание, особо крупный размер и тд)
можно нажав: M-x customize-face <enter> highlight-current-line-face <enter>

Теперь выбор папки и письма похожи на выбор каталога в Norton Comander'е или почтовых клиентах tin/mutt.

Чтобы приблизить gnus к tin'у и mutt'у изменим поведение кнопок в режимах просмотра списков папок и писем:

up - переход к предыдущей строке буфера как и раньше, но спрятанный курсор перемещается в начало строки, а не остается в той же колонке.

down - переход к следующей строке буфера (по прежнему ничего необычного), но как и для кнопки 'up', переходим в начало строки, к тому же не даем курсору попасть в позицию 'end-of-buffer', предотвращая случай, когда ни одна группа не выбрана (не даем курсору уйти за пределы списка).

pgdown - аналогично изменениям с кнопкой 'down', но перемещение происходит на страницу, а не строку, логично.

home - переход к началу списка, а не в начало строки.

end - переход к концу списка, а не в конец строки.

right - более сложные изменения, но по прежнему весьма интуитивные (разгружаем
лобную долю и нагружаем мозжечок, занимая голову более полезной работой чем
обдумывание нажатий).
В режиме просмотра списка папок, нажатие на эту клавишу,
открывает папку для просмотра ее содержимого
(в mc мы входим таким же образом в каталог). В режиме просмотра списка
писем нажатие на эту клавишу открывает выбранное письмо для просмотра.
Если письмо уже открыто на просмотр, то нажатие на right скроллирует письмо
на страницу вперед (на страницу минус две строки). При достижении конца письма,
дальнейшие нажатия игнорируются. Фактиски мы переносим ф-ии клавишь
space и enter, на клавишу right.

left - самая сложная функция. В режиме просмотра списка папок (групп) осуществляет
выход из gnus'а (функция кнопки q). В контексте просмотра списка сообщений,
когда не открыто ни одного сообщения (то есть когда на экране только список
сообщений папки), производит выход в режим просмотра списка папок,
но без перехода к следующей папке, как это происходит по кнопке q.
Если же имеется открытое письмо и первая его строка на экране не видна
(письмо отображается не с начала), то скроллирует письмо на страницу назад,
также как и кнопка 'backspace'. Если письмо отображается с самого начала, то
нажатие на left приводит к удалению его с экрана подобно нажатию на C-x 1.

Таким образом типичный цикл чтения почты осуществляется всего 4мя основными клавишами left, right, up, down, и выглядит следующим образом: в списке папок клавишами up/down выбираем нужную и нажимаем right, видим список писем в папке, выбираем заинтересовавшее нас письмо с помощью клавишь up/down и нажимаем кнопку right, нажимаем ее периодически до тех пор, пока не прочтем до конца, после чего либо выбираем следующее письмо кнопками up/down и нажимает right, либо с помощью кнопки left возвращаемся к началу письма и еще одним нажатием этой же кнопки разворачиваем список писем на весь экран для лучшего его обзора. Полистав список писем и не найдя ничего хорошего нажимаем left и оказавшись в режиме просмотра папок повторяем все операции сначала, или еще одним нажатием
left выходим из gnus'a.

Реализация этих изменений:

;; - - - - - - -- - -- - - - - - - - - - - - - - - - - - - - - - - - -
;; Кнопки в режиме просмотра групп - - - - - - - - - - - - - - - - - - -

;; Переопределяем Down на переход к началу слудющей строки
(define-key gnus-group-mode-map (kbd "<down>")
'my-next-line-nomark)

;; Переопределяем Up на переход к началу предыдущей строки
(define-key gnus-group-mode-map (kbd "<up>")
'my-previous-line-nomark)

;; Переопределяем PgDn на переход через страницу
(define-key gnus-group-mode-map (kbd "<next>")
'my-scroll-up-nomark)

;; Переопределяем End для перехода к конец списка
(define-key gnus-group-mode-map (kbd "<end>")
'my-goto-last-line)

;; Переопределяем Home для перехода в начало списка
(define-key gnus-group-mode-map (kbd "<home>")
'my-goto-first-line)

;; Переопределяем Right для входа в группу
(define-key gnus-group-mode-map (kbd "<right>")
'gnus-group-select-group)

;; Переопределяем Left для выхода из gnus'а
(define-key gnus-group-mode-map (kbd "<left>")
'gnus-group-exit)

;; - - - - - - -- - -- - - - - - - - - - - - - - - - - - - - - - - - -
;; Кнопки в режиме просмотра группы - - - - - - - - - - - - - - - - - -

;; Переопределяем Down на переход к началу слудющей строки
(define-key gnus-summary-mode-map (kbd "<down>")
'my-next-line-nomark)

;; Переопределяем Up на переход к началу предыдущей строки
(define-key gnus-summary-mode-map (kbd "<up>")
'my-previous-line-nomark)

;; Переопределяем PgDn на переход через страницу
(define-key gnus-summary-mode-map (kbd "<next>")
'my-scroll-up-nomark)

;; Переопределяем End
(define-key gnus-summary-mode-map (kbd "<end>")
'my-goto-last-line)

;; Переопределяем Home
(define-key gnus-summary-mode-map (kbd "<home>")
'my-goto-first-line)

;; Переопределяем Right на скроллинг сообщения
(define-key gnus-summary-mode-map (kbd "<right>")
'my-summary-right)

;; Переопределяем Left на:
;; , если плказывается не начало письма, то скролим назад на страницу
;; , если показывается начала письма, то прячем письмо и показываем полный список сообщений
;; , если показан полный список сообщений, то выходим в режим групп
(define-key gnus-summary-mode-map (kbd "<left>")
'my-summary-left)

;; Переход на последнюю строку
(defun my-goto-last-line ()
"Goto last line"

(interactive)
(goto-char (- (point-max) 1))
(beginning-of-line)
)

;; Переход на первую строку
(defun my-goto-first-line ()
"Goto last line"

(interactive)
(goto-char 0)
)

;; Переход к началу следующей строки, и если мы достигли конца буфера, то переход
;; к началу последней строки.
(defun my-next-line-nomark ()
"Goto beginning of next line preventing end-of-buffer position"

(interactive)
(next-line-nomark 1)
(when (eobp)
(goto-char (- (point) 1)))
(beginning-of-line)
)

;; Переход к началу следующей части буфера, и если мы достигли конца буфера, то переход
;; к началу последней строки.
(defun my-scroll-up-nomark ()
"Goto beginning of next page preventing end-of-buffer position"

(interactive)
(scroll-up-nomark nil)
(when (eobp)
(goto-char (- (point) 1)))
(beginning-of-line)
)

;; Переход к началу предыдущей строки
(defun my-previous-line-nomark ()
"Goto beginning of previous line"

(interactive)
(previous-line-nomark 1)
(beginning-of-line)
)

;; Функция для кнопки влево в режиме просмотра списка сообщений
(defun my-summary-left ()
"Multifunctional left button handler in summary mode"

(interactive)

;; Если буфер сообщения существует
(if (buffer-live-p (get-buffer gnus-article-buffer))
;; Буфер открытого сообщения существует,
;; получаем окно для этого буфера
(let ((article-window (get-buffer-window gnus-article-buffer t)))
;; Если окно nil, тоесть у буфера нет окна, то выходим из группы
(if (null article-window)
(my-summary-exit)

;; Окно с сообщение показано на экране, определяем начало ли показано или нет
;; Если показано начало, то значит убираем буфер сообщения с экрана,
;; иначе скролируем кусок страницы сообщения назад
(if (pos-visible-in-window-p 1 article-window)
;; Убираем буфер сообщения с экрана
(delete-other-windows)

;; Скролируем назад
(gnus-summary-prev-page)
)
)
)

;; Буфер сообщения не существует - выходим из группы
(my-summary-exit)
)
)

;; Выходим из группы.
;; Из-за того, что gnus перекидывает курсор к следующей группе
;; мы сохраняем позицию курсора в буфере групп перед выходом из группы
;; и восстанавливаем ее после выхода из группы
(defun my-summary-exit ()
"Exit summary preserving selected group"

(let ((old-group-point (gnus-eval-in-buffer-window gnus-group-buffer (point))))
(gnus-summary-exit)
(goto-char old-group-point)
)
)

;; Функция для кнопки вправо в режиме просмотра списка сообщений
(defun my-summary-right ()
"Scroll article buffer without jumping to new article at the end"

(interactive)
(gnus-summary-next-page nil nil t)
)

В дополнение ко всему, письма прочитанные более 15 дней назад, автоматически кладем в одноименные папки, добавив к ним (к именам папок) префикс "archive." и суффикс являющийся годом написания устаревшего письма. Например письмо из папки в mail.private написанное в 2004 году уйдет через 15 дней после прочтения в папку archive.mail.private.2004.

;; По умолчанию сообщения в группах не устаревают
(setq gnus-auto-expirable-newsgroups nil)

;; Время хранения почты после прочтения прежде чем она устреет
(setq nnmail-expiry-wait 15)

;; Настройка свойств групп - - - - - - - - - - - - - - - - - - - - - --
(setq gnus-parameters
'(("^nnml:mail\\.\\(.*\\)$"
(auto-expire . t)
(gcc-self . t))

("^nnml:list\\.\\(.*\\)$"
(auto-expire . t))

("^nnml:archive\\.mail\\.\\(.*\\)$"
(gcc-self . "nnml:mail.\\1"))
))

;; Устаревание производится через нашу ф-ию
(setq nnmail-expiry-target 'my-fancy-expiry-target)

;; Возвращает название группы куда складывать устаревшие письма для данной
(defun my-fancy-expiry-target (group)
"Returns a target expiry group with year included."
(let ((date (date-to-time
(or (message-fetch-field "date") (current-time-string))
)
)
)
(concat "nnml:archive." group "." (format-time-string "%Y" date))
)
)