понедельник, 21 января 2008 г.

WayBack

Иногда возникает необходимость запустить приложение не в настоящем времени, а в прошлом или будущем. Обычно это нужно для тестирования приложения или для обхода устаревания evaluation license. Достигается это переводом системных часов, либо запуском приложения в vmware - оба этих метода имеют свои очевидные недостатки. К счастью в linux есть такая замечательная вещь как LD_PRELOAD, позволяющяя "переопределить" импортируемые функции приложения. Так, например, если подменить time, gettimeofday и clock_gettime - то программа с большой долей вероятности будет жить в том времени который нам нужен, в то время как вся остальная система будет жить в обычном временном русле. Для этих целей я написал небольшую C'ную библиотеку. Устанавливается библиотека как обычно make install.

Для примера, небольшое тестовое приложение на C:

void main () {
printf ("%lu\n", time(0));
}

Запускаем:

akshaal@...:/tmp% ./test
1200947571

akshaal@...:/tmp% WAYBACK_TODAY=1970.01.01 LD_PRELOAD=libwayback.so.0 /tmp/test
63225

akshaal@...:/tmp% WAYBACK_LOG_LEVEL=3 WAYBACK_TODAY=1970.01.01 LD_PRELOAD=libwayback.so.0 /tmp/test
Delta time: 3094082896
time modified
63257


Библиотека libwayback не возвращает константу. После запуска, она определяет разницу между текущим временем и желаемым и запоминает ее как delta. В дальнейшем все вызовы к функция возвращающим время перехватываются и delta прибваляется к результату. Таким образом время не стоит на месте и в то же время она очень близко к желаемому.

Например приложение на Java и стандартная утилита date:

import java.util.Date;

public class test {
public static void main (String[] args) {
System.out.println (new Date());
}
}

akshaal@...:/tmp% java test
Mon Jan 21 22:39:26 EET 2008

akshaal@...:/tmp% WAYBACK_TODAY=2010.11.27 LD_PRELOAD=libwayback.so.0 java test
Sat Nov 27 20:40:39 EET 2010

akshaal@...:/tmp% date
Mon Jan 21 22:41:20 EET 2008

akshaal@...:/tmp% WAYBACK_TODAY=2010.11.27 LD_PRELOAD=libwayback.so.0 date
Sat Nov 27 20:41:50 EET 2010


К сожалению данный способ может быть использован для обхода устаревания лицензии :) у программ к которым есть evaluation license ограниченная на определенный срок (например JProfiler 5, YourKit Profiler 7, InteliJ Idea и еще много). Я не проверял работает ли (но уверен, что да :) ) ибо предпочитаю опенсоурс софт и вообще это плохо и я против использования библиотеки для этих целей :).

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

Библиотека написана за час и не является Абсолютным Решением. Кому надо - юзают на свой страх и риск, а с меня никакого спросу.

P.S. Больше всего напарился с одной вещью. Запускаю свинговое приложение, а оно виснет. Думаю че за нахрен. Копался-копался... оказывается java юзает pthread_condwait и ей в качестве параметра передается не относительное время (время когда ожидание прекращается), а абсолютное! Пришлось и эту функцию переопределить и вычитать дельту. Но как я искал.. strace говорит, что нить висит на futex каком-то.. и только чудом догадался, что futex может быть более нижним лаером под pthread'ом, а там уже полез смотреть как работа с X'ами идет в libawt.

четверг, 17 января 2008 г.

Scala - первое впечатление

Technology stack: java, spring framework, maven, eclipse, jboss

Все состыковалось нормально, но разочарований пока больше:
1. Поддержка Scala в eclipse'е просто никакая. Подсвечивает двумя цветами, комплишенов нет, рефакторинга нет, навигации нет. Может я что-то не правильно сделал с плагином, но для первого впечатления я бы сказал, что поддержки вообще нет.

2. Maven2 нормально собирает Scala, но нужно обязательно Maven 2.0.7 или выше. Сначало собираются все Java классы, а потом Scala. Поэтому врятли получится из Java сослаться на Scala класс. Это в пределах одного проекта. Если нужно наоборот, то надо делать два разных под модуля. А если в перемешку охото - вообще не знаю как (правда и желание левое).

3. Spring'у вобще-то пофиг инстансы какого класса делать. Главное, чтобы геттеры были. Какой-то особенной поддержки для scala в спринге нет (в отличии от BSH, Groovy и еще чего-то (jruby или jpython)).

4. Каким-то странным образом Scala делает getter'еры и setter'ы для переменных класса. То делает, то нет. Например для java.lang.Map не сделала. А для scala.collection.jcl.Map - сделала что-то типа setVar... Я сначало губу расскатал, думаю вот, наконец то не надо геттеры и сеттеры руками делать. А хрен. По умолчанию не делает само. Если в еклипсе это хотя бы для джавы двумя кликами делается, то тут это еще и руками писать вида: def getVar () = var.

5. Про override не понял. В примере override нарисован - у меня он говорит мол не ожидаю я тут override увидеть....

6. Интеграция с Java. Думаю да, клево, есть imlicit методы - значит наверное все круто и просто. Нефига. Все равно врапперы надо делать, чтобы из java Map получить scala Map. И trait scala Map не совместим с interface java Map.

7. Пишу val pending : List[(=>Unit)] = Nil; Определил. А вот pending = (println "") :: pending -- ругается, что (println "") - это Unit, а ему нужен =>Unit. lazy val v = println "" и последующий pending = v :: pending; тоже самое. v видетели Unit. Забил, сделал val pending : List[() => Unit] = Nil ... но осадок остался.

Понятно, что часть этой фигни - по моей неопытности и незнанию и наверняка решается. Но всеже.. )
Из приятного: type inference это хорошо, замыкания это хорошо, карринг это хорошо. Экономия кода не большая по сравнению с джавой, но она есть. Код чище, если на врапперы не втыкать. А если использовать приемущественно одну Scala, то вообще красота по чистоте будет.

P.S. Тут на работе постоянно бухтят о коллбэках в моем API и пытаются от них избавится... :) А буть скала, было бы это scala:
helper.runInScope (scope) {
println ("Hello world");
}


вместо:
helper.runInScope (scope, new Callback0 () {
public void run () {
println ("hello world");
}
});


Но scala там не быть :))

вторник, 8 января 2008 г.

CAL & Scala

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

CAL (Quark Framework) - прикольно все-таки, что это Haskell для Java, но вот что-то интеграция с Java меня не впечатлила - чисто на урове FFI как в Haskell. Как домэйн язык оно может и удобно, но как полная замена Java не катит. Правда сильно не вчитывался - может и не прав.

Scala - весмьа красиво и многообщеюще. Почему-то постоянно слышал про этот язык несколько лет назад, когда читал рассылку O'Caml, но ни разу Scala не удосужился попробовать. А зря, весьма элегантно. Немного расстроило отсутствие поддержи Scripting for Java (не помню номер JSR).

суббота, 5 января 2008 г.

CollectD

Грубо говоря - ни дня без патчей (хотя бы так, чем ничего). Провозившись с collectd нашел ошибку. Исправил. Патч (аж из трех новых строк) отослал. Может кто-то тоже возится, пытаясь понять, почему unixsock plugin для collectd не проставляет права на socket.
diff -urN old/src/unixsock.c new/src/unixsock.c
--- old/src/unixsock.c 2007-11-10 17:15:39.000000000 +0200
+++ new/src/unixsock.c 2008-01-05 20:45:17.000000000 +0200
@@ -29,6 +29,7 @@
#include <pthread.h>

#include <sys/socket.h>
+#include <sys/stat.h>
#include <sys/un.h>

#include <grp.h>
@@ -365,6 +366,8 @@
return (-1);
}

+ chmod (sa.sun_path, sock_perms);
+
status = listen (sock_fd, 8);
if (status != 0)
{

понедельник, 19 ноября 2007 г.

eurokbd

Собрал на скорую руку cab для модифицированной eurokbd, о которой я писал ранее.

LooxLight 2007-11-19

Подробнее о Looxlight (утилита для Fujitsu-Siemens Loox N560/C550) смотрите здесь.

Обновление!

Вот cab.
Вот исходники.

Добавил возможность блокрировать экран и клавиатуру. Раньше у меня стояло что-то-там-guard (screen guard или типа того). Но оно было глючное: иногда при разблокриовке весило кпк, при нажатии на кнопку suspend разблокировала экран, но при нажатии любой клавиши блокировало его обратно - выглядело это страшно, при нажатии на кнопки разблокировало экран и тутже блокировало обратно. Терпел. А вот недавно перепрошил КПК на новую версию WM5 (кстати стало побыстрее все рисоваться, а в каталог /windows так вообще мгновенно заходит (раньше 10сек)). После перепрошивки обнаружил, что жить без блокировки тяжело - батарея садится быстрее, кнопки нажимаются случайно, когда в кармане КПК лежит... И позавчера вечером не выдержал и решил написать сам. Ну как сказать, получилось вобщем-то. Отличается от прочих (пукнт 1.42) тем, что заточено чисто под Fujitsu-Siemens Loox N560/C550 (может конечно и на 720 заработает, дрова у них похожие), поэтому выключает и экран и клавиатуру в том числе и кнопку suspend(!!). Кстати, выключенный экран (при прослушивании музыки или аудиокниги) экономит более 50мА. С выключенным экраном заряжаться с USB должно быстрее.

Как установить:
Если КПК не Fujitsu-Siemens Loox N560/C550, то очень рекоменудю сделать backup! Если это Fujitsu-Siemens Loox N560/C550, то все равно рекомендую, хотя у меня и жены вроде ничего страшного не произошло пока. В любом случае, я ответственности никакой не несу. :) (Добполнение: У меня работает уже больше чем 2 года без проблем)

Скачать cab. Закинуть на кпк. Запустить установку. Установить. Подождать минуту для верности. Перезагрузить КПК (софтресет). Запустить LooxLight Control и поставить галочку напротив "Permit blocking". Ниже, напротив "Unblock by" два выпадающих списка. В них задается какими кнопками будет происходить РАЗБЛОКИРОВКА. Когда КПК будет заблокирована, эти две кнопки надо будет последовательно нажать. У меня это 2 и 3 кнопки. А по умолчанию почему-то стоит 2 и 4, ну и фик с ним. Теперь идем в Settings/Buttons и там вешаем LooxLight Control на ту кнопку, которой КПК будет БЛОКИРОВАТЬСЯ. У меня это долгое нажатие на кнопку 2. Готово. Можно тестировать.

При нажатии на первую из кнопок разблокировки, следующую нужно зажать в течении одной секунды. Во время ожидания клавиатура КПК будет подмигивать.

Как реализовано (в общих чертах):
С выключением экрана все просто. А вот с его блокировкой посложнее. LooxLight аттачит себя к gwes.exe. Находит в памяти keybddr.dll и правит в ней таблицу импортированных функций, а именно WaitForSingleObject. Эта функция вызывается нитями keybddr.dll для ожидания прерывания клавиатуры (0x10 и 0x2a), а также для ожидания срабатывания события suspend. Т.е. в таблице импорта keybddr.dll, LooxLight подменяет адрес WaitForSingleObject с адреса функции из библиотеки coredll.dll на собственную функцию. Получается, что в момент, когда keybddr инициирует ожидание нажатия, вызывается функция LooxLight'а, которая в свою очередь дергает оригинальную функцию. Если блокировка не включена, то результат сразу же возвращается в keybddr.dll. Иначе в результате проверяется пара адресов памяти на код нажатой клавиши. По нему решается, разблокировать ли экран или нет (грубо очень). Если нажатие нужно проигнорировать, то сообщаем OS о том, что мы обработали прерывание (InterruptDone) и вызываем оригинальный WaitForSingleObject - тоесть управление обратно в keybddr не передается, пока клавиатуру не разблокируется. Таким образом ни одной нажатие мимо нас в принципе не проходит. Ибо обработчик LooxLight находится первым в цепочке обработки прерывания клавиатуры.

Пока заметил, что если воткнуть КПК в крэдл, когда КПК заблокирована, то экран включается. Правда при этому клавиатура попрежнему заблокирована. Я думаю activesync что-то делает такое не хорошее, например вызывает ExtEscape - функция используемая для выключения/включения питания экрана. Или еще хуже. он дергает какое-то более общее API (варианты: вывести окно на передний план, послать уведомление, включить подсветку), которое как раз и и дергает ExtEscape. Вариантов лечения я вижу два:
1. Простой. Эпизодически запрашивать состояния питания на экран и если блокировка включена, а экран выключен - то выключать экран. Но я так не люблю по таймеру что-то делать. Да и мерцание будет, хоть и очень редко.
2. Сложный. Я уже попробовал пойти по этому пути - убил 3 часа и пока решил отложить. Смысл в том, что можно вклинится в какое-нибудь API какой-нибудь dll. Например можно было бы перехватить ExtEscape, но для этого нужно пробежаться по всем dll'кам в системе и каждую из них происнструментировать так же как и keybddr. Так я еще не пробовал, но есть подозрение, что может не сработать. Ибо у нас есть gwes.exe который и рулит GDI'ем. И если кто-то вызывает API у coredll, тот направляет его еще куда.. короче я к тому, что вызов ExtEscape из coredll может быть проскипан кодом, который напрямую работает с DeviceDriver'ом - а с ним напрямую, я так подозреваю, работает gwes. Было бы просто идиально вклинится в Display Driver. Когда кто-то дергает ExtEscape, тот в свою очередь вызывает DrvEscape callback у драйвера дисплея. Как это вообще работает. Когда gwes.exe грузит драйвер дисплея, он вызывает у него метод DrvDeviceEnable (или типа того). Передавая ему в качестве параметра структуру, которую драйвер должен заполнить. В этой структуре указатели на методы связаные с работой с дисплеем, в том числе и DrvEscape. После того, как драйвер заполнил эту структуру, далее gwes уже работает только с методами прописанными в этой структуре. Было бы клево, найти в памяти адрес этой структура и прописать свой DrvEscape туда. Но полазив по header'ам от wince я пока не нашел цепочку, чтобы добраться до туда. Может имея HDC можно по нему добраться? Но HDC - это указатель на C++ структуру и я этот путь пока не осилил. Другой вариант, изучать gwes.exe и посмотреть куда он копирует адрес заполненной структуры и плясать отсюда. Но блин, gwes.exe как-то криво разобрался IDA (или криво задампился). Все локальные переменные имеют совсем не те адреса, на какие ссылается код. Еще можно было бы посмотреть coredll.dll и как там работает ExtEscape и потыкаться оттуда, но coredll.dll у меня задамлпнного нет (это вообще вроде kernel сам). А дамп всей ОЗУ неплохо так разобранный во времена когда искал как управлять диодами я где-то посеял..

Вобщем я решил пока этот вопрос отложить. Посмотрим как оно будет анноить или нет.

--------
Будущие версии программы можно будет найти здесь.
Другая программа для уменьшения расхода заряда батареи описана здесь.

суббота, 17 ноября 2007 г.

iphone?


Прочитал недавно статью про iphone. Какой он там весь удобный и все такое. Заразился идеей о управлении КПК (WM5) с помощью одной руки и даже одного пальца. Для меня это актуально, так как пользуюсь КПК в метро. Поменял часть софта - менеджер задач и лоунчер. А с клавиатурой пришлось повозится. Перепробовал много вариантов. PocketCM уовлетворила почти всем, кроме того, что была глючная и не давала сделать такую раскладку какую я хочу. И вот я случайно наткнулся на eurokbd. Программа интересная тем, что opensource (хоть и слегка кривовато написана, но да ладно, дареному коню... спасибо автору огромное!). Подправил для нее раскладку, перерисовал скины, подправил исходники и вот, что получилось.

Картинко 1
Картинко 2
Картинко 3
Картинко 4

Первое, что нужно было сделать - придумать раскладку. Раскладка должна была быть такой, чтобы легко попадать по клавишам большим пальцем моей руки (держа КПК той же рукой). Qwerty не подходит. Был соблазн набросать как придется новую раскладку, но это абсолютно не аправданно. Решил сгенерировать оптимальный вариант с учетом частоты следования букв друг за другом. Написал на perl анализатор текста на входе которого текст, на выходе описание для graphwiz (буква -> буква [вес]). Вес являлся частотой появления комбинации букв. На основе такого описания graphwiz построил графики, расположив узлы таким образом, чтобы расстояния между элементами с наибольшими весами было наименьшее. Таким образом минимизируется время набор слов. На графиках линии связей между буквами убраны для простоты картинки.

Инглиш
Русский

Далее нарисовал скины. А саму eurokbd пришлось пропатчить, чтобы она активную кнопку подсвечивала белым фоном (а не черным как это жестко вбито в исходниках), а также не выводила меню переключения раскладки, а переключала ее сразу же с rus на lat и с lat на rus). Итого получилась очень удобная клавиатура! Несмотря на то, что раскладка не qwerty - привыкаешь ОЧЕНЬ быстро. Я уже не могу на стандартную мелкую смотреть без тошноты.

http://toril.ru/pda/eukbak.zip

Для интересующихся, скрипт на perl для анализа текста (textanal.pl):

#!/usr/bin/perl
# Akshaal (C) 2007

# Creates dot graph for link between characters

use warnings;
use strict;

use utf8;
use encoding 'utf8';

my %ass;
my $prev;

my %allow;
for (split '', "йцукенгшщзхъфывапролджэячсмитьбюё ") {
$allow{$_} = 1;
};

sub add ($) {
my $c = shift;

if (defined $prev) {
if (ord($prev) > ord($c)) {
$ass{"$prev$c"} ++;
} else {
$ass{"$c$prev"} ++;
}
}

$prev = $c;
}

# Calc weights
while (<>) {
chop;
for my $c (split '') {
$c = lc $c;

unless ($allow{$c}) {
$c = ' ';
}

add ($c);
}
add (' ');
}

# Create dot
print "graph D {\n";
for my $link (keys %ass) {
my $weight = $ass {$link};
my $a = substr $link, 0, 1;
my $b = substr $link, 1, 1;

print " \"$a\" -- \"$b\" [weight=$weight, style=invis];\n";
}
print "}\n";


А это патч на eurokbd:

--- eurokbd.cpp.old 2007-10-11 20:45:00.000000000 +0300
+++ eurokbd.cpp 2007-11-17 17:58:48.000000000 +0200
@@ -369,10 +369,12 @@

if(vk==_VK_MOD)
{
- if( m_subkeyCurrent!=SUBKEY_PARENT )
- {
- LoadConfig(m_subkeyCurrent->data.pw);
- }
+ if(0==wcscmp(m_pwCurrentConfigName.ptr(), L"rus"))
+ {
+ LoadConfig(L"lat");
+ } else {
+ LoadConfig(L"rus");
+ }
} else if(vk==_VK_FN)
{
// } else if(vk==_VK_CUT)
@@ -788,8 +790,8 @@
COLORREF bgColor, fgColor;
if( m_subkeyCurrent==p )
{
- fgColor = RGB(0xFF,0xFF,0xFF);
- bgColor = RGB(0x00,0x00,0x00);
+ bgColor = RGB(0xFF,0xFF,0xFF);
+ fgColor = RGB(0x00,0x00,0x00);
} else
{
fgColor = m_config->m_colors[p->group][0];
@@ -832,8 +834,8 @@
COLORREF bgColor, fgColor;
if( m_subkeyCurrent==SUBKEY_PARENT )
{
- fgColor = RGB(0xFF,0xFF,0xFF);
- bgColor = RGB(0x00,0x00,0x00);
+ bgColor = RGB(0xFF,0xFF,0xFF);
+ fgColor = RGB(0x00,0x00,0x00);
} else
{
fgColor = m_config->m_colors[m_keyCurrent->group][0];
@@ -987,43 +989,6 @@
}
if(m_hwndPop)
{
- // кнопка Mod
- if(m_keyCurrent->vk==_VK_MOD)
- {
- m_keyCurrent->subkeys.destroy();
-
- WIN32_FIND_DATA ffdata;
- WCHAR* pwMask = new WCHAR[wcslen(g_pwDllDir)+6+1];
- wcscpy(pwMask, g_pwDllDir);
- wcscat(pwMask, L"\\*.txt");
- //UINT iLenMask = wcslen(swMask);
- HANDLE hFind = FindFirstFile(pwMask, &ffdata);
- if( hFind!=INVALID_HANDLE_VALUE )
- {
- int i = 0;
- do
- {
- SUBKEYENTRY* psk = new SUBKEYENTRY;
- psk->vk = _VK_MOD;
- //psk->data.pw= new WCHAR[iLenMask-5+wcslen(ffdata.cFileName)+1];
- //wcscpy(psk->data.pw, swMask);
- //wcscpy(psk->data.pw+iLenMask-5, ffdata.cFileName);
- ffdata.cFileName[wcslen(ffdata.cFileName)-4] = '\0'; // trim .txt
- psk->data.pw= _wcsdup(ffdata.cFileName);
- psk->left = 0;
- psk->top = (i+1)*-16;
- psk->right = 80;
- psk->bottom = (i+0)*-16;
- psk->group = SK_GROUP_LATIN;
- psk->desc.pw= 0;
- m_keyCurrent->subkeys.add_first(psk);
- i ++;
- } while(FindNextFile(hFind, &ffdata));
- FindClose(hFind);
- }
- delete []pwMask;
- }
-
// кнопка Fn
if(m_keyCurrent->vk==_VK_FN)
{
@@ -1148,7 +1113,7 @@
// если нет обоев - рисуем кнопочку, если кнопка текущая - рисуем чёрную кнопочку
if(!m_hbmSkin || p==m_keyCurrent)
{
- COLORREF bgColor = p==m_keyCurrent ? RGB(0x00,0x00,0x00) : m_config->m_colors[p->group][1];
+ COLORREF bgColor = p==m_keyCurrent ? RGB(0xff,0xff,0xff) : m_config->m_colors[p->group][1];
CGdiObj>HBRUSH> hBrushBg2 = CreateSolidBrush(bgColor);
assert(hBrushBg2);
DrawButton(hdc, r0, hBrushBg2, hPenTL, hPenBR);
@@ -1158,13 +1123,13 @@
if (0!=p->data.pw)
{
SelectObject(hdc, m_hFontBig);
- COLORREF fgColor = p==m_keyCurrent ? RGB(0xFF,0xFF,0xFF) : m_config->m_colors[p->group][0];
+ COLORREF fgColor = p==m_keyCurrent ? RGB(0x00,0x00,0x00) : m_config->m_colors[p->group][0];
DrawText(hdc, p->data, &rText, DT_NOPREFIX | DT_BOTTOM | DT_LEFT, fgColor);

if (0!=p->desc.pw) // надстрочный знак
{
SelectObject(hdc, m_hFontIndex);
- COLORREF fgColor2 = p==m_keyCurrent ? RGB(0xFF,0xFF,0xFF) : m_config->m_colorDesc;
+ COLORREF fgColor2 = p==m_keyCurrent ? RGB(0x00,0x00,0x00) : m_config->m_colorDesc;
//if(0!=p->desc2.pw) // есть оба знака - надстрочный двинем чуть левее
// rText.right -= 7*m_vga;
DrawText(hdc, p->desc, &rText, DT_NOPREFIX | DT_RIGHT | DT_TOP, fgColor2);
@@ -1175,13 +1140,13 @@
{
rText.top += 4*m_vga;
SelectObject(hdc, m_hFontIndex);
- COLORREF fgColor2 = p==m_keyCurrent ? RGB(0xFF,0xFF,0xFF) : m_config->m_colorDesc2;
+ COLORREF fgColor2 = p==m_keyCurrent ? RGB(0x00,0x00,0x00) : m_config->m_colorDesc2;
DrawText(hdc, p->desc2, &rText, DT_NOPREFIX | DT_RIGHT | DT_TOP, fgColor2);
}
} else if (0!=p->desc.pw) // текст вроде 'Caps'
{
SelectObject(hdc, m_hFontSmall);
- COLORREF fgColor2 = p==m_keyCurrent ? RGB(0xFF,0xFF,0xFF) : m_config->m_colors[p->group][0];
+ COLORREF fgColor2 = p==m_keyCurrent ? RGB(0x00,0x00,0x00) : m_config->m_colors[p->group][0];
DrawText(hdc, p->desc, &rText, DT_NOPREFIX | DT_CENTER | DT_VCENTER, fgColor2);
}
}
@@ -1205,7 +1170,6 @@
{
m_keyCurrent = pKey;
if(0==m_nPopupDelay ||
- m_keyCurrent->vk==_VK_MOD ||
m_keyCurrent->vk==_VK_FN )
{
PopUp();



Долой стилус!