понедельник, 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))
)
)